diff --git a/biosimulators_utils/_version.py b/biosimulators_utils/_version.py index 17c53dbd..27a98350 100644 --- a/biosimulators_utils/_version.py +++ b/biosimulators_utils/_version.py @@ -1 +1 @@ -__version__ = '0.1.182' +__version__ = '0.1.183' diff --git a/biosimulators_utils/sedml/exec.py b/biosimulators_utils/sedml/exec.py index 949a0dbf..f0209277 100644 --- a/biosimulators_utils/sedml/exec.py +++ b/biosimulators_utils/sedml/exec.py @@ -27,6 +27,7 @@ from lxml import etree # noqa: F401 import copy import datetime +import functools import numpy import os import sys @@ -51,7 +52,8 @@ def exec_sed_doc(task_executer, doc, working_dir, base_out_path, rel_out_path=No apply_xml_model_changes=False, log=None, indent=0, pretty_print_modified_xml_models=False, log_level=StandardOutputErrorCapturerLevel.c, - config=None): + config=None, get_value_executer=None, set_value_executer=None, preprocessed_task_executer=None, + reset_executer=None): """ Execute the tasks specified in a SED document and generate the specified outputs Args: @@ -164,14 +166,24 @@ def exec_task(task, variables, preprocessed_task=None, log=None, config=None, ** original_model_changes = {} temp_model_sources = [] model_etrees = {} + preprocessed_task = None + + task_vars = get_variables_for_task(doc, task) + preprocessed_task_sub_executer = None + if preprocessed_task_executer: + preprocessed_task_sub_executer = functools.partial(preprocessed_task_executer, + task, task_vars, + config=config) + for original_model in original_models: original_model_sources[original_model.id] = original_model.source original_model_changes[original_model.id] = original_model.changes - temp_model, temp_model_source, model_etree = resolve_model_and_apply_xml_changes( + temp_model, temp_model_source, model_etree, preprocessed_task = resolve_model_and_apply_xml_changes( original_model, doc, working_dir, apply_xml_model_changes=apply_xml_model_changes, - pretty_print_modified_xml_models=pretty_print_modified_xml_models) + pretty_print_modified_xml_models=pretty_print_modified_xml_models, + set_value_executer=set_value_executer, preprocessed_task_sub_executer=preprocessed_task_sub_executer) original_model.source = temp_model.source original_model.changes = temp_model.changes @@ -181,18 +193,24 @@ def exec_task(task, variables, preprocessed_task=None, log=None, config=None, ** model_etrees[original_model.id] = model_etree - task_vars = get_variables_for_task(doc, task) + # The preprocessed task was not created if there was no set_value_executer, so create one now: + if not preprocessed_task and preprocessed_task_executer: + preprocessed_task = preprocessed_task_sub_executer() # execute task if isinstance(task, Task): - task_var_results = exec_task(task, task_executer, task_vars, doc, log=task_log, config=config) + task_var_results = exec_task(task, task_executer, task_vars, doc, + preprocessed_task=preprocessed_task, log=task_log, config=config) elif isinstance(task, RepeatedTask): task_var_results = exec_repeated_task(task, task_executer, task_vars, doc, apply_xml_model_changes=apply_xml_model_changes, model_etrees=model_etrees, pretty_print_modified_xml_models=pretty_print_modified_xml_models, - config=config) + config=config, preprocessed_task=preprocessed_task, + get_value_executer=get_value_executer, + set_value_executer=set_value_executer, + reset_executer=reset_executer) else: # pragma: no cover: already validated by :obj:`get_models_referenced_by_task` raise NotImplementedError('Tasks of type {} are not supported.'.format(task.__class__.__name__)) @@ -363,7 +381,7 @@ def exec_task(task, variables, preprocessed_task=None, log=None, config=None, ** return report_results, log -def exec_task(task, task_executer, task_vars, doc, log=None, config=None): +def exec_task(task, task_executer, task_vars, doc, log=None, config=None, preprocessed_task=None): """ Execute a basic SED task Args: @@ -401,7 +419,7 @@ def exec_task(task, variables, preprocessed_task=None, log=None, config=None, ** :obj:`VariableResults`: results of the variables """ # execute task - task_variable_results, _ = task_executer(task, task_vars, log=log, config=config) + task_variable_results, _ = task_executer(task, task_vars, log=log, config=config, preprocessed_task=preprocessed_task) # check that the expected variables were recorded variable_results = VariableResults() @@ -413,7 +431,8 @@ def exec_task(task, variables, preprocessed_task=None, log=None, config=None, ** def exec_repeated_task(task, task_executer, task_vars, doc, apply_xml_model_changes=False, model_etrees=None, - pretty_print_modified_xml_models=False, config=None): + pretty_print_modified_xml_models=False, config=None, preprocessed_task=None, get_value_executer=None, + set_value_executer=None, reset_executer=None): """ Execute a repeated SED task Args: @@ -451,7 +470,7 @@ def exec_task(task, variables, preprocessed_task=None, log=None, config=None, ** :obj:`VariableResults`: results of the variables """ # warn about inability to not reset models - if not task.reset_model_for_each_iteration: + if not task.reset_model_for_each_iteration and not reset_executer: models = get_first_last_models_executed_by_task(task) if models[0] == models[-1]: msg = ( @@ -462,7 +481,8 @@ def exec_task(task, variables, preprocessed_task=None, log=None, config=None, ** sub_tasks = sorted(task.sub_tasks, key=lambda sub_task: sub_task.order) for prev_sub_task, next_sub_task in zip(sub_tasks[0:-1], sub_tasks[1:]): - if get_first_last_models_executed_by_task(prev_sub_task.task)[-1] == get_first_last_models_executed_by_task(next_sub_task.task)[0]: + if get_first_last_models_executed_by_task(prev_sub_task.task)[-1] == get_first_last_models_executed_by_task(next_sub_task.task)[0] \ + and not reset_executer: msg = ( 'Only independent execution of sub-tasks is supported. ' 'Successive sub-tasks will not be executed starting from the end state of the previous sub-task.' @@ -500,6 +520,8 @@ def exec_task(task, variables, preprocessed_task=None, log=None, config=None, ** doc = copy.deepcopy(original_doc) task = next(task for task in doc.tasks if task.id == original_task.id) model_etrees = copy.deepcopy(original_model_etrees) + if reset_executer: + reset_executer(preprocessed_task) # get range values current_range_values = {} @@ -514,27 +536,35 @@ def exec_task(task, variables, preprocessed_task=None, log=None, config=None, ** for change in task.changes: variable_values = {} for variable in change.variables: - if not apply_xml_model_changes: + if get_value_executer and preprocessed_task: + value = get_value_executer(change.model, variable, preprocessed_task) + variable_values[variable.id] = value + elif not apply_xml_model_changes: raise NotImplementedError('Set value changes that involve variables of non-XML-encoded models are not supported.') - variable_values[variable.id] = get_value_of_variable_model_xml_targets(variable, model_etrees) + else: + variable_values[variable.id] = get_value_of_variable_model_xml_targets(variable, model_etrees) new_value = calc_compute_model_change_new_value(change, variable_values=variable_values, range_values=current_range_values) - if new_value == int(new_value): - new_value = str(int(new_value)) + + if set_value_executer: + set_value_executer(change.model, change.target, change.symbol, new_value, preprocessed_task) else: - new_value = str(new_value) + if new_value == int(new_value): + new_value = str(int(new_value)) + else: + new_value = str(new_value) - if change.symbol: - raise NotImplementedError('Set value changes of symbols is not supported.') + if change.symbol: + raise NotImplementedError('Set value changes of symbols is not supported.') - attr_change = ModelAttributeChange(target=change.target, target_namespaces=change.target_namespaces, new_value=new_value) + attr_change = ModelAttributeChange(target=change.target, target_namespaces=change.target_namespaces, new_value=new_value) - if apply_xml_model_changes and is_model_language_encoded_in_xml(change.model.language): - model = Model(changes=[attr_change]) - apply_changes_to_xml_model(model, model_etrees[change.model.id], None, None) + if apply_xml_model_changes and is_model_language_encoded_in_xml(change.model.language): + model = Model(changes=[attr_change]) + apply_changes_to_xml_model(model, model_etrees[change.model.id], None, None) - else: - change.model.changes.append(attr_change) + else: + change.model.changes.append(attr_change) # sort the sub-tasks sub_tasks = sorted(task.sub_tasks, key=lambda sub_task: sub_task.order) @@ -554,7 +584,7 @@ def exec_task(task, variables, preprocessed_task=None, log=None, config=None, ** standalone=False, pretty_print=pretty_print_modified_xml_models) - sub_task_var_results = exec_task(sub_task.task, task_executer, task_vars, doc, config=config) + sub_task_var_results = exec_task(sub_task.task, task_executer, task_vars, doc, config=config, preprocessed_task=preprocessed_task) if apply_xml_model_changes and is_model_language_encoded_in_xml(model.language): os.remove(model.source) @@ -565,7 +595,10 @@ def exec_task(task, variables, preprocessed_task=None, log=None, config=None, ** apply_xml_model_changes=apply_xml_model_changes, model_etrees=model_etrees, pretty_print_modified_xml_models=pretty_print_modified_xml_models, - config=config) + config=config, preprocessed_task=preprocessed_task, + get_value_executer=get_value_executer, + set_value_executer=set_value_executer, + reset_executer=reset_executer) else: # pragma: no cover: already validated by :obj:`get_first_last_models_executed_by_task` raise NotImplementedError('Tasks of type {} are not supported.'.format(sub_task.task.__class__.__name__)) diff --git a/biosimulators_utils/sedml/utils.py b/biosimulators_utils/sedml/utils.py index ecbecdfd..7b884bed 100644 --- a/biosimulators_utils/sedml/utils.py +++ b/biosimulators_utils/sedml/utils.py @@ -258,10 +258,12 @@ def get_model_changes_for_task(task): BIOMODELS_DOWNLOAD_ENDPOINT = 'https://www.ebi.ac.uk/biomodels/model/download/{}?filename={}_url.xml' -def resolve_model_and_apply_xml_changes(model, sed_doc, working_dir, +def resolve_model_and_apply_xml_changes(orig_model, sed_doc, working_dir, apply_xml_model_changes=True, save_to_file=True, - pretty_print_modified_xml_models=False): + pretty_print_modified_xml_models=False, + set_value_executer=None, + preprocessed_task_sub_executer=None): """ Resolve the source of a model and, optionally, apply XML changes to the model. Args: @@ -288,10 +290,13 @@ def resolve_model_and_apply_xml_changes(model, sed_doc, working_dir, a remote source of modified * :obj:`etree._Element`: element tree for the resolved/modified model """ - model = copy.deepcopy(model) + model = copy.deepcopy(orig_model) + # We need to save this because we're going to change it, then change it back. + orig_model_source = orig_model.source # resolve model temp_model_source = resolve_model(model, sed_doc, working_dir) + preprocessed_task = None # apply changes to model if apply_xml_model_changes and model.language and is_model_language_encoded_in_xml(model.language): @@ -302,8 +307,12 @@ def resolve_model_and_apply_xml_changes(model, sed_doc, working_dir, raise ValueError('The model could not be parsed because the model is not a valid XML document: {}'.format(str(exception))) if model.changes: + # Change source here so that tasks point to actual source they can find. + orig_model.source = model.source # apply changes - apply_changes_to_xml_model(model, model_etree, sed_doc, working_dir) + preprocessed_task = apply_changes_to_xml_model(model, model_etree, sed_doc, working_dir, + set_value_executer=set_value_executer, + preprocessed_task_sub_executer=preprocessed_task_sub_executer) model.changes.clear() # write model to file @@ -321,7 +330,9 @@ def resolve_model_and_apply_xml_changes(model, sed_doc, working_dir, else: model_etree = None - return model, temp_model_source, model_etree + # Reset the model source, in case it matters. + orig_model.source = orig_model_source + return model, temp_model_source, model_etree, preprocessed_task def resolve_model(model, sed_doc, working_dir): @@ -401,7 +412,8 @@ def resolve_model(model, sed_doc, working_dir): def apply_changes_to_xml_model(model, model_etree, sed_doc=None, working_dir=None, variable_values=None, range_values=None, - validate_unique_xml_targets=True): + validate_unique_xml_targets=True, + set_value_executer=None, preprocessed_task_sub_executer=None): """ Modify an XML-encoded model according to a model change Args: @@ -418,29 +430,10 @@ def apply_changes_to_xml_model(model, model_etree, sed_doc=None, working_dir=Non validate_unique_xml_targets (:obj:`bool`, optional): whether to validate the XML targets match uniue objects """ - for change in model.changes: - if isinstance(change, ModelAttributeChange): - # get object to change - obj_xpath, sep, attr = change.target.rpartition('/@') - if sep != '/@': - raise ValueError('target {} is not a valid XPath to an attribute of a model element'.format(change.target)) - objs = eval_xpath(model_etree, obj_xpath, change.target_namespaces) - if validate_unique_xml_targets and len(objs) != 1: - raise ValueError('xpath {} must match a single object'.format(obj_xpath)) - - ns_prefix, _, attr = attr.rpartition(':') - if ns_prefix: - ns = change.target_namespaces.get(ns_prefix, None) - if ns is None: - raise ValueError('No namespace is defined with prefix `{}`'.format(ns_prefix)) - attr = '{{{}}}{}'.format(ns, attr) - - # change value - for obj in objs: - obj.set(attr, change.new_value) - - elif isinstance(change, AddElementModelChange): + # First pass: Must-be-XML changes: + for change in model.changes: + if isinstance(change, AddElementModelChange): parents = eval_xpath(model_etree, change.target, change.target_namespaces) if validate_unique_xml_targets and len(parents) != 1: @@ -484,6 +477,51 @@ def apply_changes_to_xml_model(model, model_etree, sed_doc=None, working_dir=Non parent = element.getparent() parent.remove(element) + elif isinstance(change, ModelAttributeChange) or isinstance(change, ComputeModelChange): + change.model = model + + else: + raise NotImplementedError('Change{} of type {} is not supported.'.format( + ' ' + change.name if change.name else '', change.__class__.__name__)) + + # Interlude: set up the preprocessed task, if there's a set_value_executor + preprocessed_task = None + if set_value_executer: + model_etree.write(model.source, + xml_declaration=True, + encoding="utf-8", + standalone=False, + pretty_print=True) + + if preprocessed_task_sub_executer: + preprocessed_task = preprocessed_task_sub_executer() + + # Second pass: changes that might need to be interpreter-based: + for change in model.changes: + if isinstance(change, ModelAttributeChange): + if set_value_executer: + set_value_executer(change.model, change.target, None, change.new_value, preprocessed_task) + else: + # get object to change + obj_xpath, sep, attr = change.target.rpartition('/@') + if sep != '/@': + raise NotImplementedError('target ' + change.target + ' cannot be changed by XML manipulation, as the target ' + 'is not an attribute of a model element') + objs = eval_xpath(model_etree, obj_xpath, change.target_namespaces) + if validate_unique_xml_targets and len(objs) != 1: + raise ValueError('xpath {} must match a single object'.format(obj_xpath)) + + ns_prefix, _, attr = attr.rpartition(':') + if ns_prefix: + ns = change.target_namespaces.get(ns_prefix, None) + if ns is None: + raise ValueError('No namespace is defined with prefix `{}`'.format(ns_prefix)) + attr = '{{{}}}{}'.format(ns, attr) + + # change value + for obj in objs: + obj.set(attr, change.new_value) + elif isinstance(change, ComputeModelChange): # get the values of model variables referenced by compute model changes if variable_values is None: @@ -499,28 +537,30 @@ def apply_changes_to_xml_model(model, model_etree, sed_doc=None, working_dir=Non else: new_value = str(new_value) - # get object to change - obj_xpath, sep, attr = change.target.rpartition('/@') - if sep != '/@': - raise ValueError('target {} is not a valid XPath to an attribute of a model element'.format(change.target)) - objs = eval_xpath(model_etree, obj_xpath, change.target_namespaces) - if validate_unique_xml_targets and len(objs) != 1: - raise ValueError('xpath {} must match a single object'.format(obj_xpath)) - - ns_prefix, _, attr = attr.rpartition(':') - if ns_prefix: - ns = change.target_namespaces.get(ns_prefix, None) - if ns is None: - raise ValueError('No namespace is defined with prefix `{}`'.format(ns_prefix)) - attr = '{{{}}}{}'.format(ns, attr) - - # change value - for obj in objs: - obj.set(attr, new_value) + if set_value_executer: + set_value_executer(change.model, change.target, change.symbol, new_value, preprocessed_task) + else: + # get object to change + obj_xpath, sep, attr = change.target.rpartition('/@') + if sep != '/@': + raise NotImplementedError('target ' + change.target + ' cannot be changed by XML manipulation, as the target ' + 'is not an attribute of a model element') + objs = eval_xpath(model_etree, obj_xpath, change.target_namespaces) + if validate_unique_xml_targets and len(objs) != 1: + raise ValueError('xpath {} must match a single object'.format(obj_xpath)) + + ns_prefix, _, attr = attr.rpartition(':') + if ns_prefix: + ns = change.target_namespaces.get(ns_prefix, None) + if ns is None: + raise ValueError('No namespace is defined with prefix `{}`'.format(ns_prefix)) + attr = '{{{}}}{}'.format(ns, attr) - else: - raise NotImplementedError('Change{} of type {} is not supported.'.format( - ' ' + change.name if change.name else '', change.__class__.__name__)) + # change value + for obj in objs: + obj.set(attr, new_value) + + return preprocessed_task def get_values_of_variable_model_xml_targets_of_model_change(change, sed_doc, model_etrees, working_dir): @@ -541,7 +581,7 @@ def get_values_of_variable_model_xml_targets_of_model_change(change, sed_doc, mo for variable in change.variables: variable_model = variable.model if variable_model.id not in model_etrees: - copy_variable_model, temp_model_source, variable_model_etree = resolve_model_and_apply_xml_changes( + copy_variable_model, temp_model_source, variable_model_etree, preprocessed_task = resolve_model_and_apply_xml_changes( variable_model, sed_doc, working_dir, apply_xml_model_changes=True, save_to_file=False) @@ -575,7 +615,8 @@ def get_value_of_variable_model_xml_targets(variable, model_etrees): obj_xpath, sep, attr = variable.target.rpartition('/@') if sep != '/@': - raise ValueError('target {} is not a valid XPath to an attribute of a model element'.format(variable.target)) + raise NotImplementedError('the value of target ' + variable.target + + ' cannot be obtained by examining the XML, as the target is not an attribute of a model element') et = model_etrees[variable.model.id] obj = eval_xpath(et, obj_xpath, variable.target_namespaces) diff --git a/biosimulators_utils/sedml/validation.py b/biosimulators_utils/sedml/validation.py index 29f4522f..b29953fc 100644 --- a/biosimulators_utils/sedml/validation.py +++ b/biosimulators_utils/sedml/validation.py @@ -1386,8 +1386,8 @@ def validate_data_generator_variables(variables, model_etrees=None, validate_tar if not variable.id: variable_errors.append(['Variable must have an id.']) - if variable.model: - variable_errors.append(['Variable should not reference a model.']) + # if variable.model: + # variable_errors.append(['Variable should not reference a model.']) if variable.task: task_types.add(get_task_results_shape(variable.task)) diff --git a/tests/combine/test_combine_exec.py b/tests/combine/test_combine_exec.py index 4b25d2da..8dee88a7 100644 --- a/tests/combine/test_combine_exec.py +++ b/tests/combine/test_combine_exec.py @@ -383,7 +383,7 @@ def test_exec_sedml_docs_in_archive_without_log(self): archive_filename = os.path.join(self.tmp_dir, 'archive.omex') CombineArchiveWriter().run(archive, archive_dirname, archive_filename) - def sed_task_executer(task, variables, log=None, config=None): + def sed_task_executer(task, variables, log=None, config=None, preprocessed_task=None): if log: log.algorithm = task.simulation.algorithm.kisao_id log.simulator_details = { @@ -395,7 +395,7 @@ def sed_task_executer(task, variables, log=None, config=None): 'var_2': numpy.linspace(10., 20., task.simulation.number_of_points + 1), }), log - def sed_task_executer_error(task, variables, log=None, config=None): + def sed_task_executer_error(task, variables, log=None, config=None, preprocessed_task=None): raise ValueError('Big error') out_dir = os.path.join(self.tmp_dir, 'outputs') @@ -464,7 +464,7 @@ def exec_sed_doc(task_executer, filename, working_dir, base_out_dir, indent=0, log=None, log_level=None, config=None): return None, None - def sed_task_executer(task, variables): + def sed_task_executer(task, variables, preprocessed_task=None): pass sed_doc_executer = functools.partial(exec_sed_doc, sed_task_executer) diff --git a/tests/sedml/test_sedml_exec.py b/tests/sedml/test_sedml_exec.py index 688b7e95..8613a36a 100644 --- a/tests/sedml/test_sedml_exec.py +++ b/tests/sedml/test_sedml_exec.py @@ -186,7 +186,7 @@ def test_successful(self): filename = os.path.join(self.tmp_dir, 'test.sedml') io.SedmlSimulationWriter().run(doc, filename, validate_models_with_languages=False) - def exec_task(task, variables, log=None, config=None): + def exec_task(task, variables, log=None, config=None, preprocessed_task=None): results = VariableResults() if task.id == 'task_1_ss': results[doc.data_generators[0].variables[0].id] = numpy.array((1., 2.)) @@ -555,7 +555,7 @@ def test_with_model_changes(self): os.path.join(os.path.dirname(__file__), '..', 'fixtures', 'sbml-three-species.xml'), os.path.join(working_dir, 'model1.xml')) - def exec_task(task, variables, log=None, config=None): + def exec_task(task, variables, log=None, config=None, preprocessed_task=None): et = etree.parse(task.model.source) results = VariableResults() @@ -591,7 +591,7 @@ def test_warnings(self): filename = os.path.join(self.tmp_dir, 'test.sedml') io.SedmlSimulationWriter().run(doc, filename) - def exec_task(task, variables, log=None, config=None): + def exec_task(task, variables, log=None, config=None, preprocessed_task=None): return VariableResults(), log out_dir = os.path.join(self.tmp_dir, 'results') @@ -651,7 +651,7 @@ def exec_task(task, variables, log=None, config=None): filename = os.path.join(self.tmp_dir, 'test.sedml') io.SedmlSimulationWriter().run(doc, filename, validate_models_with_languages=False) - def exec_task(task, variables, log=None, config=None): + def exec_task(task, variables, log=None, config=None, preprocessed_task=None): if task.id == 'task1': return VariableResults({'data_gen_1_var_1': numpy.array(1.)}), log else: @@ -720,7 +720,7 @@ def test_errors(self): filename = os.path.join(self.tmp_dir, 'test.sedml') io.SedmlSimulationWriter().run(doc, filename, validate_models_with_languages=False) - def exec_task(task, variables, log=None, config=None): + def exec_task(task, variables, log=None, config=None, preprocessed_task=None): return VariableResults(), log working_dir = os.path.dirname(filename) @@ -786,7 +786,7 @@ def exec_task(task, variables, log=None, config=None): ], )) - def exec_task(task, variables, log=None, config=None): + def exec_task(task, variables, log=None, config=None, preprocessed_task=None): results = VariableResults() results[doc.data_generators[0].variables[0].id] = numpy.array((1.,)) results[doc.data_generators[0].variables[1].id] = numpy.array((1.,)) @@ -827,7 +827,7 @@ def exec_task(task, variables, log=None, config=None): ) ] - def exec_task(task, variables, log=None, config=None): + def exec_task(task, variables, log=None, config=None, preprocessed_task=None): results = VariableResults() results[doc.data_generators[0].variables[0].id] = numpy.array((1.,)) return results, log @@ -875,7 +875,7 @@ def exec_task(task, variables, log=None, config=None): ), ] - def exec_task(task, variables, log=None, config=None): + def exec_task(task, variables, log=None, config=None, preprocessed_task=None): results = VariableResults() results[doc.data_generators[0].variables[0].id] = numpy.array((1.,)) results[doc.data_generators[0].variables[1].id] = numpy.array((1., 2.)) @@ -947,7 +947,7 @@ def exec_task(task, variables, log=None, config=None): ), ] - def exec_task(task, variables, log=None, config=None): + def exec_task(task, variables, log=None, config=None, preprocessed_task=None): results = VariableResults() results[doc.data_generators[0].variables[0].id] = numpy.array((1.,)) results[doc.data_generators[1].variables[0].id] = numpy.array((1., 2.)) @@ -1045,7 +1045,7 @@ def exec_task(task, variables, log=None, config=None): ), ] - def exec_task(task, variables, log=None, config=None): + def exec_task(task, variables, log=None, config=None, preprocessed_task=None): results = VariableResults() results[doc.data_generators[0].variables[0].id] = numpy.array((1., 2.)) results[doc.data_generators[1].variables[0].id] = numpy.array((2., 3.)) @@ -1171,7 +1171,7 @@ def test_2d_plot(self): filename = os.path.join(self.tmp_dir, 'test.sedml') io.SedmlSimulationWriter().run(doc, filename, validate_models_with_languages=False) - def exec_task(task, variables, log=None, config=None): + def exec_task(task, variables, log=None, config=None, preprocessed_task=None): results = VariableResults() results[doc.data_generators[0].variables[0].id] = numpy.linspace(0., 10., 10 + 1) results[doc.data_generators[1].variables[0].id] = 2 * results[doc.data_generators[0].variables[0].id] @@ -1274,7 +1274,7 @@ def exec_task(task, variables, log=None, config=None): ) # error with a task - def exec_task(task, variables, log=None, config=None): + def exec_task(task, variables, log=None, config=None, preprocessed_task=None): results = VariableResults() results[doc.data_generators[0].variables[0].id] = None results[doc.data_generators[1].variables[0].id] = 2 * numpy.linspace(0., 10., 10 + 1) @@ -1421,7 +1421,7 @@ def test_3d_plot(self): filename = os.path.join(self.tmp_dir, 'test.sedml') io.SedmlSimulationWriter().run(doc, filename, validate_models_with_languages=False) - def exec_task(task, variables, log=None, config=None): + def exec_task(task, variables, log=None, config=None, preprocessed_task=None): results = VariableResults() x = numpy.arange(-5, 5, 0.25) x, _ = numpy.meshgrid(x, x) @@ -1662,7 +1662,7 @@ def test_exec_repeated_task(self): model2.id: etree.parse(model_filename2), } - def task_executer(task, variables, log=None, config=None): + def task_executer(task, variables, log=None, config=None, preprocessed_task=None): et = etree.parse(task.model.source) if task.id == task1.id: @@ -1781,7 +1781,7 @@ def test_exec_sed_doc_with_repeated_task(self): file.write(' ') file.write('') - def task_executer(task, variables, log=None, config=None): + def task_executer(task, variables, log=None, config=None, preprocessed_task=None): results = VariableResults({ 'x': numpy.linspace(10., 15., 6), 'y': numpy.linspace(20., 25., 6), @@ -1829,7 +1829,7 @@ def test_capturer_not_available(self): file.write(' ') file.write('') - def task_executer(task, variables, log=None, config=None): + def task_executer(task, variables, log=None, config=None, preprocessed_task=None): results = VariableResults({ 'x': numpy.linspace(10., 15., 6), }) @@ -1906,7 +1906,7 @@ def test_exec_without_log(self): filename = os.path.join(self.tmp_dir, 'test.sedml') io.SedmlSimulationWriter().run(doc, filename, validate_models_with_languages=False) - def exec_task(task, variables, log=None, config=None): + def exec_task(task, variables, log=None, config=None, preprocessed_task=None): results = VariableResults() results[doc.data_generators[0].variables[0].id] = numpy.array((1., 2.)) return results, log diff --git a/tests/sedml/test_sedml_utils.py b/tests/sedml/test_sedml_utils.py index bb2e8eb7..10390b36 100644 --- a/tests/sedml/test_sedml_utils.py +++ b/tests/sedml/test_sedml_utils.py @@ -685,7 +685,7 @@ def test_errors(self): target_namespaces={'sbml': 'http://www.sbml.org/sbml/level2/version4'}, new_value='1.9') et = etree.parse(self.FIXTURE_FILENAME) - with self.assertRaises(ValueError): + with self.assertRaises(NotImplementedError): utils.apply_changes_to_xml_model(data_model.Model(changes=[change]), et, None, None) change = data_model.ModelAttributeChange( @@ -768,7 +768,7 @@ def test_apply_compute_model_change_new_value(self): } change.variables[0].target = "/model/parameter[@id='x']" - with self.assertRaisesRegex(ValueError, 'not a valid XPath'): + with self.assertRaisesRegex(NotImplementedError, 'cannot be obtained by examining the XML'): utils.get_value_of_variable_model_xml_targets(change.variables[0], models) change.variables[0].target = "/model/parameter/@value" @@ -864,7 +864,7 @@ def test_apply_compute_model_change_new_value(self): change.target = "/model/parameter[@type='parameter']" et = etree.parse(in_file) - with self.assertRaisesRegex(ValueError, 'not a valid XPath to an attribute'): + with self.assertRaisesRegex(NotImplementedError, 'cannot be changed by XML manipulation'): utils.apply_changes_to_xml_model(data_model.Model(changes=[change]), et, None, None, variable_values=variable_values) with open(in_file, 'w') as file: @@ -880,6 +880,43 @@ def test_apply_compute_model_change_new_value(self): change.target_namespaces['qual'] = "https://qual.sbml.org" utils.apply_changes_to_xml_model(data_model.Model(changes=[change]), et, None, None, variable_values=variable_values) + def test_apply_compute_model_change_new_value_only_objects(self): + change = data_model.ComputeModelChange( + target="/model/parameter[@id='p1']", + parameters=[ + data_model.Parameter(id='a', value=1.5), + data_model.Parameter(id='b', value=2.25), + data_model.Parameter(id='c', value=2.), + ], + variables=[ + data_model.Variable(id='x', model=data_model.Model(id='model_1'), target="/model/parameter[@id='x']"), + data_model.Variable(id='y', model=data_model.Model(id='model_2'), target="/model/parameter[@id='y']"), + ], + math='a * x + b * y', + ) + + # get values of variables + model_filename = os.path.join(self.tmp_dir, 'model_1.xml') + with open(model_filename, 'w') as file: + file.write('') + file.write('') + file.write('') + file.write('') + models = { + 'model_1': etree.parse(model_filename), + 'model_2': etree.parse(model_filename), + } + + change.variables[0].target = None + change.variables[0].symbol = True + + change.variables[0].target = "/model/parameter[@id='x']" + change.variables[0].symbol = None + with self.assertRaisesRegex(NotImplementedError, 'cannot be obtained by examining the XML'): + self.assertEqual(utils.get_value_of_variable_model_xml_targets(change.variables[0], models), 2.0) + with self.assertRaisesRegex(NotImplementedError, 'cannot be obtained by examining the XML'): + self.assertEqual(utils.get_value_of_variable_model_xml_targets(change.variables[1], models), 3.0) + def test_set_value_calc_compute_model_change_new_value(self): change = data_model.SetValueComputeModelChange( target="/model/parameter[@id='p1']/@value", @@ -1521,7 +1558,7 @@ def test_resolve_model_and_apply_xml_changes_error_handling(self): file.write('') file.write('') - temp_model, temp_model_source, temp_model_etree = utils.resolve_model_and_apply_xml_changes( + temp_model, temp_model_source, temp_model_etree, __ = utils.resolve_model_and_apply_xml_changes( model, sed_doc, self.tmp_dir, save_to_file=False) self.assertEqual(temp_model.source, model_filename) @@ -1540,7 +1577,7 @@ def test_resolve_model_and_apply_xml_changes_error_handling(self): model.language = data_model.ModelLanguage.BNGL.value with open(model_filename, 'w') as file: file.write('sbml') - temp_model, temp_model_source, temp_model_etree = utils.resolve_model_and_apply_xml_changes( + temp_model, temp_model_source, temp_model_etree, __ = utils.resolve_model_and_apply_xml_changes( model, sed_doc, self.tmp_dir, save_to_file=False) self.assertEqual(temp_model.source, model_filename) @@ -1647,3 +1684,6 @@ def test_get_task_results_shape(self): ], ) self.assertEqual(utils.get_task_results_shape(task), (5, 1, 3, 2, 11)) + +if __name__ == "__main__": + unittest.main() diff --git a/tests/sedml/test_sedml_validation.py b/tests/sedml/test_sedml_validation.py index 2da94095..f4778561 100644 --- a/tests/sedml/test_sedml_validation.py +++ b/tests/sedml/test_sedml_validation.py @@ -234,7 +234,7 @@ def test_validate_doc(self): ], )) errors, warnings = validation.validate_doc(doc, self.dirname) - self.assertIn('should not reference a model', flatten_nested_list_of_strings(errors)) + # self.assertIn('should not reference a model', flatten_nested_list_of_strings(errors)) self.assertEqual(warnings, []) doc = data_model.SedDocument() @@ -1210,7 +1210,7 @@ def test_validate_task(self): data_model.Variable(task=data_model.Task(), model=data_model.Model()) ] errors, warnings = self._validate_task(task, variables) - self.assertIn('should not reference a model', flatten_nested_list_of_strings(errors)) + # self.assertIn('should not reference a model', flatten_nested_list_of_strings(errors)) self.assertEqual(warnings, []) variables = [