|
12 | 12 | from biosimulators_utils.log.data_model import CombineArchiveLog, TaskLog, StandardOutputErrorCapturerLevel # noqa: F401 |
13 | 13 | from biosimulators_utils.viz.data_model import VizFormat # noqa: F401 |
14 | 14 | 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 |
16 | 16 | Variable, Symbol) |
17 | 17 | from biosimulators_utils.utils.core import validate_str_value, parse_value |
18 | 18 | from biosimulators_utils.sedml import validation |
@@ -126,43 +126,115 @@ def exec_sed_task(task, variables, preprocessed_task=None, log=None, config=None |
126 | 126 | if preprocessed_task is None: |
127 | 127 | preprocessed_task = preprocess_sed_task(task, variables, config=config) |
128 | 128 |
|
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 |
130 | 142 | 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() |
131 | 203 |
|
| 204 | + # validate simulation |
132 | 205 | if config.VALIDATE_SEDML: |
133 | 206 | raise_errors_warnings(validation.validate_task(task), |
134 | 207 | error_summary='Task `{}` is invalid.'.format(task.id)) |
135 | 208 | 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)) |
139 | 212 | 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)) |
141 | 214 | 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)) |
143 | 216 | 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)) |
145 | 218 | raise_errors_warnings(*validation.validate_data_generator_variables(variables), |
146 | 219 | error_summary='Data generator variables for task `{}` are invalid.'.format(task.id)) |
147 | 220 |
|
148 | 221 | 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') |
153 | 224 |
|
154 | 225 | # 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) |
158 | 230 | try: |
159 | | - interfaces.convertSBML2PSC(sbmlfile=task.model.source, pscfile=model_filename) |
| 231 | + pysces_interface.convertSBML2PSC(sbmlfile=sbml_model_filename, pscfile=pysces_model_filename) |
160 | 232 | except Exception as exception: |
161 | | - os.remove(model_filename) |
| 233 | + os.remove(pysces_model_filename) |
162 | 234 | raise ValueError('Model at {} could not be imported:\n {}'.format( |
163 | 235 | 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) |
166 | 238 |
|
167 | 239 | # Load the algorithm specified by `simulation.algorithm.kisao_id` |
168 | 240 | sim = task.simulation |
@@ -233,99 +305,59 @@ def exec_sed_task(task, variables, preprocessed_task=None, log=None, config=None |
233 | 305 | if model.mode_integrator == 'CVODE': |
234 | 306 | model.__settings__['cvode_return_event_timepoints'] = False |
235 | 307 |
|
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())) |
247 | 311 |
|
248 | | - # execute simulation |
249 | | - model.Simulate() |
| 312 | + variable_results_model_attr_map = {} |
250 | 313 |
|
251 | | - # extract results |
252 | | - variable_results = VariableResults() |
253 | 314 | unpredicted_symbols = [] |
254 | 315 | unpredicted_targets = [] |
255 | | - results, labels = model.data_sim.getAllSimData(lbls=True) |
256 | | - labels = {label: i_label for i_label, label in enumerate(labels)} |
257 | 316 |
|
258 | 317 | for variable in variables: |
259 | 318 | 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) |
263 | 321 | else: |
264 | 322 | unpredicted_symbols.append(variable.symbol) |
265 | 323 |
|
266 | 324 | 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) |
275 | 334 |
|
276 | 335 | if unpredicted_symbols: |
277 | 336 | raise NotImplementedError("".join([ |
278 | 337 | "The following variable symbols are not supported:\n - {}\n\n".format( |
279 | 338 | '\n - '.join(sorted(unpredicted_symbols)), |
280 | 339 | ), |
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), |
282 | 341 | ])) |
283 | 342 |
|
284 | 343 | if unpredicted_targets: |
285 | 344 | 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( |
287 | 346 | '\n - '.join(sorted(unpredicted_targets)), |
288 | 347 | ), |
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))), |
291 | 350 | ), |
292 | 351 | ])) |
293 | 352 |
|
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