Skip to content

Commit 64a4dc5

Browse files
LinearModelPredictor
1 parent a92438c commit 64a4dc5

6 files changed

Lines changed: 304 additions & 1904 deletions

File tree

docs/model_based_design.md

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,49 +16,58 @@ This class contains properties of the static model, those properties derived fro
1616
Included are the properties:
1717

1818
* ``model_name``
19-
* ``sbml_str`` is an SBML string for the model
19+
* ``sbml_str`` is an SBML string for the model. Antimony models are converted to SBML using roadrunner.
2020
* Various static properties, such as ``species_names``, ``num_species``
2121

22+
StaticModel uses roadrunner transiently to query the underlying model (e.g., names of floating species).
23+
2224
### ``DynamicModel``
2325

2426
This class is a container for properties obtained running a simulation.
2527

2628
The constructor has arguments for: static model, and the following:
2729

28-
* ``start_time``, ``end_time`, ``num_point``
2930
* ``jacobian_collection_arr`` the Jacobians at each of the timepoints
3031
* ``timepoint_arr`` is the times at which simulation results are reported
31-
* ``forced_input_collection_arr`` an array of arrays of forced inputs calculated at each timepoint
32+
* ``forcing_input_collection_arr`` an array of arrays of forced inputs calculated at each timepoint
3233
* ``timecourse_df`` is the timecourse for the dynamics
3334

3435
The following are properties calculated from the properties above:
3536

3637
* ``jacobian_median_arr`` is the median of the values in ``jacobian_collection_arr``, a computed property.
3738
* ``jacobian_std_arr`` is the standard deviation of Jacobian values, a computed property.
39+
* ``start_time``, ``end_time``, ``num_point``
3840

3941
The following are external methods:
4042

41-
* ``makeSubmodel(start_time, end_time)`` uses a subset of the dynamical data (as specified by start and end) to construct a new dynamical model that copies of subset of the data in the current model into the new model.
42-
* ``makeModel(start_time, end_time, num_point, StaticModel)`` runs simulations to collect
43+
* ``makeSubmodel(start_time, end_time)`` uses a subset of the dynamical data (as specified by start and end) to construct a new dynamical model that copies of subset of the data in the current model into the new model. This method uses slicing to obtain values for constructing a new DynamicModel.
44+
* ``makeFromSimulation(start_time, end_time, num_point, StaticModel)`` is a class method that runs simulations to obtain the arguments for the DynamicModel constructor. This is the only method in DynamicModel that uses roadrunner. If the model_name begins with "BIOMD", then: (a) the model is in BIOMODELS_DIR and (b) ``end_time`` is obtained from the module ``biomodels_iterator``. ``end_time=None`` triggers autodetect of ``end_time``.
45+
* ``_makeEndtime`` is a method formerly in LRoadrunner.
4346

4447
### ``LinearModelPredictor``
4548

46-
This class does linear prediction and evaluations of these predictions. It is constructed with a ``DynamicModel`` and ``num_step``, the number of steps ahead to do the prediction. It has the following methods
49+
This class does linear prediction and evaluations of these predictions. It is constructed with a ``DynamicModel``, ``jacobian_selection`` and ``num_step``, the number of steps ahead to do the prediction. It has the following methods
4750

4851
* ``predict`` provides predictions for the timecourse of the ``DynamicModel``.
4952
* ``score`` scores the prediction using the Score class.
5053
* ``plotPrediction`` plots the timecourse and the prediction from the ``start_time`` to the ``end_time``
5154

5255
### ``Score``
56+
* We will use ``makeScoreInfo`` to construct the various score metrics to evaluate the quality of a prediction.
5357

5458
### ``MultipleLinearPredictor``
5559

56-
This class performs piece-wise linear prediction. It is constructed wtih a DynamicModel, ``num_step``, and a collection of timepoints a which a new dynamic model is constructed.
60+
This class performs piece-wise linear prediction. The times at which
61+
there is a partition of the linear model is a "split point". Using slicing, we can easily construct the DynamicModels for a collection of split points. (Of course, all will have the same StaticModel.) If there are n split points, then there are n + 1 linear models.
62+
Note that the old Trajectory.sequentialPartition / nonsequentialPartition aren't mentioned beause their implementation is deferred.
63+
* When splitting a ``DynamicModel``, slicing is used, not simulation.
5764

5865
* ``predict``
5966
* ``score``
6067
* ``plotPrediction`` The plot shows predicted and actual (simulated) values with vertical dashed lines to indicate regions for submodels.
6168

69+
70+
6271
## To Do
6372

6473
### Prompts
@@ -67,5 +76,4 @@ This class performs piece-wise linear prediction. It is constructed wtih a Dynam
6776
1. Review the design and provide comments on where confusions exist as well as where improvements can be made. Do not implement yet.
6877
1. Implement StaticModel as described in @model_based_design.md and associated tests.
6978
2. Implement DynamicModel as described in @model_based_design.md and associated tests.
70-
3.
7179
4. Update the modules in scripts to use ...

notebooks/score_analysis.ipynb

Lines changed: 236 additions & 1889 deletions
Large diffs are not rendered by default.

src/constants.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,6 @@
4040
NULL_ARRAY = np.array([])
4141

4242
# Jacobian selection
43-
JAC_FITTED = "fit_gershgorin" # Fit a Jacobian, selecting diagonal elements using Gershgorin circles
43+
JAC_FITTED = "fit_gershgorin" # Fit a Jacobian by using the timecourse for each row
4444
JAC_MEDIAN = "median" # Use the median Jacobian
4545
JAC_FIRST = "first" # Use the first Jacobian

src/dataframe_serializer.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,18 +15,22 @@ class DataframeSerializer:
1515
an instance from an existing file.
1616
"""
1717

18-
def __init__(self, path: str) -> None:
18+
def __init__(self, path: str, is_initialize: bool = False) -> None:
1919
"""
2020
Parameters
2121
----------
2222
path : str
2323
Path to the CSV serialization file.
24+
is_initialize : bool
25+
Whether to initialize the CSV file by writing an empty DataFrame with the appropriate columns.
2426
"""
2527
self._path = path
2628
if os.path.exists(path):
2729
self.dataframe: pd.DataFrame = pd.read_csv(path)
2830
else:
2931
self.dataframe = pd.DataFrame()
32+
if is_initialize:
33+
self.dataframe = pd.DataFrame()
3034

3135
def __eq__(self, other: object) -> bool:
3236
if not isinstance(other, DataframeSerializer):

src/score.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -77,19 +77,24 @@ class Score:
7777
"""
7878

7979
def __init__(self, serialization_path: str = SERIALIZATION_PATH,
80-
is_ignore_first_prediction: bool = True) -> None:
80+
is_ignore_first_prediction: bool = True,
81+
is_initialize: bool = False) -> None:
8182
"""
8283
Parameters
8384
----------
8485
serialization_path : str
8586
Path to a CSV file for persistence.
8687
is_ignore_first_prediction : bool
8788
Whether to ignore the first prediction when computing scores, since it may be an outlier.
89+
is_initialize : bool
90+
Whether to initialize the CSV file by writing an empty DataFrame with the appropriate columns.
8891
"""
89-
self._serializer = DataframeSerializer(serialization_path)
92+
self._serializer = DataframeSerializer(serialization_path,
93+
is_initialize=is_initialize)
9094
self._serialization_path = serialization_path
9195
self._is_ignore_first_prediction = is_ignore_first_prediction
92-
96+
if is_initialize:
97+
self._serializer.serialize([])
9398
@property
9499
def score_df(self) -> pd.DataFrame:
95100
return self._serializer.dataframe

tests/test_score.py

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,6 @@
1313
import pandas as pd # type: ignore
1414

1515
import src.constants as cn # type: ignore
16-
from src.l_roadrunner import LRoadrunner # type: ignore
17-
from src.trajectory import Trajectory # type: ignore
1816
from src.score import (Score, ScoreInfo, # type: ignore
1917
AGGREGATION_TYPE, AGGREGATION_MEAN, AGGREGATION_COUNT)
2018

@@ -71,6 +69,42 @@ def test_existing_file_loaded(self) -> None:
7169
restored = Score(serialization_path=score._serialization_path)
7270
self.assertFalse(restored.score_df.empty)
7371

72+
def test_is_initialize_true_clears_existing_data(self) -> None:
73+
"""is_initialize=True gives empty score_df even when CSV has existing data."""
74+
if IGNORE_TESTS:
75+
return
76+
score = _make_score()
77+
path = score._serialization_path
78+
reset = Score(serialization_path=path, is_initialize=True)
79+
self.assertTrue(reset.score_df.empty)
80+
81+
def test_is_initialize_true_writes_empty_csv(self) -> None:
82+
"""is_initialize=True creates the CSV file on disk."""
83+
if IGNORE_TESTS:
84+
return
85+
path = _temp_csv_path()
86+
Score(serialization_path=path, is_initialize=True)
87+
self.assertTrue(os.path.exists(path))
88+
89+
def test_is_initialize_true_subsequent_add_starts_fresh(self) -> None:
90+
"""After is_initialize=True, addTestResult produces the correct row count."""
91+
if IGNORE_TESTS:
92+
return
93+
score = _make_score()
94+
path = score._serialization_path
95+
reset = Score(serialization_path=path, is_initialize=True)
96+
reset.addTestResult(TRUE_DF, PRED_DF)
97+
self.assertEqual(len(reset.score_df), 1 + len(TRUE_DF.columns))
98+
99+
def test_is_initialize_false_is_default(self) -> None:
100+
"""Default is_initialize=False preserves existing CSV data."""
101+
if IGNORE_TESTS:
102+
return
103+
score = _make_score()
104+
path = score._serialization_path
105+
reloaded = Score(serialization_path=path)
106+
self.assertFalse(reloaded.score_df.empty)
107+
74108

75109
class TestAddTestResult(unittest.TestCase):
76110
"""Tests for Score.addTestResult."""
@@ -419,6 +453,8 @@ def _checkBiomodel(self, model_num: int, end_time: float) -> None:
419453
"""Run full pipeline for one BioModel and assert valid score statistics."""
420454
if IGNORE_TESTS:
421455
return
456+
from src.l_roadrunner import LRoadrunner # type: ignore
457+
from src.trajectory import Trajectory # type: ignore
422458
l_roadrunner = LRoadrunner.makeBiomodel(model_num=model_num, start_time=0.0, end_time=end_time, num_point=11)
423459
true_df = l_roadrunner.timecourse_df
424460
trajectory = Trajectory(l_roadrunner)

0 commit comments

Comments
 (0)