Skip to content

Commit 0f1f8b5

Browse files
Refactor getPhrasedml->getPhraSEDML, separate classes for time course simulations, progress on MultipleModelTimeCourse.
1 parent 489d3aa commit 0f1f8b5

16 files changed

+633
-344
lines changed

examples/usage_examples.ipynb

Lines changed: 329 additions & 179 deletions
Large diffs are not rendered by default.

src/model.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@ def _getSBMLFromReference(self)->str:
174174
raise ValueError(f"Failed to fetch SBML from URL: {self.model_ref}")
175175
return sbml_str
176176

177-
def getPhrasedml(self):
177+
def getPhraSEDML(self):
178178
params = ", ".join(f"{param} = {val}" for param, val in self.param_change_dct.items())
179179
if len(params) > 0:
180180
params = f" with {params}"
@@ -188,7 +188,7 @@ def __str__(self)->str:
188188
"""
189189
Construct the PhraSED-ML string. This requires:
190190
"""
191-
return self.getPhrasedml()
191+
return self.getPhraSEDML()
192192

193193
@staticmethod
194194
def findReferenceType(model_ref:str, model_ids:List[str], ref_type:Optional[str]=None)->str:

src/multiple_model_time_course.py

Lines changed: 99 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,19 @@
11
'''Provides comparisons between multiple simulations.'''
22

3+
"""
4+
This module provides a class for comparing multiple models with common variables
5+
for the same time course. The class allows for the simulation of a collection of models
6+
with the same variables and generates plots and reports for the comparisons.
7+
The class is designed to be used with the PhraSED-ML format and provides methods
8+
for creating simulation directives, task directives, report directives, and plot directives.
9+
10+
The generation of PhraSED-ML strings is done through the `getPhrasedml` method,
11+
which constructs the string by adding the necessary directives based on the provided
12+
model references, simulation parameters, and display variables. This "late generation" approach
13+
allows for flexibility in model definitions since the definitions in the constructor
14+
can reference other model definitions that occur later on.
15+
"""
16+
317
import constants as cn # type: ignore
418
from simple_sedml_base import SimpleSEDMLBase # type:ignore
519
from typing import Optional, List
@@ -10,10 +24,16 @@
1024
class MultipleModelTimeCourse(SimpleSEDMLBase):
1125
"""Provides comparisons between multiple simulations."""
1226

