SimpleSEDML 0.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
__init__.py ADDED
@@ -0,0 +1 @@
1
+ __version__ = "0.0.1"
constants.py ADDED
@@ -0,0 +1,4 @@
1
+ import os
2
+
3
+ PROJECT_DIR = os.path.dirname(os.path.dirname(__file__))
4
+ TEST_DIR = os.path.join(PROJECT_DIR, "tests")
model.py ADDED
@@ -0,0 +1,188 @@
1
+ '''Class that handles model definitions and their parameters.'''
2
+
3
+ import codecs
4
+ import urllib3
5
+ import os
6
+ import tellurium as te # type: ignore
7
+ from typing import Optional, List
8
+ import warnings
9
+
10
+ SBML_STR = "sbml_str"
11
+ ANT_STR = "ant_str"
12
+ SBML_FILE = "sbml_file"
13
+ ANT_FILE = "ant_file"
14
+ SBML_URL = "sbml_url"
15
+ MODEL_ID = "model_id"
16
+ MODEL_REF_TYPES = [SBML_STR, ANT_STR, SBML_FILE, ANT_FILE, SBML_URL, MODEL_ID]
17
+
18
+ """
19
+ Issues
20
+ 1. To eliminate the warning for urllib3, need to create a virtual environment with a higher version of python.
21
+ """
22
+
23
+ class Model:
24
+ def __init__(self, id:str, model_ref:Optional[str]=None, ref_type:Optional[str]=None,
25
+ model_source:Optional[str]=None,
26
+ is_overwrite:bool=False,
27
+ **kwargs):
28
+ """Provide information about the model and a model identifier.
29
+
30
+ Args:
31
+ id (str): identifier for the model
32
+ model_ref (str): reference to the file; reference type is specified separately
33
+ ref_type (str):
34
+ - "sbml_str": string representation of the SBML model
35
+ - "ant_str": string representation of the SBML model
36
+ - "sbml_file": file path to the SBML model
37
+ - "ant_file": file path to the Antimony model
38
+ - "sbml_url": URL to the SBML model
39
+ - "model_id": ID of a previously defined model
40
+ is_overwrite (bool): if True, overwrite the model if it already exists
41
+ model_source (str): source for the SBML model. If None, the source is a file with the same name as the model ID
42
+ in the current directory.
43
+ """
44
+ # Handle defaults
45
+ if model_ref is None:
46
+ # id is a file path to an SBML model
47
+ model_ref = id
48
+ _, filename = os.path.split(id)
49
+ splits = filename.split(".")
50
+ id = splits[0]
51
+ ref_type = SBML_FILE
52
+ elif ref_type is None:
53
+ ref_type = SBML_STR
54
+ # id, model_ref, ref_type should all be assigned
55
+ self.id = id
56
+ self.model_ref = model_ref
57
+ self.ref_type = ref_type
58
+ self.param_change_dct = kwargs
59
+ self.is_overwrite = is_overwrite
60
+ #
61
+ self.sbml_str = self._getSBMLFromReference()
62
+ self.model_source_path = self._makeModelSource(model_source)
63
+
64
+ def _makeModelSource(self, source:Optional[str])->str:
65
+ """Saves the model to a file. The file name is the model ID.
66
+ """
67
+ if self.ref_type == MODEL_ID:
68
+ # model_ref is the ID of a previously defined model
69
+ return self.model_ref
70
+ # FIXME: Model source should be the filename in the path
71
+ if source is None:
72
+ # Use the current directory
73
+ source = os.getcwd()
74
+ source = os.path.join(source, self.id)
75
+ source = str(source)
76
+ if self.is_overwrite or not os.path.exists(source):
77
+ with open(source, "wb") as f:
78
+ f.write(self.sbml_str.encode('utf-8'))
79
+ f.flush()
80
+ print(f"**Model saved to {source}")
81
+ if (not self.is_overwrite and os.path.exists(source)):
82
+ msg = "*** File {model_source_path} already exists and will be used as model source."
83
+ msg += "\n Use is_overwrite=True to overwrite."
84
+ warnings.warn(msg)
85
+ return source
86
+
87
+ def _getSBMLFromReference(self)->str:
88
+ """Extracts an SBML strong from the model reference
89
+
90
+ Args:
91
+ self.model_ref (str): reference to the file; reference type is specified separately
92
+ self.ref_type (str): One of self.MODEL_self.REF_TYPES
93
+
94
+ Returns:
95
+ SBML string
96
+ """
97
+ if self.ref_type in [SBML_FILE, ANT_FILE]:
98
+ with open(self.model_ref, "r") as f:
99
+ lines = f.read()
100
+ if self.ref_type == SBML_FILE:
101
+ sbml_str = lines
102
+ else:
103
+ sbml_str = te.antimonyToSBML(lines)
104
+ elif self.ref_type == SBML_STR:
105
+ sbml_str = self.model_ref
106
+ elif self.ref_type == ANT_STR:
107
+ sbml_str = te.antimonyToSBML(self.model_ref)
108
+ elif self.ref_type == MODEL_ID:
109
+ # self.model_ref is the ID of a previously defined model
110
+ sbml_str = ""
111
+ else:
112
+ # self.ref_type == SBML_URL
113
+ response = urllib3.request("GET", self.model_ref)
114
+ if response.status == 200:
115
+ sbml_str = codecs.decode(response.data, 'utf-8')
116
+ else:
117
+ raise ValueError(f"Failed to fetch SBML from URL: {self.model_ref}")
118
+ return sbml_str
119
+
120
+ def __str__(self):
121
+ params = ", ".join(f"{param} = {val}" for param, val in self.param_change_dct.items())
122
+ if len(params) > 0:
123
+ params = f" with {params}"
124
+ if self.ref_type == MODEL_ID:
125
+ source = self.id
126
+ else:
127
+ source = f'"{self.model_source_path}"'
128
+ return f'{self.id} = model {source} {params}'
129
+
130
+ @staticmethod
131
+ def findReferenceType(model_ref:str, model_ids:List[str], ref_type:Optional[str]=None)->str:
132
+ """Infers the reference type from the model reference.
133
+
134
+ Args:
135
+ model_ref (str): reference to the file; reference type is specified separately
136
+ model_ids (List[str]): List of known model IDs
137
+ refer_type (str): One of self.MODEL_REF_TYPES
138
+
139
+ Returns:
140
+ str: reference type
141
+ """
142
+ # Use the ref_type if it is specified
143
+ if ref_type is not None:
144
+ return ref_type
145
+ # Check if this is a model ID
146
+ if model_ref in model_ids:
147
+ try:
148
+ is_file = os.path.exists(model_ref)
149
+ except:
150
+ is_file = False
151
+ if is_file:
152
+ warnings.warn(f"Model ID {model_ref} is also a file path. Using model ID.")
153
+ return MODEL_ID
154
+ # Check for Antimony string
155
+ try:
156
+ _ = te.loada(model_ref)
157
+ return ANT_STR
158
+ except:
159
+ pass
160
+ # Check for SBML string
161
+ try:
162
+ _ = te.loadSBMLModel(model_ref)
163
+ if "sbml" in model_ref:
164
+ return SBML_STR
165
+ except:
166
+ pass
167
+ # Check if this is a URL
168
+ if ("http://" in model_ref) or ("https://" in model_ref):
169
+ return SBML_URL
170
+ # Check if this is a file path
171
+ try:
172
+ is_file = os.path.exists(model_ref)
173
+ except:
174
+ is_file = False
175
+ if is_file:
176
+ try:
177
+ with open(model_ref, "r") as f:
178
+ lines = f.read()
179
+ if lines.startswith("<"):
180
+ return SBML_FILE
181
+ else:
182
+ return ANT_FILE
183
+ except:
184
+ pass
185
+ # Report error
186
+ msg = f"Unidentifiable model reference: {model_ref}. "
187
+ msg += f"\nFix the reference and/or specify the reference type.\nMust be one of {MODEL_REF_TYPES}."
188
+ raise ValueError(msg)
plot.py ADDED
@@ -0,0 +1,41 @@
1
+ from typing import Optional, List, Union
2
+
3
+ class Plot:
4
+ 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
+ """
7
+ 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)
11
+
12
+ Args:
13
+ x_var (str): x variable
14
+ y_var (str): y variable
15
+ z_var (str, optional): z variable. Defaults to None.
16
+ title (str, optional): title of the plot. Defaults to None.
17
+ """
18
+ self.x_var = x_var
19
+ self.y_var = y_var
20
+ self.z_var = z_var
21
+ self.title = title
22
+ self.is_plot = is_plot
23
+
24
+ # FIXME: Support the above use cases
25
+ def __str__(self)->str:
26
+ if not self.is_plot:
27
+ return ""
28
+ if self.z_var is None:
29
+ if isinstance(self.y_var, str):
30
+ y_vars = [self.y_var]
31
+ else:
32
+ y_vars = self.y_var
33
+ var_clause = f'{self.x_var} vs {", ".join(y_vars)}'
34
+ else:
35
+ if not isinstance(self.y_var, str):
36
+ raise ValueError("y_var must be a string when z_var is provided")
37
+ var_clause = f"{self.x_var} vs {self.y_var} vs {self.z_var}"
38
+ if self.title is None:
39
+ return f"plot {var_clause}"
40
+ else:
41
+ return f"plot \"{self.title}\" {var_clause}"
report.py ADDED
@@ -0,0 +1,26 @@
1
+ from typing import Optional
2
+
3
+ class Report:
4
+ def __init__(self, metadata:Optional[dict]=None, title:str=""):
5
+ """Reports data after a simulation.
6
+
7
+ Args:
8
+ metadata (Optional[dict], optional): A dictionary of values saved in the
9
+ 'attrs' attribute of the DataFrame generated.
10
+ title (str, optional): Saved in the SEDML
11
+ """
12
+ self.metadata = metadata
13
+ self.title = title
14
+ self.variables:list = []
15
+
16
+ def addVariables(self, *args):
17
+ """
18
+ List of data to report
19
+
20
+ Args:
21
+ *args: list of report variables
22
+ """
23
+ self.variables.extend(args)
24
+
25
+ def __str__(self)->str:
26
+ return "\n".join([f'report "{self.title}" {", ".join(self.variables)}'])
simple_sedml.py ADDED
@@ -0,0 +1,390 @@
1
+ from src.model import Model, ANT_STR
2
+ from src.simulation import Simulation
3
+ from src.task import Task, RepeatedTask
4
+ from plot import Plot
5
+ from src.report import Report
6
+
7
+ from collections import namedtuple
8
+ import pandas as pd # type: ignore
9
+ import phrasedml # type: ignore
10
+ import tellurium as te # type: ignore
11
+ import warnings
12
+ from typing import Optional, List, Tuple, Union
13
+
14
+ REPORT = "report"
15
+ MODEL = "model"
16
+ SIMULATION = "simulation"
17
+ TASK = "task"
18
+ REPEATED_TASK = "repeated_task"
19
+ PLOT2D = "plot2d"
20
+ TIME_COURSE = "time_course"
21
+ # Simulation parameters
22
+ ST_UNIFORM = "uniform"
23
+ ST_STEADY_STATE = "steady_state"
24
+ ST_UNIFORM_STOCHASTIC = "uniform_stochastic"
25
+ # Default values
26
+ D_START = 0
27
+ D_END = 5
28
+ D_NUM_STEP = 10
29
+ D_NUM_POINT = D_NUM_STEP + 1
30
+ D_REF_TYPE = ANT_STR
31
+ D_SIM_TYPE = ST_UNIFORM
32
+ D_SIM_UNIFORM_ALGORITHM = "CVODE"
33
+ D_SIM_UNIFORM_STOCHASTIC_ALGORITHM = "gillespie"
34
+
35
+ ModelInfo = namedtuple("ModelInfo", ["model_id", "parameters", "floating_species"])
36
+
37
+ """
38
+ PhraSED-ML is strctured as a series of sections, each of which specifies a Model, Simulation, Task or repeated task.
39
+
40
+ A model section contains one or more references to models. Some of these may be indirect in that they reference a reference.
41
+ A model section may contain changes to parameters, initial conditions or other model components.
42
+
43
+
44
+ Restrictions:
45
+ - No local variables (because this is an API)
46
+ - No formulas (because this is an API and python can do this)
47
+ """
48
+
49
+
50
+ class SimpleSEDML(object):
51
+ """A directive can consist of many sections each of which species a Model, Simulation, Task or repeated task,
52
+ and an action (plot or report).
53
+
54
+ Args:
55
+ object (_type_): _description_
56
+ """
57
+ def __init__(self)->None:
58
+ # Dictionary of script elements, indexed by their section ID
59
+ self.model_dct:dict = {}
60
+ self.simulation_dct:dict = {}
61
+ self.task_dct:dict = {}
62
+ self.repeated_task_dct:dict = {}
63
+ self.report_dct:dict = {}
64
+ self.plot_dct:dict = {}
65
+ #
66
+ self.report_id = 0
67
+ self.plot_id = 0
68
+ self.time_course_id = 0
69
+
70
+ def __str__(self)->str:
71
+ """Creates phrasedml string from composition of sections
72
+
73
+ Returns:
74
+ str: SED-ML string
75
+ """
76
+ sections = [
77
+ *[str(m) for m in self.model_dct.values()],
78
+ *[str(s) for s in self.simulation_dct.values()],
79
+ *[str(t) for t in self.task_dct.values()],
80
+ *[str(rt) for rt in self.repeated_task_dct.values()],
81
+ *[str(r) for r in self.report_dct.values()],
82
+ *[str(p) for p in self.plot_dct.values()],
83
+ ]
84
+ return "\n".join(sections)
85
+
86
+ def antimonyToSBML(antimony_str)->str:
87
+ """Converts an Antimony string to SBML
88
+
89
+ Args:
90
+ antimony_str: Antimony string
91
+
92
+ Returns:
93
+ str: SBML string
94
+ """
95
+ return te.antimonyToSBML(antimony_str)
96
+
97
+ def getSEDML(self)->str:
98
+ """Converts the script to a SED-ML string
99
+
100
+ Returns:
101
+ str: SED-ML string
102
+ Raises:
103
+ ValueError: if the conversion failsk
104
+ """
105
+ sedml_str = phrasedml.convertString(str(self))
106
+ if sedml_str is None:
107
+ raise ValueError(phrasedml.getLastError())
108
+ return sedml_str
109
+
110
+ def _checkDuplicate(self, id:str, dict_type:str):
111
+ """Checks if the ID already exists in the dictionary
112
+
113
+ Args:
114
+ id: ID of the model
115
+ dict_type: type of the dictionary (model, simulation, task, repeated_task)
116
+
117
+ Raises:
118
+ ValueError: if the ID already exists in the dictionary
119
+ """
120
+ TYPE_DCT = {
121
+ MODEL: self.model_dct,
122
+ SIMULATION: self.simulation_dct,
123
+ TASK: self.task_dct,
124
+ REPEATED_TASK: self.repeated_task_dct,
125
+ REPORT: self.report_dct,
126
+ PLOT2D: self.plot_dct,
127
+ }
128
+ if id in TYPE_DCT[dict_type]:
129
+ raise ValueError(f"Duplicate {dict_type} ID: {id}")
130
+
131
+ def addModel(self, id:str, model_ref:str, ref_type:Optional[str]=None,
132
+ model_source_path:Optional[str]=None, is_overwrite:bool=False, **parameters):
133
+ """Adds a model to the script
134
+
135
+ Args:
136
+ id: ID of the model
137
+ model_ref: reference to the model
138
+ ref_type: type of the reference (e.g. "sbml_str", "ant_str", "sbml_file", "ant_file", "sbml_url")
139
+ model_source_path: path to the model source file
140
+ is_overwrite: if True, overwrite the model if it already exists
141
+ """
142
+ self._checkDuplicate(id, MODEL)
143
+ model_ids = list(self.model_dct.keys())
144
+ ref_type = Model.findReferenceType(model_ref, model_ids, ref_type=ref_type)
145
+ model = Model(id, model_ref, ref_type=ref_type,
146
+ model_source=model_source_path, is_overwrite=is_overwrite, **parameters)
147
+ self.model_dct[id] = model
148
+
149
+ def addSimulation(self,
150
+ id:str,
151
+ simulation_type:str=ST_UNIFORM,
152
+ start:float=D_START,
153
+ end:float=D_END,
154
+ num_step:Optional[int]=None,
155
+ num_point:Optional[int]=None,
156
+ algorithm:Optional[str]=None):
157
+ """Adds a simulation to the script
158
+
159
+ Args:
160
+ id (str): Simulation identifier
161
+ start (float): start time for simulation
162
+ end (float): end time for simulation
163
+ """
164
+ if (num_step is None) and (num_point is None):
165
+ num_step = D_NUM_STEP
166
+ elif (num_step is None) and (num_point is not None):
167
+ num_step = num_point - 1
168
+ if algorithm is None:
169
+ if simulation_type == ST_UNIFORM:
170
+ algorithm = D_SIM_UNIFORM_ALGORITHM
171
+ elif simulation_type == ST_UNIFORM_STOCHASTIC:
172
+ algorithm = D_SIM_UNIFORM_STOCHASTIC_ALGORITHM
173
+ self._checkDuplicate(id, SIMULATION)
174
+ self.simulation_dct[id] = Simulation(id, simulation_type, start, end,
175
+ num_step, algorithm=algorithm) # type: ignore
176
+
177
+ def addTask(self, id, model_id:str, simulation_id:str):
178
+ """Adds a task to the script
179
+
180
+ Args:
181
+ id: ID of the task
182
+ model: Model object
183
+ simulation: Simulation object
184
+ """
185
+ self._checkDuplicate(id, TASK)
186
+ task = Task(id, model_id, simulation_id)
187
+ self.task_dct[id] = task
188
+
189
+ def addRepeatedTask(self, id:str, subtask_id:str, parameter_df:pd.DataFrame, reset:bool=True):
190
+ """Adds a repeated task to the script
191
+
192
+ Args:
193
+ repeated_task: RepeatedTask object
194
+ """
195
+ self._checkDuplicate(id, REPEATED_TASK)
196
+ task = RepeatedTask(id, subtask_id, parameter_df, reset=reset)
197
+ self.repeated_task_dct[id] = task
198
+
199
+ def addPlot(self, x_var:str, y_var:Union[str, List[str]], z_var:Optional[str]=None, title:Optional[str]=None,
200
+ id:Optional[str]=None,
201
+ is_plot:bool=True)->None:
202
+ """
203
+ Plot class to represent a plot in the script.
204
+ Args:
205
+ x_var (str): x variable
206
+ y_var (str): y variable or list of y variables
207
+ z_var (str, optional): z variable. Defaults to None.
208
+ title (str, optional): title of the plot. Defaults to None.
209
+ id (str, optional): ID of the plot. Defaults to None.
210
+ is_plot (bool, optional): if True, plot the data. Defaults to True.
211
+ """
212
+ if id is None:
213
+ id = str(self.plot_id)
214
+ self.plot_id += 1
215
+ plot = Plot(x_var, y_var, z_var=z_var, title=title, is_plot=is_plot)
216
+ self.plot_dct[id] = plot
217
+
218
+ def addReport(self, *report_variables, id:Optional[str]=None,
219
+ metadata:Optional[dict]=None, title:str=""):
220
+ """Adds data to the report
221
+
222
+ Args:
223
+ id: ID of the report
224
+ report_variable: variable to be added to the report
225
+ metadata: metadata for the report variable
226
+ title: title for the report variable
227
+ """
228
+ if id is None:
229
+ id = str(self.report_id)
230
+ self.report_id += 1
231
+ if not id in self.report_dct.keys():
232
+ if len(title) == 0:
233
+ title = f"Report {id}"
234
+ self.report_dct[id] = Report(metadata=metadata, title=title)
235
+ if metadata is not None:
236
+ self.report_dct[id].metadata = metadata
237
+ if title is not None:
238
+ self.report_dct[id].title = title
239
+ self.report_dct[id].addVariables(*report_variables)
240
+
241
+ def _getModelInfo(self, model_id)->ModelInfo:
242
+ """Returns information about model ID, parameters, floating species and fixed species.
243
+
244
+ Args:
245
+ model_id (str):
246
+
247
+ Returns: ModelInfo
248
+ """
249
+ if model_id not in self.model_dct:
250
+ raise ValueError(f"Model ID {model_id} not found.")
251
+ model = self.model_dct[model_id]
252
+ rr = te.loadSBMLModel(model.sbml_str)
253
+ model_info = ModelInfo(
254
+ model_id=model.id,
255
+ parameters=rr.getGlobalParameterIds(),
256
+ floating_species=rr.getFloatingSpeciesIds(),
257
+ )
258
+ return model_info
259
+
260
+ def execute(self)->pd.DataFrame:
261
+ """Executes the script and returns the results as a DataFrame
262
+
263
+ Returns:
264
+ pd.DataFrame: DataFrame with the results
265
+ """
266
+ if (len(self.repeated_task_dct) > 0):
267
+ is_repeated_task = True
268
+ else:
269
+ is_repeated_task = False
270
+ if len(self.task_dct) > 1:
271
+ is_more_than_one_task = True
272
+ else:
273
+ is_more_than_one_task = False
274
+ if len(self.report_dct) > 0:
275
+ if is_repeated_task:
276
+ warnings.warn("Reports only generate data for the last repeated task.")
277
+ if is_more_than_one_task:
278
+ warnings.warn("Reports only generate data for the last task.")
279
+ te.executeSEDML(self.getSEDML())
280
+ return te.getLastReport()
281
+
282
+ def getModelInfo(self, model_id:Optional[str]=None)->List[dict]:
283
+ """Returns a dictionary with the model information
284
+
285
+ Args:
286
+ model_id: ID of the model. If None, returns information for all models
287
+
288
+ Returns: List of dictionaries structured as follows:
289
+ "task_id": str
290
+ "parameters": list of parameters
291
+ "species": list of species
292
+ """
293
+ info_dcts:list = []
294
+ for model in self.model_dct.values():
295
+ if (model_id is not None) and (model.id != model_id):
296
+ continue
297
+ model_info = self._getModelInfo(model.id)
298
+ info_dct = dict(
299
+ model_id=model_info.model_id,
300
+ parameters=model_info.parameters,
301
+ floating_species=model_info.floating_species
302
+ )
303
+ info_dcts.append(info_dct)
304
+ return info_dcts
305
+
306
+ @classmethod
307
+ def makeTimeCourse(cls,
308
+ model_ref:str,
309
+ ref_type:Optional[str]=None,
310
+ plot_variables:Optional[str]=None,
311
+ start:float=0, end:float=5, num_step:int=50,
312
+ time_course_id:Optional[str]=None,
313
+ title:Optional[str]=None,
314
+ algorithm:Optional[str]=None,
315
+ **parameters)->str:
316
+ """Creates a time course simulation
317
+
318
+ Args:
319
+ model_ref: reference to the model
320
+ ref_type: type of the reference (e.g. "sbml_str", "ant_str", "sbml_file", "ant_file", "sbml_url")
321
+ plot_variables: variables to be plotted
322
+ start: start time
323
+ end: end time
324
+ num_step: number of steps
325
+ time_course_id: ID of the time course simulation
326
+ algorithm: algorithm to use for the simulation
327
+ title: title of the plot
328
+ parameters: parameters to be passed to the model
329
+
330
+ Returns:
331
+ str: SEDML
332
+ """
333
+ if time_course_id is None:
334
+ time_course_id = TIME_COURSE
335
+ model_id = f"{time_course_id}_model"
336
+ sim_id = f"{time_course_id}_sim"
337
+ task_id = f"{time_course_id}_task"
338
+ if title is None:
339
+ title = ""
340
+ #
341
+ simple = cls()
342
+ simple.addModel(model_id, model_ref, ref_type=ref_type, is_overwrite=True, **parameters)
343
+ if plot_variables is None:
344
+ variable_dct = simple.getModelInfo(model_id)[0]
345
+ plot_variables = variable_dct['floating_species']
346
+ plot_variables.insert(0, "time") # type: ignore
347
+ simple.addSimulation(sim_id, "uniform", start, end, num_step, algorithm=algorithm)
348
+ simple.addTask(task_id, model_id, sim_id)
349
+ x1_var = plot_variables[0]
350
+ y_vars = plot_variables[1:]
351
+ simple.addPlot(x1_var, y_vars, title=title)
352
+ return simple.getSEDML()
353
+
354
+ def validate(self):
355
+ """
356
+ Validates the script and returns a list of errors.
357
+ Checks for:
358
+ 1. At least one model is defined.
359
+ 2. At least one simulation is defined.
360
+ 3. At least one task is defined.
361
+ 4. At least one output is defined.
362
+ 5. All referenced task IDs are defined.
363
+ """
364
+ raise NotImplementedError("Validation is not implemented yet.")
365
+
366
+ @classmethod
367
+ def executeSEDML(cls, sedml_str:str)->Union[None, pd.DataFrame]:
368
+ """Executes the SED-ML string and returns the results as a DataFrame
369
+
370
+ Args:
371
+ sedml_str: SED-ML string
372
+
373
+ Returns:
374
+ pd.DataFrame: DataFrame with the results
375
+ """
376
+ try:
377
+ te.executeSEDML(sedml_str)
378
+ except Exception as e:
379
+ raise RuntimeError(f"SED-ML execution failed: {e}")
380
+ # Return a DataFrame if there is a report
381
+ num_report = sedml_str.count("<report id=")
382
+ if num_report > 1:
383
+ warnings.warn("Only generate data for the last report.")
384
+ if num_report >= 1:
385
+ df = te.getLastReport()
386
+ if df is None:
387
+ raise ValueError("No report found.")
388
+ return df
389
+ else:
390
+ return None
@@ -0,0 +1,92 @@
1
+ Metadata-Version: 2.4
2
+ Name: SimpleSEDML
3
+ Version: 0.0.1
4
+ Summary: Python Subnet Discovery for Systems Biology
5
+ Author-email: Joseph Hellerstein <joseph.hellerstein@gmail.com>, Herbert S Sauro <hsauro@uw.edu>
6
+ License: MIT License
7
+
8
+ Copyright (c) 2025 UW Sauro Lab
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ of this software and associated documentation files (the "Software"), to deal
12
+ in the Software without restriction, including without limitation the rights
13
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ copies of the Software, and to permit persons to whom the Software is
15
+ furnished to do so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in all
18
+ copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
+ SOFTWARE.
27
+
28
+ Project-URL: Homepage, https://github.com/sys-bio/SimpleSEDML
29
+ Requires-Python: >=3.6
30
+ Description-Content-Type: text/markdown
31
+ License-File: LICENSE
32
+ Requires-Dist: numpy
33
+ Requires-Dist: build
34
+ Requires-Dist: coverage
35
+ Requires-Dist: jupyterlab
36
+ Requires-Dist: matplotlib
37
+ Requires-Dist: nose2
38
+ Requires-Dist: pandas
39
+ Requires-Dist: phrasedml
40
+ Requires-Dist: pip
41
+ Requires-Dist: requests
42
+ Requires-Dist: tabulate
43
+ Requires-Dist: tellurium
44
+ Requires-Dist: twine
45
+ Requires-Dist: urllib3
46
+ Dynamic: license-file
47
+
48
+ [![Build](https://github.com/sys-bio/SimpleSEDML/actions/workflows/github-actions.yml/badge.svg)](https://github.com/sys-bio/SimpleSBML/actions/workflows/github-actions.yml)
49
+
50
+ # SimpleSEDML
51
+ A simple API for using the [Simulation Experiment Description Markup Language (SED-ML)](https://sed-ml.org/), a community standard for describing simulation experiments.
52
+
53
+ The project provides a python interface to generating SED-ML based on the abstractions provided by [phraSED-ML](https://pmc.ncbi.nlm.nih.gov/articles/PMC5313123/pdf/nihms846540.pdf) to describe simulation experiments. These absractions are: (a) models (including changes in values of model parameters);
54
+ (b) simulations (including deterministic, stochastic, and steady state);
55
+ (c) tasks (which specify simulations to run on tasks and repetitions for changes in parameter values);
56
+ and (d) output for data reports and plots.
57
+
58
+ ``SimpleSEDML`` generalizes the capabilities of ``PhraSEDML`` and simplifies its usage by exploiting the Python environment:
59
+
60
+ * A model source can be a file path or URL and may be in the Antimony language as well as SBML;
61
+ * Repeated tasks are defined more simply by the use of a ``pandas`` ``DataFrame``.
62
+ * Convenience methods are provided to simplify the API.
63
+
64
+ # Example
65
+
66
+ See this [Jupyter notebook](https://github.com/sys-bio/SimpleSEDML/blob/main/examples/usage_examples.ipynb) for a detailed example.
67
+
68
+ Consider the model below in the Antimony language.
69
+
70
+ mymodel = """
71
+ model myModel
72
+ J1: S1 -> S2; k1*S1;
73
+ k1 = 0.5;
74
+ end
75
+ """
76
+
77
+ We want to simulate this model and do a time course plot of all floating species in the model.
78
+
79
+ from simple_sedml import SimpleSEDML
80
+
81
+ sedml_str = SimpleSEDML.makeTimeCourse(mymodel)
82
+
83
+ We can print, save, or execute ``sedml_str``. To execute it,
84
+
85
+ SimpleSEDML.executeSEDML(sedml_str)
86
+ <img src="docs/images/phrasedml_example.png" style="width:300px;height:300px;">
87
+
88
+ # Restrictions
89
+ 1. If there are multiple task directives and/or there is a repeated task directive AND there is a report directive, SimpleSEDML.execute only returns the results of the last simulation. You can circumvent this by iterating in python to obtain the desired reports.
90
+
91
+ # Plans
92
+ 1. First implementation of ``SimpleSEDML`` with methods for ``addModel``, ``addSimulation``, ``addTask``, ``addReport``, ``execute``, and ``to_sedml``.
@@ -0,0 +1,13 @@
1
+ __init__.py,sha256=sXLh7g3KC4QCFxcZGBTpG2scR7hmmBsMjq6LqRptkRg,22
2
+ constants.py,sha256=Gko24Uw_l3rFDYmL9qEjOWZHyVul14dycoIV_BDUA-k,113
3
+ model.py,sha256=VYKRcIYTpdfV2KpHGScp3xmAKqPu_BvtWgq1aTF4n2c,7058
4
+ plot.py,sha256=uhVbfd7RDcjAH1_mIxIYYHXkPA8NZLxAAKnnpbDDCH0,1562
5
+ report.py,sha256=K4VNeQE5RfJncyvjgSbZw9qU1ww0vGM1sjt3ndGZho4,771
6
+ simple_sedml.py,sha256=YHaroiHa_vYG2MH7p6F7nIV_CD0JOtTdKaM1rcQox_o,14077
7
+ simulation.py,sha256=0_82AVeAPGzY9vOLysrl6khHwt-abV1ofcP4nZzUCQk,3654
8
+ task.py,sha256=XA5CLfrbbXuXumfbYcHCvecl0PtYxnYG38uOLPmc1lY,2064
9
+ simplesedml-0.0.1.dist-info/licenses/LICENSE,sha256=vyn7B-UTdbg7Tu5NPD5FChKtwroX0n_ILQwKp3RpXGY,1069
10
+ simplesedml-0.0.1.dist-info/METADATA,sha256=ZsCoH1XRog6Hu0dIeImskzx4_CcNYlZ-bRoc-iUv5os,4300
11
+ simplesedml-0.0.1.dist-info/WHEEL,sha256=0CuiUZ_p9E4cD6NyLD6UG80LBXYyiSYZOKDm5lp32xk,91
12
+ simplesedml-0.0.1.dist-info/top_level.txt,sha256=7Yk_B53o9eR6joDkOmHrrrMr8zgi25-LP7XMmOWwELg,66
13
+ simplesedml-0.0.1.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.3.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 UW Sauro Lab
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,8 @@
1
+ __init__
2
+ constants
3
+ model
4
+ plot
5
+ report
6
+ simple_sedml
7
+ simulation
8
+ task
simulation.py ADDED
@@ -0,0 +1,88 @@
1
+ '''Simulation directives for PhraSEDML'''
2
+
3
+ from typing import Optional
4
+
5
+ # Simulation types
6
+ ST_UNIFORM = "uniform"
7
+ ST_STOCHASTIC = "stochastic"
8
+ ST_ONESTEP = "onestep"
9
+
10
+
11
+ class Simulation:
12
+ def __init__(self, id:str, simulation_type:str,
13
+ start:float=0,
14
+ end:float=5,
15
+ num_step:int=50,
16
+ time_interval:float=0.5, # required for onestep
17
+ absolute_tolerance:Optional[float]=None,
18
+ algorithm:Optional[str]=None,
19
+ initial_time_step:Optional[float]=None,
20
+ maximum_adams_order:Optional[int]=None,
21
+ maximum_bdf_order:Optional[int]=None,
22
+ maximum_iterations:Optional[int]=None,
23
+ maximum_num_steps:Optional[int]=None,
24
+ maximum_time_step:Optional[float]=None,
25
+ minimum_damping:Optional[float]=None,
26
+ minimum_time_step:Optional[float]=None,
27
+ relative_tolerance:Optional[float]=None,
28
+ seed:Optional[int]=None,
29
+ variable_step_size:Optional[bool]=None):
30
+ """Simulation class for SED-ML
31
+ Args:
32
+ id (str): identifier for the simulation
33
+ simulation_type (str): type of simulation
34
+ - "uniform": uniform simulation
35
+ - "stochastic": stochastic simulation
36
+ - "onestep": one-step simulation
37
+ start (float): start time for the simulation
38
+ end (float): end time for the simulation
39
+ num_step (int): number of steps for the simulation
40
+ time_interval (float): time interval for the simulation
41
+ algorithm (str): algorithm to use for the simulation. Defaults are:
42
+ - "CVODE": CVODE algorithm
43
+ - "gillespie": Gillespie algorithm
44
+ """
45
+ self.id = id
46
+ self.simulation_type = simulation_type
47
+ self.start = start
48
+ self.end = end
49
+ self.num_step = num_step
50
+ self.time_interval = time_interval
51
+ if algorithm is None:
52
+ if simulation_type == ST_UNIFORM:
53
+ algorithm = "CVODE"
54
+ elif simulation_type == ST_STOCHASTIC:
55
+ algorithm = "gillespie"
56
+ self.algorithm = algorithm
57
+ # Setup the options
58
+ self.option_dct = dict(
59
+ absolute_tolerance=absolute_tolerance,
60
+ algorithm=algorithm,
61
+ initial_time_step=initial_time_step,
62
+ maximum_adams_order=maximum_adams_order,
63
+ maximum_bdf_order=maximum_bdf_order,
64
+ maximum_iterations=maximum_iterations,
65
+ maximum_num_steps=maximum_num_steps,
66
+ maximum_time_step=maximum_time_step,
67
+ minimum_damping=minimum_damping,
68
+ minimum_time_step=minimum_time_step,
69
+ relative_tolerance=relative_tolerance,
70
+ seed=seed,
71
+ variable_step_size=variable_step_size
72
+ )
73
+
74
+ def __str__(self)->str:
75
+ if self.simulation_type == ST_UNIFORM:
76
+ simulate_arg = "simulate uniform"
77
+ elif self.simulation_type == ST_STOCHASTIC:
78
+ simulate_arg = "simulate uniform_stochastic"
79
+ if self.simulation_type == ST_ONESTEP:
80
+ line = f'{self.id} = simulate onestep({self.time_interval})'
81
+ else:
82
+ line = f'{self.id} = {simulate_arg}({self.start}, {self.end}, {self.num_step})'
83
+ # Include the options
84
+ option_lines = [f"{self.id}.algorithm.{k} = {str(v)} " for k, v in self.option_dct.items()
85
+ if (v is not None) and (k != "algorithm")]
86
+ option_lines.append(f"{self.id}.algorithm = {self.algorithm}")
87
+ section = line + "\n" + "\n".join(option_lines)
88
+ return section
task.py ADDED
@@ -0,0 +1,57 @@
1
+ from src.model import Model
2
+ from src.simulation import Simulation
3
+
4
+ import pandas as pd; # type: ignore
5
+ from typing import List
6
+
7
+
8
+ class Task:
9
+ def __init__(self, id:str, model_id:str, simulation_id:str):
10
+ self.id = id
11
+ self.model_id = model_id
12
+ self.simulation_id = simulation_id
13
+
14
+ def __str__(self)->str:
15
+ result = f'{self.id} = run {self.simulation_id} on {self.model_id}'
16
+ return result
17
+
18
+
19
+ class RepeatedTask:
20
+ # A RepeatedTask executes a task with changes in the values of global parameters.
21
+ # Ex: repeat1 = repeat nested_task for S1 in [1, 3, 5], S2 in [0, 10, 3], reset=true
22
+ # Note that it is not necessary to specify functions as in the original phraSED-ML since python provides this.
23
+
24
+ def __init__(self, id:str, subtask_id:str, parameter_df:pd.DataFrame, reset:bool=True):
25
+ """Repeats a single task with changes in global parameters.
26
+
27
+ Args:
28
+ id (str): Identity of repeated task
29
+ subtask_id (Task): Task to be repeated
30
+ parameter_df (pd.DataFrame): DataFrame with the parameters to be changed. Column names must be global parameters.
31
+ reset (bool, optional): _description_. Defaults to True.
32
+ """
33
+ #
34
+ self.id = id
35
+ self.subtask_id = subtask_id
36
+ self.parameter_df = parameter_df
37
+ self.reset = reset
38
+
39
+ def _makeChangeValues(self)->str:
40
+ """Creates a string with the changes in the values of global parameters.
41
+
42
+ Args:
43
+ ser (pd.Series): Series with the changes in the values of global parameters.
44
+
45
+ Returns:
46
+ str: String with the changes in the values of global parameters.
47
+ """
48
+ results = []
49
+ for col in self.parameter_df.columns:
50
+ results.append(f'{col} in {str(list(self.parameter_df[col].values))}')
51
+ return ', '.join(results)
52
+
53
+ def __str__(self)->str:
54
+ line = f'{self.id} = repeat {self.subtask_id} for '
55
+ line += self._makeChangeValues()
56
+ line += f', reset={self.reset}'
57
+ return line