Skip to content

Commit adebb75

Browse files
committed
feat: added support for observables for parameters, compartments not set via assignment rules; separated task preprocessing; added test for negative initial times
1 parent 53f26ed commit adebb75

File tree

4 files changed

+1199
-96
lines changed

4 files changed

+1199
-96
lines changed

biosimulators.json

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,9 +298,17 @@
298298
"variables": "species concentrations",
299299
"targetPattern": "/sbml:sbml/sbml:model/sbml:listOfSpecies/sbml:species"
300300
},
301+
{
302+
"variables": "constant parameter values",
303+
"targetPattern": "/sbml:sbml/sbml:model/sbml:listOfParameters/sbml:parameter[@constant!='false']"
304+
},
301305
{
302306
"variables": "reaction fluxes",
303307
"targetPattern": "/sbml:sbml/sbml:model/sbml:listOfReactions/sbml:reaction"
308+
},
309+
{
310+
"variables": "constant compartment sizes",
311+
"targetPattern": "/sbml:sbml/sbml:model/sbml:listOfCompartments/sbml:compartment[@constant!='false']"
304312
}
305313
],
306314
"availableSoftwareInterfaceTypes": [
@@ -464,9 +472,17 @@
464472
"variables": "species concentrations",
465473
"targetPattern": "/sbml:sbml/sbml:model/sbml:listOfSpecies/sbml:species"
466474
},
475+
{
476+
"variables": "constant parameter values",
477+
"targetPattern": "/sbml:sbml/sbml:model/sbml:listOfParameters/sbml:parameter[@constant!='false']"
478+
},
467479
{
468480
"variables": "reaction fluxes",
469481
"targetPattern": "/sbml:sbml/sbml:model/sbml:listOfReactions/sbml:reaction"
482+
},
483+
{
484+
"variables": "constant compartment sizes",
485+
"targetPattern": "/sbml:sbml/sbml:model/sbml:listOfCompartments/sbml:compartment[@constant!='false']"
470486
}
471487
],
472488
"availableSoftwareInterfaceTypes": [
@@ -476,6 +492,13 @@
476492
"BioSimulators Docker image"
477493
],
478494
"dependencies": [
495+
{
496+
"name": "Assimulo",
497+
"version": null,
498+
"required": true,
499+
"freeNonCommercialLicense": true,
500+
"url": "https://jmodelica.org/assimulo/"
501+
},
479502
{
480503
"name": "SUNDIALS",
481504
"version": null,

biosimulators_pysces/core.py

Lines changed: 121 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
from biosimulators_utils.log.data_model import CombineArchiveLog, TaskLog, StandardOutputErrorCapturerLevel # noqa: F401
1313
from biosimulators_utils.viz.data_model import VizFormat # noqa: F401
1414
from biosimulators_utils.report.data_model import ReportFormat, VariableResults, SedDocumentResults # noqa: F401
15-
from biosimulators_utils.sedml.data_model import (Task, ModelLanguage, UniformTimeCourseSimulation, # noqa: F401
15+
from biosimulators_utils.sedml.data_model import (Task, ModelLanguage, ModelAttributeChange, UniformTimeCourseSimulation, # noqa: F401
1616
Variable, Symbol)
1717
from biosimulators_utils.utils.core import validate_str_value, parse_value
1818
from biosimulators_utils.sedml import validation
@@ -126,43 +126,115 @@ def exec_sed_task(task, variables, preprocessed_task=None, log=None, config=None
126126
if preprocessed_task is None:
127127
preprocessed_task = preprocess_sed_task(task, variables, config=config)
128128

129-
model = task.model
129+
# get the model configured with simulation algorithm and its parameters
130+
model = preprocessed_task['model']['model']
131+
132+
# modify model
133+
raise_errors_warnings(validation.validate_model_change_types(task.model.changes, (ModelAttributeChange, )),
134+
error_summary='Changes for model `{}` are not supported.'.format(task.model.id))
135+
model_change_target_sbml_id_map = preprocessed_task['model']['change_target_sbml_id_map']
136+
for change in task.model.changes:
137+
sbml_id = model_change_target_sbml_id_map[change.target]
138+
new_value = float(change.new_value)
139+
setattr(model, sbml_id + '_init', new_value)
140+
141+
# setup time course
130142
sim = task.simulation
143+
model.sim_start = sim.initial_time
144+
model.sim_end = sim.output_end_time
145+
model.sim_points = (
146+
sim.number_of_points
147+
* (sim.output_end_time - sim.initial_time)
148+
/ (sim.output_end_time - sim.output_start_time)
149+
+ 1
150+
)
151+
if model.sim_points != int(model.sim_points):
152+
raise NotImplementedError('Time course must specify an integer number of time points')
153+
154+
# execute simulation
155+
model.Simulate()
156+
157+
# extract results
158+
results = model.data_sim.getAllSimData(lbls=False)
159+
variable_results = VariableResults()
160+
variable_results_model_attr_map = preprocessed_task['model']['variable_results_model_attr_map']
161+
for variable in variables:
162+
i_results, model_attr_name = variable_results_model_attr_map[(variable.target, variable.symbol)]
163+
if i_results is not None:
164+
variable_results[variable.id] = results[:, i_results][-(sim.number_of_points + 1):]
165+
else:
166+
variable_results[variable.id] = numpy.full((sim.number_of_points + 1,), getattr(model, model_attr_name))
167+
168+
# log action
169+
if config.LOG:
170+
log.algorithm = preprocessed_task['simulation']['algorithm_kisao_id']
171+
172+
arguments = {}
173+
for key, val in model.__settings__.items():
174+
if model.mode_integrator == 'CVODE':
175+
if key.startswith('cvode_'):
176+
arguments[key] = val
177+
else:
178+
if key.startswith('lsoda_'):
179+
arguments[key] = val
180+
181+
log.simulator_details = {
182+
'method': model.Simulate.__module__ + '.' + model.Simulate.__name__,
183+
'arguments': arguments,
184+
}
185+
186+
# return results and log
187+
return variable_results, log
188+
189+
190+
def preprocess_sed_task(task, variables, config=None):
191+
""" Preprocess a SED task, including its possible model changes and variables. This is useful for avoiding
192+
repeatedly initializing tasks on repeated calls of :obj:`exec_sed_task`.
193+
194+
Args:
195+
task (:obj:`Task`): task
196+
variables (:obj:`list` of :obj:`Variable`): variables that should be recorded
197+
config (:obj:`Config`, optional): BioSimulators common configuration
198+
199+
Returns:
200+
:obj:`object`: preprocessed information about the task
201+
"""
202+
config = config or get_config()
131203

204+
# validate simulation
132205
if config.VALIDATE_SEDML:
133206
raise_errors_warnings(validation.validate_task(task),
134207
error_summary='Task `{}` is invalid.'.format(task.id))
135208
raise_errors_warnings(validation.validate_model_language(task.model.language, ModelLanguage.SBML),
136-
error_summary='Language for model `{}` is not supported.'.format(model.id))
137-
raise_errors_warnings(validation.validate_model_change_types(task.model.changes, ()),
138-
error_summary='Changes for model `{}` are not supported.'.format(model.id))
209+
error_summary='Language for model `{}` is not supported.'.format(task.model.id))
210+
raise_errors_warnings(validation.validate_model_change_types(task.model.changes, (ModelAttributeChange, )),
211+
error_summary='Changes for model `{}` are not supported.'.format(task.model.id))
139212
raise_errors_warnings(*validation.validate_model_changes(task.model),
140-
error_summary='Changes for model `{}` are invalid.'.format(model.id))
213+
error_summary='Changes for model `{}` are invalid.'.format(task.model.id))
141214
raise_errors_warnings(validation.validate_simulation_type(task.simulation, (UniformTimeCourseSimulation, )),
142-
error_summary='{} `{}` is not supported.'.format(sim.__class__.__name__, sim.id))
215+
error_summary='{} `{}` is not supported.'.format(task.simulation.__class__.__name__, task.simulation.id))
143216
raise_errors_warnings(*validation.validate_simulation(task.simulation),
144-
error_summary='Simulation `{}` is invalid.'.format(sim.id))
217+
error_summary='Simulation `{}` is invalid.'.format(task.simulation.id))
145218
raise_errors_warnings(*validation.validate_data_generator_variables(variables),
146219
error_summary='Data generator variables for task `{}` are invalid.'.format(task.id))
147220