13-
def __init__(self, start:float=cn.D_START, end:float=cn.D_END, num_step:int=cn.D_NUM_STEP,
14-
num_point:int=cn.D_NUM_POINT, algorithm:str=cn.D_ALGORITHM,
15-
model_refs:Optional[List[str]]=None,
16-
compare_variables:Optional[List[str]]=None,
27+
def __init__(self,
28+
model_refs:List[str],
29+
start:float=cn.D_START,
30+
end:float=cn.D_END,
31+
num_step:Optional[int]=None,
32+
num_point:Optional[int]=None,
33+
algorithm:str=cn.D_ALGORITHM,
34+
time_course_id:Optional[str]=None,
35+
display_variables:Optional[List[str]]=None,
36+
**parameter_dct,
1737
):
1838
"""Simulates a collection of models with common variables for the same time course.
1939
All models have the compared_variables. The outputs are:
@@ -29,6 +49,7 @@ def __init__(self, start:float=cn.D_START, end:float=cn.D_END, num_step:int=cn.D
2949
models (Optional[List[str]], optional): List of model references. Defaults to None.
3050
variables (Optional[List[str]], optional): List of variables to be compared. Defaults to None.
3151
if not provided, all variables in the model are used.
52+
parameters (Optional[dict], optional): Dictionary of parameters whose values are changed
3253
3354
Example 1: Compare two models with the same variables
3455
mmtc = MultipleModelTimeCourse(start=0, end=10, num_step=100,
@@ -44,22 +65,39 @@ def __init__(self, start:float=cn.D_START, end:float=cn.D_END, num_step:int=cn.D
4465
mmtc.addModel(model1_str, k1=0.2, k2=0.4)
4566
sedml_str = mmtc.getSEDMLString()
4667
"""
68+
# Error checks
69+
if len(model_refs) == 0:
70+
raise ValueError("No models have been added to the simulation.")
71+
#
72+
super().__init__()
73+
#
4774
self.start = start
4875
self.end = end
4976
self.num_step = num_step
5077
self.num_point = num_point
5178
self.algorithm = algorithm
52-
if model_refs is not None:
53-
for model_ref in model_refs:
54-
self.addModel(model_ref)
5579
self.model_refs = model_refs
56-
if compare_variables is None:
57-
compare_variables = []
58-
self.compared_variables = compare_variables
59-
80+
self.time_course_id = time_course_id
81+
if display_variables is None:
82+
display_variables = []
83+
self.display_variables = display_variables
84+
self.parameter_dct = parameter_dct
85+
6086
def _makeSimulationDirective(self):
6187
self.addSimulation(SIM_ID, "uniform", start=self.start, end=self.end, num_step=self.num_step,
6288
num_point=self.num_point, algorithm=self.algorithm)
89+
90+
@staticmethod
91+
def _makeTaskID(model_id:str)->str:
92+
"""Make a task ID from the model ID and simulation ID.
93+
94+
Args:
95+
model_id (str): model ID
96+
97+
Returns:
98+
str: task ID
99+
"""
100+
return f"t{model_id}"
63101

64102
def _makeTaskDirectives(self):
65103
"""Make the task directives for the compared variables.
@@ -70,79 +108,82 @@ def _makeTaskDirectives(self):
70108
if len(self.model_dct) == 0:
71109
raise ValueError("No models have been added to the simulation.")
72110
#
73-
for model_id in self.model_dct.keys():
111+
for model_id in self.model_refs:
74112
task_id = self._makeTaskID(model_id)
75113
self.addTask(task_id, model_id, SIM_ID)
76114

77-
def _makeReportDirective(self):
78-
"""Make the report directive for the compared variables.
115+
def _makeScopePrefix(self, model_id:str)->str:
116+
"""Makes the scoping prefix for the compared variables.
117+
118+
Args:
119+
model_id (str): model ID
79120
80121
Returns:
81-
str: report directives
122+
str: task name
82123
"""
83-
if self.compared_variables is None:
84-
first_model_id = list(self.model_dct.keys())[0]
85-
self.compared_variables = list(self.model_dct[first_model_id].getInformation().floating_species_dct.keys())
86-
#
87-
report_variables = []
88-
for model_id in self.model_dct.keys():
89-
task_prefix = self._makeTaskID(model_id) + "."
90-
new_report_variable = [task_prefix + v for v in self.compared_variables]
91-
report_variables.extend(new_report_variable)
92-
self.addReport(*report_variables)
93-
124+
return self._makeTaskID(model_id) + "."
125+
94126
def _makeVariables(self):
95127
# Creates variables if it's None
96-
if self.compared_variables is None:
128+
if self.display_variables is None:
97129
first_model_id = list(self.model_dct.keys())[0]
98-
self.compared_variables = list(self.model_dct[first_model_id].getInformation().floating_species_dct.keys())
130+
self.display_variables = list(self.model_dct[first_model_id].getInformation().floating_species_dct.keys())
99131

100-
def _makePlotDirectives(self):
101-
"""Make the plot directives for the compared variables.
132+
def _makeReportDirective(self, task_ids:List[str]):
133+
"""Make the report directive for the compared variables.
134+
135+
Args:
136+
task_ids: list of task ids
102137
103138
Returns:
104-
str: plot directives
139+
str: report directives
105140
"""
106-
#
141+
# Calculate the task ids to consider
142+
# Handle the case where no variables are provided
107143
self._makeVariables()
108-
for variable in self.compared_variables:
109-
plot_variables = []
110-
for model_id in self.model_dct.keys():
111-
task_prefix = self._makeTaskID(model_id) + "."
112-
plot_variables.append(task_prefix + variable)
113-
self.addPlot(*plot_variables, title=variable)
144+
#
145+
report_variables = []
146+
for variable in self.display_variables:
147+
new_report_variables = [self._makeScopePrefix(m) + variable for m in task_ids]
148+
report_variables.extend(new_report_variables)
149+
self.addReport(*report_variables)
114150

115-
@staticmethod
116-
def _makeTaskID(model_id:str)->str:
117-
"""Make a task ID from the model ID and simulation ID.
151+
def _makePlotDirective(self, task_ids:List[str]):
152+
"""Make the report directive for the compared variables.
153+
There is one plot for each variable that plots comparisons of the models.
118154
119155
Args:
120-
model_id (str): model ID
156+
task_ids: list of task ids
121157
122158
Returns:
123-
str: task ID
159+
str: report directives
124160
"""
125-
return f"t{model_id}"
161+
#
162+
self._makeVariables()
163+
for variable in self.display_variables:
164+
plot_variables = [self._makeScopePrefix(m) + variable for m in task_ids]
165+
self.addPlot(*plot_variables, title=variable)
126166

127-
def getPhrasedml(self)->str:
167+
def getPhraSEDML(self)->str:
128168
"""Construct the PhraSED-ML string. This requires:
129169
1. Create simulation and task directives
130170
2. Changing the model and report
131171
132172
Returns:
133-
str: _description_
173+
str: Phrased-ML string
134174
"""
135-
##
136-
# Add the simulation directive
137-
self.addSimulation(SIM_ID, "uniform", start=self.start, end=self.end, num_step=self.num_step,
138-
num_point=self.num_point, algorithm=self.algorithm)
139-
# Add a task directive for each model
140-
for model_id in self.model_dct.keys():
141-
task_id = self._makeTaskID(model_id)
142-
self.addTask(task_id, model_id, SIM_ID)
143-
for plot in self.plot_dct.values():
144-
plot.changeVariableScope(model_id, task_id)
145-
# FIXME: do variable change for report
175+
# Add the models specified in the constructors
176+
exclude_ids = list(self.model_dct.keys())
177+
for idx, model_ref in enumerate(self.model_refs):
178+
model_id = f"model{idx}"
179+
self.addModel(model_id, model_ref, is_overwrite=True, **self.parameter_dct)
180+
# Calculate the task ids to consider
181+
task_ids = list(set(self.model_dct.keys()) - set(exclude_ids))
182+
# Add the other directives
183+
self._makeSimulationDirective()
184+
self._makeTaskDirectives()
185+
self._makeReportDirective(task_ids)
186+
self._makePlotDirectives(task_ids)
146187
#
147188
return self.self.super().getPhrasedml()
148189

@@ -152,4 +193,4 @@ def __str__(self)->str:
152193
Returns:
153194
str:
154195
"""
155-
return self.getPhrasedml()
196+
return self.getPhraSEDML()

src/plot.py

Lines changed: 19 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
from typing import Optional, List, Union
2+
import constants as cn # type: ignore
23

34
class Plot:
45
def __init__(self, x_var:str, y_var:Union[str, List[str]], z_var:Optional[str]=None, title:Optional[str]=None,
5-
is_plot:bool=True)->None:
6+
is_plot:bool=True)->None:
67
"""
78
Plot class to represent a plot in the script. The following cases are supported:
8-
plot x vs y (z is None, y is str)
9-
plot x vs y1, y2, y3 (z is None, y is a list of str)
10-
plot x vs y vs z (z is a str, y is str)
9+
plot x vs y (z is None, y is str)
10+
plot x vs y1, y2, y3 (z is None, y is a list of str)
11+
plot x vs y vs z (z is a str, y is str)
1112
1213
Args:
1314
x_var (str): x variable
@@ -21,7 +22,7 @@ def __init__(self, x_var:str, y_var:Union[str, List[str]], z_var:Optional[str]=N
2122
self.title = title
2223
self.is_plot = is_plot
2324

24-
def getPhrasedml(self)->str:
25+
def getPhraSEDML(self)->str:
2526
if not self.is_plot:
2627
return ""
2728
if self.z_var is None:
@@ -46,24 +47,22 @@ def __str__(self)->str:
4647
Returns:
4748
str: PhraSED-ML string
4849
"""
49-
return self.getPhrasedml()
50+
return self.getPhraSEDML()
5051

51-
#FIXME: test
52-
def changeVariableScope(self, old_scope:str, new_scope:str)->None:
53-
"""Change the scope of the variables in the plot. For example,
54-
change "model1.x" to "task1.x"
52+
def scopeVariables(self, scope:str)->None:
53+
"""_summary_
5554
5655
Args:
57-
old_scope (str): old scope
58-
new_scope (str): new scope
56+
scope (str)
5957
"""
60-
if self.x_var.startswith(old_scope + "."):
61-
self.x_var = self.x_var.replace(old_scope, new_scope)
62-
if isinstance(self.y_var, str) and self.y_var.startswith(old_scope + "."):
63-
self.y_var = self.y_var.replace(old_scope, new_scope)
58+
scope_prefix = scope + "."
59+
self.x_var = scope_prefix + self.x_var
60+
if isinstance(self.y_var, str):
61+
self.y_var = scope_prefix + self.y_var
6462
elif isinstance(self.y_var, list):
6563
for i in range(len(self.y_var)):
66-
if self.y_var[i].startswith(old_scope + "."):
67-
self.y_var[i] = self.y_var[i].replace(old_scope, new_scope)
68-
if self.z_var is not None and self.z_var.startswith(old_scope + "."):
69-
self.z_var = self.z_var.replace(old_scope, new_scope)
64+
self.y_var[i] = scope_prefix + self.y_var[i]
65+
else:
66+
raise RuntimeError(f"y_var must be a string or a list of strings, not {type(self.y_var)}")
67+
if self.z_var is not None:
68+
self.z_var = scope_prefix + self.z_var

src/report.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,22 @@ def addVariables(self, *args):
2222
"""
2323
self.variables.extend(args)
2424

25-
def getPhrasedml(self)->str:
25+
def getPhraSEDML(self)->str:
2626
return "\n".join([f'report "{self.title}" {", ".join(self.variables)}'])
2727

2828
def __str__(self)->str:
29-
return self.getPhrasedml()
29+
return self.getPhraSEDML()
30+
31+
def scopeVariables(self, scope:str)->None:
32+
"""Use the specified scope for all variables
33+
34+
Args:
35+
scope (str): scope
36+
"""
37+
scope_indicator = "."
38+
scope_str = scope + scope_indicator
39+
for idx in range(len(self.variables)):
40+
# FIXME: Make scoping a utility
41+
if scope_indicator in self.variables[idx]:
42+
raise ValueError(f"Variable {self.variables[idx]} already has a scope. Cannot change scope.")
43+
self.variables[idx] = scope_str + self.variables[idx]

0 commit comments

Comments
 (0)