148221
model_etree = lxml.etree.parse(task.model.source)
149-
target_x_paths_to_sbml_ids = validation.validate_target_xpaths(variables, model_etree, attr='id')
150-
151-
# Get the current working directory because PySCeS opaquely changes it
152-
cwd = os.getcwd()
222+
change_target_sbml_id_map = validation.validate_target_xpaths(task.model.changes, model_etree, attr='id')
223+
variable_target_sbml_id_map = validation.validate_target_xpaths(variables, model_etree, attr='id')
153224

154225
# Read the model
155-
interfaces = pysces.PyscesInterfaces.Core2interfaces()
156-
fid, model_filename = tempfile.mkstemp(suffix='.psc')
157-
os.close(fid)
226+
sbml_model_filename = task.model.source
227+
pysces_interface = pysces.PyscesInterfaces.Core2interfaces()
228+
pysces_model_file, pysces_model_filename = tempfile.mkstemp(suffix='.psc')
229+
os.close(pysces_model_file)
158230
try:
159-
interfaces.convertSBML2PSC(sbmlfile=task.model.source, pscfile=model_filename)
231+
pysces_interface.convertSBML2PSC(sbmlfile=sbml_model_filename, pscfile=pysces_model_filename)
160232
except Exception as exception:
161-
os.remove(model_filename)
233+
os.remove(pysces_model_filename)
162234
raise ValueError('Model at {} could not be imported:\n {}'.format(
163235
task.model.source, str(exception).replace('\n', '\n ')))
164-
model = pysces.model(model_filename)
165-
os.remove(model_filename)
236+
model = pysces.model(pysces_model_filename)
237+
os.remove(pysces_model_filename)
166238

167239
# Load the algorithm specified by `simulation.algorithm.kisao_id`
168240
sim = task.simulation
@@ -233,99 +305,59 @@ def exec_sed_task(task, variables, preprocessed_task=None, log=None, config=None
233305
if model.mode_integrator == 'CVODE':
234306
model.__settings__['cvode_return_event_timepoints'] = False
235307

236-
# setup time course
237-
model.sim_start = sim.initial_time
238-
model.sim_end = sim.output_end_time
239-
model.sim_points = (
240-
sim.number_of_points
241-
* (sim.output_end_time - sim.initial_time)
242-
/ (sim.output_end_time - sim.output_start_time)
243-
+ 1
244-
)
245-
if model.sim_points != int(model.sim_points):
246-
raise NotImplementedError('Time course must specify an integer number of time points')
308+
# validate and preprocess variables
309+
dynamic_ids = ['Time'] + list(model.species) + list(model.reactions)
310+
fixed_ids = (set(model.parameters) | set(model.__compartments__.keys())).difference(set(model.__rules__.keys()))
247311

248-
# execute simulation
249-
model.Simulate()
312+
variable_results_model_attr_map = {}
250313

251-
# extract results
252-
variable_results = VariableResults()
253314
unpredicted_symbols = []
254315
unpredicted_targets = []
255-
results, labels = model.data_sim.getAllSimData(lbls=True)
256-
labels = {label: i_label for i_label, label in enumerate(labels)}
257316

258317
for variable in variables:
259318
if variable.symbol:
260-
if variable.symbol == Symbol.time:
261-
i_result = labels['Time']
262-
variable_results[variable.id] = results[:, i_result][-(sim.number_of_points + 1):]
319+
if variable.symbol == Symbol.time.value:
320+
variable_results_model_attr_map[(variable.target, variable.symbol)] = (0, None)
263321
else:
264322
unpredicted_symbols.append(variable.symbol)
265323

266324
else:
267-
sbml_id = target_x_paths_to_sbml_ids[variable.target]
268-
i_result = labels.get(sbml_id, None)
269-
if i_result is not None:
270-
variable_results[variable.id] = results[:, i_result][-(sim.number_of_points + 1):]
271-
elif sbml_id in model.fixed_species:
272-
variable_results[variable.id] = numpy.full((sim.number_of_points + 1,), getattr(model, sbml_id))
273-
else:
274-
unpredicted_targets.append(variable.target)
325+
sbml_id = variable_target_sbml_id_map[variable.target]
326+
try:
327+
i_dynamic = dynamic_ids.index(sbml_id)
328+
variable_results_model_attr_map[(variable.target, variable.symbol)] = (i_dynamic, None)
329+
except ValueError:
330+
if sbml_id in fixed_ids:
331+
variable_results_model_attr_map[(variable.target, variable.symbol)] = (None, sbml_id)
332+
else:
333+
unpredicted_targets.append(variable.target)
275334

276335
if unpredicted_symbols:
277336
raise NotImplementedError("".join([
278337
"The following variable symbols are not supported:\n - {}\n\n".format(
279338
'\n - '.join(sorted(unpredicted_symbols)),
280339
),
281-
"Symbols must be one of the following:\n - {}".format(Symbol.time),
340+
"Symbols must be one of the following:\n - {}".format(Symbol.time.value),
282341
]))
283342

284343
if unpredicted_targets:
285344
raise ValueError(''.join([
286-
'The following variable targets could not be recorded:\n - {}\n\n'.format(
345+
'The following variable targets cannot not be recorded:\n - {}\n\n'.format(
287346
'\n - '.join(sorted(unpredicted_targets)),
288347
),
289-
'Targets must have one of the following ids:\n - {}'.format(
290-
'\n - '.join(sorted(set(labels.keys()).difference(set(['Time'])))),
348+
'Targets must have one of the following SBML ids:\n - {}'.format(
349+
'\n - '.join(sorted(dynamic_ids + list(fixed_ids))),
291350
),
292351
]))
293352

294-
# restore working directory
295-
os.chdir(cwd)
296-
297-
# log action
298-
if config.LOG:
299-
log.algorithm = 'KISAO_0000019' if model.mode_integrator == 'CVODE' else 'KISAO_0000088'
300-
301-
arguments = {}
302-
for key, val in model.__settings__.items():
303-
if model.mode_integrator == 'CVODE':
304-
if key.startswith('cvode_'):
305-
arguments[key] = val
306-
else:
307-
if key.startswith('lsoda_'):
308-
arguments[key] = val
309-
310-
log.simulator_details = {
311-
'method': model.Simulate.__module__ + '.' + model.Simulate.__name__,
312-
'arguments': arguments,
313-
}
314-
315-
# return results and log
316-
return variable_results, log
317-
318-
319-
def preprocess_sed_task(task, variables, config=None):
320-
""" Preprocess a SED task, including its possible model changes and variables. This is useful for avoiding
321-
repeatedly initializing tasks on repeated calls of :obj:`exec_sed_task`.
322-
323-
Args:
324-
task (:obj:`Task`): task
325-
variables (:obj:`list` of :obj:`Variable`): variables that should be recorded
326-
config (:obj:`Config`, optional): BioSimulators common configuration
327-
328-
Returns:
329-
:obj:`object`: preprocessed information about the task
330-
"""
331-
pass
353+
# return preprocessed information
354+
return {
355+
'model': {
356+
'model': model,
357+
'change_target_sbml_id_map': change_target_sbml_id_map,
358+
'variable_results_model_attr_map': variable_results_model_attr_map,
359+
},
360+
'simulation': {
361+
'algorithm_kisao_id': 'KISAO_0000019' if model.mode_integrator == 'CVODE' else 'KISAO_0000088',
362+
},
363+
}

0 commit comments

Comments
 (0)