diff --git a/Dockerfile b/Dockerfile index fa406d1..073fdcd 100644 --- a/Dockerfile +++ b/Dockerfile @@ -14,7 +14,7 @@ # Base OS FROM python:3.9-slim-buster -ARG VERSION="0.1.14" +ARG VERSION="0.1.15" ARG SIMULATOR_VERSION="2.2.0" # metadata diff --git a/biosimulators.json b/biosimulators.json index 36752c7..b37caee 100644 --- a/biosimulators.json +++ b/biosimulators.json @@ -333,10 +333,22 @@ { "kisaoId": { "namespace": "KISAO", - "id": "KISAO_0000656" + "id": "KISAO_0000671" }, - "id": "variable_step_size", - "name": "Adaptive time steps", + "id": "stiff", + "name": "Stiff", + "type": "boolean", + "value": "true", + "recommendedRange": null, + "availableSoftwareInterfaceTypes": ["library", "command-line application", "desktop application", "BioSimulators Docker image"] + }, + { + "kisaoId": { + "namespace": "KISAO", + "id": "KISAO_0000670" + }, + "id": "multiple_steps", + "name": "Multiple steps", "type": "boolean", "value": "false", "recommendedRange": null, @@ -381,6 +393,67 @@ } ] }, + { + "id": "euler", + "name": "Forward Euler method", + "kisaoId": { + "namespace": "KISAO", + "id": "KISAO_0000030" + }, + "modelingFrameworks": [{ + "namespace": "SBO", + "id": "SBO_0000293" + }], + "modelFormats": [{ + "namespace": "EDAM", + "id": "format_2585", + "version": null, + "supportedFeatures": [] + }], + "simulationFormats": [{ + "namespace": "EDAM", + "id": "format_3685", + "version": null, + "supportedFeatures": [] + }], + "archiveFormats": [{ + "namespace": "EDAM", + "id": "format_3686", + "version": "1", + "supportedFeatures": [] + }], + "parameters": [], + "dependentDimensions": [{ + "namespace": "SIO", + "id": "SIO_000418" + }], + "dependentVariableTargetPatterns": [{ + "variables": "species concentrations", + "targetPattern": "/sbml:sbml/sbml:model/sbml:listOfSpecies/sbml:species" + }, + { + "variables": "parameter values", + "targetPattern": "/sbml:sbml/sbml:model/sbml:listOfParameters/sbml:parameter/@value" + }, + { + "variables": "reaction fluxes", + "targetPattern": "/sbml:sbml/sbml:model/sbml:listOfReactions/sbml:reaction" + }, + { + "variables": "compartment sizes", + "targetPattern": "/sbml:sbml/sbml:model/sbml:listOfCompartments/sbml:compartment" + } + ], + "availableSoftwareInterfaceTypes": ["library", "command-line application", "desktop application", "BioSimulators Docker image"], + "dependencies": [{ + "name": "libRoadRunner", + "version": null, + "required": true, + "freeNonCommercialLicense": true, + "url": "http://libroadrunner.org/" + }], + "citations": [] + }, { "id": "runge_kutta_4", "name": "Runge-Kutta fourth order method", @@ -474,33 +547,36 @@ "parameters": [{ "kisaoId": { "namespace": "KISAO", - "id": "KISAO_0000656" + "id": "KISAO_0000485" }, - "name": "Adaptive time steps", - "type": "boolean", - "value": "true", + "id": "minimum_time_step", + "name": "Minimum time step", + "type": "float", + "value": "1e-12", "recommendedRange": null, "availableSoftwareInterfaceTypes": ["library", "command-line application", "desktop application", "BioSimulators Docker image"] }, { "kisaoId": { "namespace": "KISAO", - "id": "KISAO_0000485" + "id": "KISAO_0000467" }, - "name": "Minimum time step", + "id": "maximum_time_step", + "name": "Maximum time step", "type": "float", - "value": "1e-12", + "value": "1.0", "recommendedRange": null, "availableSoftwareInterfaceTypes": ["library", "command-line application", "desktop application", "BioSimulators Docker image"] }, { "kisaoId": { "namespace": "KISAO", - "id": "KISAO_0000467" + "id": "KISAO_0000597" }, - "name": "Maximum time step", + "id": "epsilon", + "name": "Epsilon", "type": "float", - "value": "1.0", + "value": "0.000000000001", "recommendedRange": null, "availableSoftwareInterfaceTypes": ["library", "command-line application", "desktop application", "BioSimulators Docker image"] } @@ -537,7 +613,7 @@ "citations": [] }, { - "id": "gillespie_direct_ssa", + "id": "gillespie", "name": "Gillespie direct method of the Stochastic Simulation Algorithm (SSA)", "kisaoId": { "namespace": "KISAO", @@ -584,30 +660,9 @@ "namespace": "KISAO", "id": "KISAO_0000488" }, + "id": "seed", "name": "Random number generator seed", "type": "integer", - "value": "0", - "recommendedRange": null, - "availableSoftwareInterfaceTypes": ["library", "command-line application", "desktop application", "BioSimulators Docker image"] - }, - { - "kisaoId": { - "namespace": "KISAO", - "id": "KISAO_0000656" - }, - "name": "Adaptive time steps", - "type": "boolean", - "value": "true", - "recommendedRange": null, - "availableSoftwareInterfaceTypes": ["library", "command-line application", "desktop application", "BioSimulators Docker image"] - }, - { - "kisaoId": { - "namespace": "KISAO", - "id": "KISAO_0000332" - }, - "name": "Initial time step", - "type": "float", "value": null, "recommendedRange": null, "availableSoftwareInterfaceTypes": ["library", "command-line application", "desktop application", "BioSimulators Docker image"] @@ -615,22 +670,12 @@ { "kisaoId": { "namespace": "KISAO", - "id": "KISAO_0000485" + "id": "KISAO_0000673" }, - "name": "Minimum time step", - "type": "float", - "value": null, - "recommendedRange": null, - "availableSoftwareInterfaceTypes": ["library", "command-line application", "desktop application", "BioSimulators Docker image"] - }, - { - "kisaoId": { - "namespace": "KISAO", - "id": "KISAO_0000467" - }, - "name": "Maximum time step", - "type": "float", - "value": null, + "id": "nonnegative", + "name": "Skip reactions which would result in negative species amounts", + "type": "boolean", + "value": "false", "recommendedRange": null, "availableSoftwareInterfaceTypes": ["library", "command-line application", "desktop application", "BioSimulators Docker image"] } @@ -723,6 +768,7 @@ "namespace": "KISAO", "id": "KISAO_0000209" }, + "id": "relative_tolerance", "name": "Relative tolerance", "type": "float", "value": "1e-12", @@ -734,6 +780,7 @@ "namespace": "KISAO", "id": "KISAO_0000486" }, + "id": "maximum_iterations", "name": "Maximum number of iterations", "type": "integer", "value": "100", @@ -745,11 +792,120 @@ "namespace": "KISAO", "id": "KISAO_0000487" }, + "id": "minimum_damping", "name": "Minimum damping factor", "type": "float", "value": "1e-20", "recommendedRange": null, "availableSoftwareInterfaceTypes": ["library", "command-line application", "desktop application", "BioSimulators Docker image"] + }, + { + "kisaoId": { + "namespace": "KISAO", + "id": "KISAO_0000674" + }, + "id": "allow_presimulation", + "name": "Presimulate", + "type": "boolean", + "value": "false", + "recommendedRange": null, + "availableSoftwareInterfaceTypes": ["library", "command-line application", "desktop application", "BioSimulators Docker image"] + }, + { + "kisaoId": { + "namespace": "KISAO", + "id": "KISAO_0000677" + }, + "id": "presimulation_maximum_steps", + "name": "Maximum number of steps for presimulation", + "type": "integer", + "value": "100", + "recommendedRange": null, + "availableSoftwareInterfaceTypes": ["library", "command-line application", "desktop application", "BioSimulators Docker image"] + }, + { + "kisaoId": { + "namespace": "KISAO", + "id": "KISAO_0000680" + }, + "id": "presimulation_time", + "name": "Amount of time to presimulate", + "type": "float", + "value": "100", + "recommendedRange": null, + "availableSoftwareInterfaceTypes": ["library", "command-line application", "desktop application", "BioSimulators Docker image"] + }, + { + "kisaoId": { + "namespace": "KISAO", + "id": "KISAO_0000682" + }, + "id": "allow_approx", + "name": "Whether to find an approximate solution if an exact solution could not be found", + "type": "boolean", + "value": "false", + "recommendedRange": null, + "availableSoftwareInterfaceTypes": ["library", "command-line application", "desktop application", "BioSimulators Docker image"] + }, + { + "kisaoId": { + "namespace": "KISAO", + "id": "KISAO_0000683" + }, + "id": "approx_tolerance", + "name": "Tolerance for finding an approximate solution", + "type": "float", + "value": "0.000001", + "recommendedRange": null, + "availableSoftwareInterfaceTypes": ["library", "command-line application", "desktop application", "BioSimulators Docker image"] + }, + { + "kisaoId": { + "namespace": "KISAO", + "id": "KISAO_0000678" + }, + "id": "approx_maximum_steps", + "name": "Maximum number of steps for finding an approximate solution", + "type": "integer", + "value": "10000", + "recommendedRange": null, + "availableSoftwareInterfaceTypes": ["library", "command-line application", "desktop application", "BioSimulators Docker image"] + }, + { + "kisaoId": { + "namespace": "KISAO", + "id": "KISAO_0000679" + }, + "id": "approx_time", + "name": "Maximum amount of time to spend finding finding an approximate solution", + "type": "float", + "value": "10000", + "recommendedRange": null, + "availableSoftwareInterfaceTypes": ["library", "command-line application", "desktop application", "BioSimulators Docker image"] + }, + { + "kisaoId": { + "namespace": "KISAO", + "id": "KISAO_0000675" + }, + "id": "broyden_method", + "name": "Broyden method", + "type": "integer", + "value": "0", + "recommendedRange": null, + "availableSoftwareInterfaceTypes": ["library", "command-line application", "desktop application", "BioSimulators Docker image"] + }, + { + "kisaoId": { + "namespace": "KISAO", + "id": "KISAO_0000676" + }, + "id": "linearity", + "name": "Degree of linearity of the systen", + "type": "integer", + "value": "3", + "recommendedRange": null, + "availableSoftwareInterfaceTypes": ["library", "command-line application", "desktop application", "BioSimulators Docker image"] } ], "dependentDimensions": [], diff --git a/biosimulators_tellurium/__init__.py b/biosimulators_tellurium/__init__.py index 51b44be..056c2f9 100644 --- a/biosimulators_tellurium/__init__.py +++ b/biosimulators_tellurium/__init__.py @@ -7,7 +7,7 @@ __all__ = [ '__version__', 'get_simulator_version', - # 'exec_sed_task', + 'exec_sed_task', 'exec_sedml_docs_in_combine_archive', ] diff --git a/biosimulators_tellurium/__main__.py b/biosimulators_tellurium/__main__.py index 7576815..ea176b0 100644 --- a/biosimulators_tellurium/__main__.py +++ b/biosimulators_tellurium/__main__.py @@ -8,12 +8,38 @@ from . import get_simulator_version from ._version import __version__ +from .config import Config from .core import exec_sedml_docs_in_combine_archive +from .data_model import SedmlInterpreter, PlottingEngine from biosimulators_utils.simulator.cli import build_cli +from biosimulators_utils.simulator.data_model import EnvironmentVariable +from biosimulators_utils.simulator.environ import ENVIRONMENT_VARIABLES +from unittest import mock + +with mock.patch.dict('os.environ', {}): + config = Config() + +environment_variables = list(ENVIRONMENT_VARIABLES.values()) + [ + EnvironmentVariable( + name='SEDML_INTERPRETER', + description='SED-ML interpreter.', + options=list(SedmlInterpreter.__members__.keys()), + default=config.sedml_interpreter, + more_info_url='https://docs.biosimulators.org/Biosimulators_tellurium/source/Biosimulators_tellurium.html', + ), + EnvironmentVariable( + name='PLOTTING_ENGINE', + description='Plotting engine.', + options=list(PlottingEngine.__members__.keys()), + default=config.plotting_engine, + more_info_url='https://docs.biosimulators.org/Biosimulators_tellurium/source/Biosimulators_tellurium.html', + ), +] App = build_cli('biosimulators-tellurium', __version__, 'tellurium', get_simulator_version(), 'http://tellurium.analogmachine.org', - exec_sedml_docs_in_combine_archive) + exec_sedml_docs_in_combine_archive, + environment_variables=environment_variables) def main(): diff --git a/biosimulators_tellurium/_version.py b/biosimulators_tellurium/_version.py index 112abf1..ac1125f 100644 --- a/biosimulators_tellurium/_version.py +++ b/biosimulators_tellurium/_version.py @@ -1 +1 @@ -__version__ = '0.1.14' +__version__ = '0.1.15' diff --git a/biosimulators_tellurium/config.py b/biosimulators_tellurium/config.py index 0de8069..0ac9502 100644 --- a/biosimulators_tellurium/config.py +++ b/biosimulators_tellurium/config.py @@ -6,7 +6,7 @@ :License: MIT """ -from .data_model import PlottingEngine +from .data_model import SedmlInterpreter, PlottingEngine import os __all__ = ['Config'] @@ -16,14 +16,23 @@ class Config(object): """ Configuration Attributes: + sedml_interpreter (:obj:`SedmlInterpreter`): SED-ML interpreter plotting_engine (:obj:`PlottingEngine`): plotting engine """ def __init__(self): - plotting_engine = os.getenv('PLOTTING_ENGINE', 'matplotlib') + sedml_interpreter = os.getenv('SEDML_INTERPRETER', SedmlInterpreter.biosimulators.name) + if sedml_interpreter not in SedmlInterpreter.__members__: + raise NotImplementedError(('`{}` is a not a supported SED-ML interpreter. ' + 'The following SED-ML interpreters are supported:\n - {}').format( + sedml_interpreter, '\n - '.join(sorted('`' + name + '`' for name in SedmlInterpreter.__members__.keys())))) + + self.sedml_interpreter = SedmlInterpreter[sedml_interpreter] + + plotting_engine = os.getenv('PLOTTING_ENGINE', PlottingEngine.matplotlib.name) if plotting_engine not in PlottingEngine.__members__: - raise NotImplementedError(('`{}` is a not a valid plotting engine for tellurium. ' - 'tellurium supports the following plotting engines:\n - {}').format( + raise NotImplementedError(('`{}` is a not a supported plotting engine. ' + 'The following plotting engines are supported:\n - {}').format( plotting_engine, '\n - '.join(sorted('`' + name + '`' for name in PlottingEngine.__members__.keys())))) self.plotting_engine = PlottingEngine[plotting_engine] diff --git a/biosimulators_tellurium/core.py b/biosimulators_tellurium/core.py index 6ffad74..83ecbed 100644 --- a/biosimulators_tellurium/core.py +++ b/biosimulators_tellurium/core.py @@ -7,33 +7,48 @@ """ from .config import Config +from .data_model import SedmlInterpreter, KISAO_ALGORITHM_MAP from biosimulators_utils.combine.exec import exec_sedml_docs_in_archive -from biosimulators_utils.log.data_model import Status, CombineArchiveLog, SedDocumentLog, StandardOutputErrorCapturerLevel # noqa: F401 +from biosimulators_utils.config import get_config as get_biosimulators_config +from biosimulators_utils.log.data_model import Status, CombineArchiveLog, SedDocumentLog, StandardOutputErrorCapturerLevel, TaskLog # noqa: F401 from biosimulators_utils.log.utils import init_sed_document_log, StandardOutputErrorCapturer from biosimulators_utils.viz.data_model import VizFormat # noqa: F401 -from biosimulators_utils.report.data_model import DataSetResults, ReportResults, ReportFormat, SedDocumentResults # noqa: F401 +from biosimulators_utils.report.data_model import DataSetResults, ReportResults, ReportFormat, SedDocumentResults, VariableResults # noqa: F401 from biosimulators_utils.report.io import ReportWriter -from biosimulators_utils.sedml.data_model import Task, Report, DataSet, Plot2D, Curve, Plot3D, Surface +from biosimulators_utils.sedml import exec as sedml_exec +from biosimulators_utils.sedml import validation +from biosimulators_utils.sedml.data_model import ( + Task, ModelLanguage, SteadyStateSimulation, UniformTimeCourseSimulation, + Symbol, Report, DataSet, Plot2D, Curve, Plot3D, Surface) from biosimulators_utils.sedml.io import SedmlSimulationReader, SedmlSimulationWriter +from biosimulators_utils.simulator.utils import get_algorithm_substitution_policy +from biosimulators_utils.utils.core import raise_errors_warnings, validate_str_value, parse_value +from biosimulators_utils.warnings import warn, BioSimulatorsWarning +from kisao.data_model import AlgorithmSubstitutionPolicy, ALGORITHM_SUBSTITUTION_POLICY_LEVELS +from kisao.utils import get_preferred_substitute_algorithm_by_ids from tellurium.sedml.tesedml import SEDMLCodeFactory import datetime +import functools import glob +import numpy import os import pandas import shutil import tellurium import tempfile import tellurium.sedml.tesedml +import roadrunner -# correct KISAO to parameter mapping -tellurium.sedml.tesedml.KISAOS_ALGORITHMPARAMETERS[656] = ('variable_step_size', bool) - - -__all__ = ['exec_sedml_docs_in_combine_archive'] +__all__ = [ + 'exec_sedml_docs_in_combine_archive', + 'exec_sed_doc', + 'exec_sed_task', +] def exec_sedml_docs_in_combine_archive(archive_filename, out_dir, + sedml_interpreter=None, return_results=False, report_formats=None, plot_formats=None, bundle_outputs=None, keep_individual_outputs=None, @@ -49,6 +64,7 @@ def exec_sedml_docs_in_combine_archive(archive_filename, out_dir, * HDF5: directory in which to save a single HDF5 file (``{ out_dir }/reports.h5``), with reports at keys ``{ relative-path-to-SED-ML-file-within-archive }/{ report.id }`` within the HDF5 file + sedml_interpreter (:obj:`SedmlInterpreter`, optional): SED-ML interpreter return_results (:obj:`bool`, optional): whether to return the result of each output of each SED-ML file report_formats (:obj:`list` of :obj:`ReportFormat`, optional): report format (e.g., csv or h5) plot_formats (:obj:`list` of :obj:`VizFormat`, optional): report format (e.g., pdf) @@ -62,9 +78,13 @@ def exec_sedml_docs_in_combine_archive(archive_filename, out_dir, * :obj:`SedDocumentResults`: results * :obj:`CombineArchiveLog`: log """ + if sedml_interpreter is None: + sedml_interpreter = Config().sedml_interpreter + return exec_sedml_docs_in_archive( - exec_sed_doc, archive_filename, out_dir, - apply_xml_model_changes=True, + functools.partial(exec_sed_doc, sedml_interpreter=sedml_interpreter), + archive_filename, out_dir, + apply_xml_model_changes=sedml_interpreter == SedmlInterpreter.biosimulators, return_results=return_results, sed_doc_executer_supported_features=(Task, Report, DataSet, Plot2D, Curve, Plot3D, Surface), report_formats=report_formats, @@ -76,22 +96,364 @@ def exec_sedml_docs_in_combine_archive(archive_filename, out_dir, ) -def exec_sed_doc(filename, working_dir, base_out_path, rel_out_path=None, - apply_xml_model_changes=True, return_results=True, report_formats=None, plot_formats=None, - log=None, indent=0, log_level=StandardOutputErrorCapturerLevel.c): +def exec_sed_doc(doc, working_dir, base_out_path, rel_out_path=None, + sedml_interpreter=None, + apply_xml_model_changes=False, return_results=False, report_formats=None, plot_formats=None, + log=None, indent=0, pretty_print_modified_xml_models=False, + log_level=StandardOutputErrorCapturerLevel.c): + """ Execute the tasks specified in a SED document and generate the specified outputs + + Args: + doc (:obj:`SedDocument` or :obj:`str`): SED document or a path to SED-ML file which defines a SED document + working_dir (:obj:`str`): working directory of the SED document (path relative to which models are located) + + base_out_path (:obj:`str`): path to store the outputs + + * CSV: directory in which to save outputs to files + ``{base_out_path}/{rel_out_path}/{report.id}.csv`` + * HDF5: directory in which to save a single HDF5 file (``{base_out_path}/reports.h5``), + with reports at keys ``{rel_out_path}/{report.id}`` within the HDF5 file + + rel_out_path (:obj:`str`, optional): path relative to :obj:`base_out_path` to store the outputs + sedml_interpreter (:obj:`SedmlInterpreter`, optional): SED-ML interpreter + apply_xml_model_changes (:obj:`bool`, optional): if :obj:`True`, apply any model changes specified in the SED-ML file before + calling :obj:`task_executer`. + return_results (:obj:`bool`, optional): whether to return a data structure with the result of each output of each SED-ML + file + report_formats (:obj:`list` of :obj:`ReportFormat`, optional): report format (e.g., csv or h5) + plot_formats (:obj:`list` of :obj:`VizFormat`, optional): plot format (e.g., pdf) + log (:obj:`SedDocumentLog`, optional): log of the document + indent (:obj:`int`, optional): degree to indent status messages + pretty_print_modified_xml_models (:obj:`bool`, optional): if :obj:`True`, pretty print modified XML models + log_level (:obj:`StandardOutputErrorCapturerLevel`, optional): level at which to log output + + Returns: + :obj:`tuple`: + + * :obj:`ReportResults`: results of each report + * :obj:`SedDocumentLog`: log of the document """ + if sedml_interpreter is None: + sedml_interpreter = Config().sedml_interpreter + + if sedml_interpreter == SedmlInterpreter.biosimulators: + return exec_sed_doc_with_biosimulators( + doc, working_dir, base_out_path, + rel_out_path=rel_out_path, + apply_xml_model_changes=apply_xml_model_changes, + return_results=return_results, + report_formats=report_formats, + plot_formats=plot_formats, + log=log, + indent=indent, + log_level=log_level) + + elif sedml_interpreter == SedmlInterpreter.tellurium: + return exec_sed_doc_with_tellurium( + doc, working_dir, base_out_path, + rel_out_path=rel_out_path, + apply_xml_model_changes=apply_xml_model_changes, + return_results=return_results, + report_formats=report_formats, + plot_formats=plot_formats, + log=log, + indent=indent, + log_level=log_level) + + else: + raise NotImplementedError('`{}` is not a supported SED-ML interpreter.'.format(sedml_interpreter)) + + +def exec_sed_doc_with_biosimulators(doc, working_dir, base_out_path, rel_out_path=None, + apply_xml_model_changes=False, return_results=False, report_formats=None, plot_formats=None, + log=None, indent=0, pretty_print_modified_xml_models=False, + log_level=StandardOutputErrorCapturerLevel.c): + """ Execute the tasks specified in a SED document and generate the specified outputs + Args: - filename (:obj:`str`): a path to SED-ML file which defines a SED document + doc (:obj:`SedDocument` or :obj:`str`): SED document or a path to SED-ML file which defines a SED document working_dir (:obj:`str`): working directory of the SED document (path relative to which models are located) + base_out_path (:obj:`str`): path to store the outputs + + * CSV: directory in which to save outputs to files + ``{base_out_path}/{rel_out_path}/{report.id}.csv`` + * HDF5: directory in which to save a single HDF5 file (``{base_out_path}/reports.h5``), + with reports at keys ``{rel_out_path}/{report.id}`` within the HDF5 file + + rel_out_path (:obj:`str`, optional): path relative to :obj:`base_out_path` to store the outputs + apply_xml_model_changes (:obj:`bool`, optional): if :obj:`True`, apply any model changes specified in the SED-ML file before + calling :obj:`task_executer`. + return_results (:obj:`bool`, optional): whether to return a data structure with the result of each output of each SED-ML + file + report_formats (:obj:`list` of :obj:`ReportFormat`, optional): report format (e.g., csv or h5) + plot_formats (:obj:`list` of :obj:`VizFormat`, optional): plot format (e.g., pdf) + log (:obj:`SedDocumentLog`, optional): log of the document + indent (:obj:`int`, optional): degree to indent status messages + pretty_print_modified_xml_models (:obj:`bool`, optional): if :obj:`True`, pretty print modified XML models + log_level (:obj:`StandardOutputErrorCapturerLevel`, optional): level at which to log output + + Returns: + :obj:`tuple`: + + * :obj:`ReportResults`: results of each report + * :obj:`SedDocumentLog`: log of the document + """ + sed_task_executer = functools.partial(exec_sed_task, sedml_interpreter=SedmlInterpreter.biosimulators) + return sedml_exec.exec_sed_doc(sed_task_executer, doc, working_dir, base_out_path, + rel_out_path=rel_out_path, + apply_xml_model_changes=True, + return_results=return_results, + report_formats=report_formats, + plot_formats=plot_formats, + log_level=log_level) - out_path (:obj:`str`): path to store the outputs + +def exec_sed_task(task, variables, log=None, sedml_interpreter=None): + ''' Execute a task and save its results + + Args: + task (:obj:`Task`): task + variables (:obj:`list` of :obj:`Variable`): variables that should be recorded + log (:obj:`TaskLog`, optional): log for the task + sedml_interpreter (:obj:`SedmlInterpreter`, optional): SED-ML interpreter + + Returns: + :obj:`tuple`: + + :obj:`VariableResults`: results of variables + :obj:`TaskLog`: log + + Raises: + :obj:`ValueError`: if the task or an aspect of the task is not valid, or the requested output variables + could not be recorded + :obj:`NotImplementedError`: if the task is not of a supported type or involves an unsuported feature + ''' + + if sedml_interpreter is None: + sedml_interpreter = Config().sedml_interpreter + + if sedml_interpreter != SedmlInterpreter.biosimulators: + raise NotImplementedError('`{}` is not a supported SED-ML interpreter.'.format(sedml_interpreter)) + + biosimulators_config = get_biosimulators_config() + + log = log or TaskLog() + + model = task.model + sim = task.simulation + + if biosimulators_config.VALIDATE_SEDML: + raise_errors_warnings(validation.validate_task(task), + error_summary='Task `{}` is invalid.'.format(task.id)) + raise_errors_warnings(validation.validate_model_language(model.language, ModelLanguage.SBML), + error_summary='Language for model `{}` is not supported.'.format(model.id)) + raise_errors_warnings(validation.validate_model_change_types(model.changes, ()), + error_summary='Changes for model `{}` are not supported.'.format(model.id)) + raise_errors_warnings(*validation.validate_model_changes(task.model), + error_summary='Changes for model `{}` are invalid.'.format(model.id)) + raise_errors_warnings(validation.validate_simulation_type(sim, (SteadyStateSimulation, UniformTimeCourseSimulation)), + error_summary='{} `{}` is not supported.'.format(sim.__class__.__name__, sim.id)) + raise_errors_warnings(*validation.validate_simulation(sim), + error_summary='Simulation `{}` is invalid.'.format(sim.id)) + raise_errors_warnings(*validation.validate_data_generator_variables(variables), + error_summary='Data generator variables for task `{}` are invalid.'.format(task.id)) + target_x_paths_to_sbml_ids = validation.validate_variable_xpaths(variables, model.source, attr='id') + + if biosimulators_config.VALIDATE_SEDML_MODELS: + raise_errors_warnings(*validation.validate_model(model, [], working_dir='.'), + error_summary='Model `{}` is invalid.'.format(model.id), + warning_summary='Model `{}` may be invalid.'.format(model.id)) + + # read model + rr = roadrunner.RoadRunner() + rr.load(model.source) + + # get algorithm to execute + algorithm_substitution_policy = get_algorithm_substitution_policy() + exec_alg_kisao_id = get_preferred_substitute_algorithm_by_ids( + sim.algorithm.kisao_id, KISAO_ALGORITHM_MAP.keys(), + substitution_policy=algorithm_substitution_policy) + alg_props = KISAO_ALGORITHM_MAP[exec_alg_kisao_id] + + if alg_props['id'] == 'nleq2': + integrator = rr.getSteadyStateSolver() + raise_errors_warnings(validation.validate_simulation_type(sim, (SteadyStateSimulation,)), + error_summary='{} `{}` is not supported.'.format(sim.__class__.__name__, sim.id)) + + else: + rr.setIntegrator(alg_props['id']) + integrator = rr.getIntegrator() + raise_errors_warnings(validation.validate_simulation_type(sim, (UniformTimeCourseSimulation,)), + error_summary='{} `{}` is not supported.'.format(sim.__class__.__name__, sim.id)) + + # set the parameters of the integrator + if exec_alg_kisao_id == sim.algorithm.kisao_id: + for change in sim.algorithm.changes: + param_props = alg_props['parameters'].get(change.kisao_id, None) + if param_props: + if validate_str_value(change.new_value, param_props['type']): + new_value = parse_value(change.new_value, param_props['type']) + setattr(integrator, param_props['id'], new_value) + + else: + if ( + ALGORITHM_SUBSTITUTION_POLICY_LEVELS[algorithm_substitution_policy] + <= ALGORITHM_SUBSTITUTION_POLICY_LEVELS[AlgorithmSubstitutionPolicy.NONE] + ): + msg = "'{}' is not a valid {} value for parameter {}".format( + change.new_value, param_props['type'].name, change.kisao_id) + raise ValueError(msg) + else: + msg = "'{}' was ignored because it is not a valid {} value for parameter {}".format( + change.new_value, param_props['type'].name, change.kisao_id) + warn(msg, BioSimulatorsWarning) + + else: + if ( + ALGORITHM_SUBSTITUTION_POLICY_LEVELS[algorithm_substitution_policy] + <= ALGORITHM_SUBSTITUTION_POLICY_LEVELS[AlgorithmSubstitutionPolicy.NONE] + ): + msg = "".join([ + "Algorithm parameter with KiSAO id '{}' is not supported. ".format(change.kisao_id), + "Parameter must have one of the following KiSAO ids:\n - {}".format('\n - '.join( + '{}: {} ({})'.format(kisao_id, param_props['id'], param_props['name']) + for kisao_id, param_props in alg_props['parameters'].items())), + ]) + raise NotImplementedError(msg) + else: + msg = "".join([ + "Algorithm parameter with KiSAO id '{}' was ignored because it is not supported. ".format(change.kisao_id), + "Parameter must have one of the following KiSAO ids:\n - {}".format('\n - '.join( + '{}: {} ({})'.format(kisao_id, param_props['id'], param_props['name']) + for kisao_id, param_props in alg_props['parameters'].items())), + ]) + warn(msg, BioSimulatorsWarning) + + # validate variables and setup observables + invalid_symbols = [] + invalid_targets = [] + + all_sbml_ids = rr.model.getAllTimeCourseComponentIds() + species_sbml_ids = rr.model.getBoundarySpeciesIds() + rr.model.getFloatingSpeciesIds() + observable_sbml_ids = [] + + for variable in variables: + if variable.symbol: + if variable.symbol == Symbol.time.value and isinstance(sim, UniformTimeCourseSimulation): + observable_sbml_ids.append('time') + else: + invalid_symbols.append(variable.symbol) + + else: + sbml_id = target_x_paths_to_sbml_ids.get(variable.target, None) + + if sbml_id and sbml_id in all_sbml_ids: + if exec_alg_kisao_id != 'KISAO_0000019' and sbml_id in species_sbml_ids: + observable_sbml_ids.append('[' + sbml_id + ']') + else: + observable_sbml_ids.append(sbml_id) + + else: + invalid_targets.append(variable.target) + + if invalid_symbols: + msg = ( + 'The following symbols are not supported:\n - {}' + '\n' + '\n' + 'Only following symbols are supported:\n - {}' + ).format( + '\n - '.join(sorted(invalid_symbols)), + '\n - '.join(sorted([Symbol.time.value])), + ) + raise NotImplementedError(msg) + + if invalid_targets: + valid_targets = [] + for species_sbml_id in species_sbml_ids: + valid_targets.append("/sbml:sbml/sbml:model/sbml:listOfSpecies/sbml:species[@id='{}']".format(species_sbml_id)) + for rxn_id in rr.model.getReactionIds(): + valid_targets.append("/sbml:sbml/sbml:model/sbml:listOfReactions/sbml:reaction[@id='{}']".format(rxn_id)) + + msg = ( + 'The following targets are not supported:\n - {}' + '\n' + '\n' + 'Only following targets are supported:\n - {}' + ).format( + '\n - '.join(sorted(invalid_targets)), + '\n - '.join(sorted(valid_targets)), + ) + raise ValueError(msg) + + # simulate + if isinstance(sim, UniformTimeCourseSimulation): + number_of_points = (sim.output_end_time - sim.initial_time) / \ + (sim.output_end_time - sim.output_start_time) * sim.number_of_steps + 1 + if abs(number_of_points % 1) > 1e-8: + msg = ( + 'The number of simulation points `{}` must be an integer:' + '\n Initial time: {}' + '\n Output start time: {}' + '\n Output end time: {}' + '\n Number of points: {}' + ).format(number_of_points, sim.initial_time, sim.output_start_time, sim.output_start_time, sim.number_of_points) + raise NotImplementedError(msg) + + number_of_points = round(number_of_points) + rr.timeCourseSelections = observable_sbml_ids + results = numpy.array(rr.simulate(sim.initial_time, sim.output_end_time, number_of_points).tolist()).transpose() + else: + rr.steadyStateSelections = observable_sbml_ids + rr.steadyState() + results = rr.getSteadyStateValues() + + # check simulation succeeded + if numpy.any(numpy.isnan(results)): + msg = 'Simulation failed with algorithm `{}` ({})'.format(exec_alg_kisao_id, alg_props['id']) + for i_param in range(integrator.getNumParams()): + param_name = integrator.getParamName(i_param) + msg += '\n - {}: {}'.format(param_name, getattr(integrator, param_name)) + raise ValueError(msg) + + # record results + variable_results = VariableResults() + for variable, result in zip(variables, results): + if isinstance(sim, UniformTimeCourseSimulation): + result = result[-(sim.number_of_points + 1):] + + variable_results[variable.id] = result + + # log action + log.algorithm = exec_alg_kisao_id + log.simulator_details = { + 'method': 'simulate' if isinstance(sim, UniformTimeCourseSimulation) else 'steadyState', + 'integrator': integrator.getName(), + } + for i_param in range(integrator.getNumParams()): + param_name = integrator.getParamName(i_param) + log.simulator_details[param_name] = getattr(integrator, param_name) + + # return results and log + return variable_results, log + + +def exec_sed_doc_with_tellurium(doc, working_dir, base_out_path, rel_out_path=None, + apply_xml_model_changes=True, return_results=True, report_formats=None, plot_formats=None, + log=None, indent=0, pretty_print_modified_xml_models=False, + log_level=StandardOutputErrorCapturerLevel.c): + """ + Args: + doc (:obj:`SedDocument` or :obj:`str`): SED document or a path to SED-ML file which defines a SED document + working_dir (:obj:`str`): working directory of the SED document (path relative to which models are located) + base_out_path (:obj:`str`): path to store the outputs * CSV: directory in which to save outputs to files - ``{out_path}/{rel_out_path}/{report.id}.csv`` - * HDF5: directory in which to save a single HDF5 file (``{out_path}/reports.h5``), + ``{base_out_path}/{rel_out_path}/{report.id}.csv`` + * HDF5: directory in which to save a single HDF5 file (``{base_out_path}/reports.h5``), with reports at keys ``{rel_out_path}/{report.id}`` within the HDF5 file - rel_out_path (:obj:`str`, optional): path relative to :obj:`out_path` to store the outputs + rel_out_path (:obj:`str`, optional): path relative to :obj:`base_out_path` to store the outputs apply_xml_model_changes (:obj:`bool`, optional): if :obj:`True`, apply any model changes specified in the SED-ML file before calling :obj:`task_executer`. return_results (:obj:`bool`, optional): whether to return the result of each output of each SED-ML file @@ -99,6 +461,7 @@ def exec_sed_doc(filename, working_dir, base_out_path, rel_out_path=None, plot_formats (:obj:`list` of :obj:`VizFormat`, optional): plot format (e.g., pdf) log (:obj:`SedDocumentLog`, optional): execution status of document indent (:obj:`int`, optional): degree to indent status messages + pretty_print_modified_xml_models (:obj:`bool`, optional): if :obj:`True`, pretty print modified XML models log_level (:obj:`StandardOutputErrorCapturerLevel`, optional): level at which to log output Returns: @@ -107,7 +470,8 @@ def exec_sed_doc(filename, working_dir, base_out_path, rel_out_path=None, * :obj:`ReportResults`: results of each report * :obj:`SedDocumentLog`: log of the document """ - doc = SedmlSimulationReader().run(filename) + if isinstance(doc, str): + doc = SedmlSimulationReader().run(doc) if not log: log = init_sed_document_log(doc) diff --git a/biosimulators_tellurium/data_model.py b/biosimulators_tellurium/data_model.py index 2155443..eaeb16a 100644 --- a/biosimulators_tellurium/data_model.py +++ b/biosimulators_tellurium/data_model.py @@ -1,19 +1,263 @@ -""" Data model for working with tellurium to execute COMBINE/OMEX archives +""" Data model for using tellurium to execute SED-ML documents :Author: Jonathan Karr -:Date: 2021-01-04 +:Date: 2021-08-23 :Copyright: 2021, Center for Reproducible Biomedical Modeling :License: MIT """ +from biosimulators_utils.data_model import ValueType +import collections import enum __all__ = [ + 'SedmlInterpreter', 'PlottingEngine', + 'KISAO_ALGORITHM_MAP', ] +class SedmlInterpreter(str, enum.Enum): + """ Code that interprets SED-ML """ + biosimulators = 'biosimulators' # biosimulators_utils.sedml.exec + tellurium = 'tellurium' # tellurium.sedml.tesedml import SEDMLCodeFactory + + class PlottingEngine(str, enum.Enum): """ Engine that tellurium uses for plottting """ matplotlib = 'matplotlib' plotly = 'plotly' + + +KISAO_ALGORITHM_MAP = collections.OrderedDict([ + ('KISAO_0000019', { + 'kisao_id': 'KISAO_0000019', + 'id': 'cvode', + 'name': "CVODE", + 'parameters': { + 'KISAO_0000209': { + 'kisao_id': 'KISAO_0000209', + 'id': 'relative_tolerance', + 'name': 'relative tolerance', + 'type': ValueType.float, + 'default': 0.000001, + }, + 'KISAO_0000211': { + 'kisao_id': 'KISAO_0000211', + 'id': 'absolute_tolerance', + 'name': 'absolute tolerance', + 'type': ValueType.float, + 'default': 1e-12, + }, + 'KISAO_0000220': { + 'kisao_id': 'KISAO_0000220', + 'id': 'maximum_bdf_order', + 'name': 'Maximum Backward Differentiation Formula (BDF) order', + 'type': ValueType.integer, + 'default': 5, + }, + 'KISAO_0000219': { + 'kisao_id': 'KISAO_0000219', + 'id': 'maximum_adams_order', + 'name': 'Maximum Adams order', + 'type': ValueType.integer, + 'default': 12, + }, + 'KISAO_0000415': { + 'kisao_id': 'KISAO_0000415', + 'id': 'maximum_num_steps', + 'name': 'Maximum number of steps', + 'type': ValueType.integer, + 'default': 20000, + }, + 'KISAO_0000467': { + 'kisao_id': 'KISAO_0000467', + 'id': 'maximum_time_step', + 'name': 'Maximum time step', + 'type': ValueType.float, + 'default': None, + }, + 'KISAO_0000485': { + 'kisao_id': 'KISAO_0000485', + 'id': 'minimum_time_step', + 'name': 'Minimum time step', + 'type': ValueType.float, + 'default': None, + }, + 'KISAO_0000332': { + 'kisao_id': 'KISAO_0000332', + 'id': 'initial_time_step', + 'name': 'Initial time step', + 'type': ValueType.float, + 'default': None, + }, + 'KISAO_0000671': { + 'kisao_id': 'KISAO_0000671', + 'id': 'stiff', + 'name': 'Stiff', + 'type': ValueType.boolean, + 'default': True, + }, + 'KISAO_0000670': { + 'kisao_id': 'KISAO_0000670', + 'id': 'multiple_steps', + 'name': 'Multiple steps', + 'type': ValueType.boolean, + 'default': False, + }, + }, + }), + ('KISAO_0000030', { + 'kisao_id': 'KISAO_0000030', + 'id': 'euler', + 'name': "Forward Euler method", + 'parameters': {} + }), + ('KISAO_0000032', { + 'kisao_id': 'KISAO_0000032', + 'id': 'rk4', + 'name': "Runge-Kutta fourth order method", + 'parameters': {} + }), + ('KISAO_0000086', { + 'kisao_id': 'KISAO_0000086', + 'id': 'rk45', + 'name': "Fehlberg method", + 'parameters': { + 'KISAO_0000467': { + 'kisao_id': 'KISAO_0000467', + 'id': 'maximum_time_step', + 'name': 'Maximum time step', + 'type': ValueType.float, + 'default': 1.0, + }, + 'KISAO_0000485': { + 'kisao_id': 'KISAO_0000485', + 'id': 'minimum_time_step', + 'name': 'Minimum time step', + 'type': ValueType.float, + 'default': 1e-12, + }, + 'KISAO_0000597': { + 'kisao_id': 'KISAO_0000597', + 'id': 'epsilon', + 'name': 'Epsilon', + 'type': ValueType.float, + 'default': 0.000000000001, + }, + } + }), + ('KISAO_0000029', { + 'kisao_id': 'KISAO_0000029', + 'id': 'gillespie', + 'name': "Gillespie direct method of the Stochastic Simulation Algorithm (SSA)", + 'parameters': { + 'KISAO_0000488': { + 'kisao_id': 'KISAO_0000488', + 'id': 'seed', + 'name': 'Random number generator seed', + 'type': ValueType.integer, + 'default': None, + }, + 'KISAO_0000673': { + 'kisao_id': 'KISAO_0000673', + 'id': 'nonnegative', + 'name': 'Skip reactions which would result in negative species amounts', + 'type': ValueType.boolean, + 'default': False, + }, + } + }), + ('KISAO_0000408', { + 'kisao_id': 'KISAO_0000408', + 'id': 'nleq2', + 'name': "Newton-type method for solveing non-linear (NL) equations (EQ)", + 'parameters': { + 'KISAO_0000209': { + 'kisao_id': 'KISAO_0000209', + 'id': 'relative_tolerance', + 'name': 'Relative tolerance', + 'type': ValueType.float, + 'default': 1e-12, + }, + 'KISAO_0000486': { + 'kisao_id': 'KISAO_0000486', + 'id': 'maximum_iterations', + 'name': 'Maximum number of iterations', + 'type': ValueType.integer, + 'default': 100, + }, + 'KISAO_0000487': { + 'kisao_id': 'KISAO_0000487', + 'id': 'minimum_damping', + 'name': 'Minimum damping factor', + 'type': ValueType.float, + 'default': 1e-20, + }, + 'KISAO_0000674': { + 'kisao_id': 'KISAO_0000674', + 'id': 'allow_presimulation', + 'name': 'Presimulate', + 'type': ValueType.boolean, + 'default': False, + }, + 'KISAO_0000675': { + 'kisao_id': 'KISAO_0000675', + 'id': 'broyden_method', + 'name': 'Broyden method', + 'type': ValueType.integer, + 'default': 0, + }, + 'KISAO_0000676': { + 'kisao_id': 'KISAO_0000676', + 'id': 'linearity', + 'name': 'Degree of linearity', + 'type': ValueType.integer, + 'default': 3, + }, + 'KISAO_0000677': { + 'kisao_id': 'KISAO_0000677', + 'id': 'presimulation_maximum_steps', + 'name': 'Maximum number of steps for presimulation', + 'type': ValueType.integer, + 'default': 100, + }, + 'KISAO_0000678': { + 'kisao_id': 'KISAO_0000678', + 'id': 'approx_maximum_steps', + 'name': 'Maximum number of steps for approximation', + 'type': ValueType.integer, + 'default': 10000, + }, + 'KISAO_0000679': { + 'kisao_id': 'KISAO_0000679', + 'id': 'approx_time', + 'name': 'Maximum amount of time for approximation', + 'type': ValueType.float, + 'default': 10000, + }, + 'KISAO_0000680': { + 'kisao_id': 'KISAO_0000680', + 'id': 'presimulation_time', + 'name': 'Amount of time to presimulate', + 'type': ValueType.float, + 'default': 100, + }, + + 'KISAO_0000682': { + 'kisao_id': 'KISAO_0000682', + 'id': 'allow_approx', + 'name': 'Whether to find an approximate solution if an exact solution could not be found', + 'type': ValueType.boolean, + 'default': False, + }, + 'KISAO_0000683': { + 'kisao_id': 'KISAO_0000683', + 'id': 'approx_tolerance', + 'name': 'Relative tolerance for an approximate solution', + 'type': ValueType.float, + 'default': 0.000001, + }, + } + }), +]) diff --git a/requirements.txt b/requirements.txt index f3b8c80..1386238 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,6 @@ -biosimulators_utils[logging] >= 0.1.104 +biosimulators_utils[logging] >= 0.1.108 +kisao >= 2.27 +libroadrunner +numpy pandas tellurium diff --git a/tests/fixtures/BIOMD0000000003_url.xml b/tests/fixtures/BIOMD0000000003_url.xml new file mode 100644 index 0000000..b01340a --- /dev/null +++ b/tests/fixtures/BIOMD0000000003_url.xml @@ -0,0 +1,469 @@ + + + + + +
Goldbeter1991 - Min Mit Oscil
+
+

Minimal cascade model for the mitotic oscillator involving cyclin and cdc2 kinase.

+
+
+

This model has been generated by MathSBML 2.4.6 (14-January-2005) 14-January-2005 18:33:39.806932.

+
+
+

This model is described in the article:

+ +
Goldbeter A.
+
Proc. Natl. Acad. Sci. U.S.A. 1991; 88(20):9107-11
+

Abstract:

+
+

A minimal model for the mitotic oscillator is presented. The model, built on recent experimental advances, is based on the cascade of post-translational modification that modulates the activity of cdc2 kinase during the cell cycle. The model pertains to the situation encountered in early amphibian embryos, where the accumulation of cyclin suffices to trigger the onset of mitosis. In the first cycle of the bicyclic cascade model, cyclin promotes the activation of cdc2 kinase through reversible dephosphorylation, and in the second cycle, cdc2 kinase activates a cyclin protease by reversible phosphorylation. That cyclin activates cdc2 kinase while the kinase triggers the degradation of cyclin has suggested that oscillations may originate from such a negative feedback loop [Félix, M. A., Labbé, J. C., Dorée, M., Hunt, T. & Karsenti, E. (1990) Nature (London) 346, 379-382]. This conjecture is corroborated by the model, which indicates that sustained oscillations of the limit cycle type can arise in the cascade, provided that a threshold exists in the activation of cdc2 kinase by cyclin and in the activation of cyclin proteolysis by cdc2 kinase. The analysis shows how miototic oscillations may readily arise from time lags associated with these thresholds and from the delayed negative feedback provided by cdc2-induced cyclin degradation. A mechanism for the origin of the thresholds is proposed in terms of the phenomenon of zero-order ultrasensitivity previously described for biochemical systems regulated by covalent modification.

+
+
+
+

This model is hosted on BioModels Database + and identified by: BIOMD0000000003 + .

+

To cite BioModels Database, please use: BioModels Database: An enhanced, curated and annotated resource for published quantitative kinetic models + .

+
+
+

To the extent possible under law, all copyright and related or neighbouring rights to this encoded model have been dedicated to the public domain worldwide. Please refer to CC0 Public Domain Dedication + for more information.

+
+ +
+ + + + + + + + Shapiro + Bruce + + bshapiro@jpl.nasa.gov + + NASA Jet Propulsion Laboratory + + + + + Chelliah + Vijayalakshmi + + viji@ebi.ac.uk + + EMBL-EBI + + + + + + 2005-02-06T23:39:40Z + + + 2013-05-16T14:38:01Z + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + C + VM1 + + + + + C + Kc + + -1 + + + + + + + + + M + VM3 + + + + + + + + + + + + + + + + + + + + + + + + + cell + vi + + + + + + + + + + + + + + + + + + + + + + + + + + + C + cell + kd + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + C + cell + vd + X + + + + + C + Kd + + -1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + cell + + + 1 + + + -1 + M + + + V1 + + + + + K1 + + + -1 + M + + 1 + + -1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + cell + M + V2 + + + + + K2 + M + + -1 + + + + + + + + + + + + + + + + + + cell + V3 + + + 1 + + + -1 + X + + + + + + + K3 + + + -1 + X + + 1 + + -1 + + + + + + + + + + + + + + + + + cell + V4 + X + + + + + K4 + X + + -1 + + + + + + + + + + +
+
diff --git a/tests/fixtures/BIOMD0000000297.xml b/tests/fixtures/BIOMD0000000297.xml new file mode 100644 index 0000000..bb59f20 --- /dev/null +++ b/tests/fixtures/BIOMD0000000297.xml @@ -0,0 +1,2404 @@ + + + + + +

This a model from the article:
+ Mathematical model of the morphogenesis checkpoint in budding yeast. +
+ Ciliberto A, Novak B, Tyson JJ J. Cell Biol. + [2003 Dec; Volume: 163 (Issue: 6 )] Page info: 1243-54 14691135 + ,
+ Abstract: +
+ The morphogenesis checkpoint in budding yeast delays progression through the cell cycle in response to stimuli that prevent bud formation. Central to the checkpoint mechanism is Swe1 kinase: normally inactive, its activation halts cell cycle progression in G2. We propose a molecular network for Swe1 control, based on published observations of budding yeast and analogous control signals in fission yeast. The proposed Swe1 network is merged with a model of cyclin-dependent kinase regulation, converted into a set of differential equations and studied by numerical simulation. The simulations accurately reproduce the phenotypes of a dozen checkpoint mutants. Among other predictions, the model attributes a new role to Hsl1, a kinase known to play a role in Swe1 degradation: Hsl1 must also be indirectly responsible for potent inhibition of Swe1 activity. The model supports the idea that the morphogenesis checkpoint, like other checkpoints, raises the cell size threshold for progression from one phase of the cell cycle to the next.

+

+ The model reproduces Fig 3 of the paper.

+

+ This model originates from BioModels Database: A Database of Annotated Published Models (http://www.ebi.ac.uk/biomodels/). It is copyright (c) 2005-2011 The BioModels.net Team.
+ For more information see the terms of use + .
+ To cite BioModels Database, please use: Li C, Donizelli M, Rodriguez N, Dharuri H, Endler L, Chelliah V, Li L, He E, Henry A, Stefan MI, Snoep JL, Hucka M, Le Novère N, Laibe C (2010) BioModels Database: An enhanced, curated and annotated resource for published quantitative kinetic models. BMC Syst Biol., 4:92. +

+ + +
+ + + + + + + + Dharuri + Harish + + hdharuri@cds.caltech.edu + + California Institute of Technology + + + + + Chelliah + Vijayalakshmi + + viji@ebi.ac.uk + + EMBL-EBI + + + + + + 2008-02-07T14:15:40Z + + + 2012-01-31T13:52:48Z + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + kswe_prime + Swe1 + + + + kswe_doubleprime + Swe1M + + + + kswe_tripleprime + PSwe1 + + + + + + + + + + + kmih_prime + Mih_ast + + + + kmih_doubleprime + Mih + + + + + + + + + Swe1 + Swe1M + PSwe1 + PSwe1M + + + + + + + + IEtot + IE + + + + + + + + Cdh1tot + Cdh1 + + + + + + + + Mih1tot + Mih1a + + + + + + + + Mcmtot + Mcm + + + + + + + + SBFtot + SBF + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + kdiss + Trim + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Trim + + + + + kdsic_prime + Clg + + + + kdsic_doubleprime + Clb + + kdsic + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Trim + + + + + kdclb_doubleprime + Cdh1 + + + + kdclb_tripleprime + Cdc20a + + kdclb_prime + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Trim + kswe + + + + + + + + + + + + + + + + + + + + + + + + + + + + + kass + Sic + Clb + + + + + + + + + + + + + + + + + + + + + + + + + + + + PTrim + kmih + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Clb + + + + + kdclb_doubleprime + Cdh1 + + + + kdclb_tripleprime + Cdc20a + + kdclb_prime + + + + + + + + + + + + + + + + + + + + + + + + + + + + + kswe + Clb + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ksclb + mass + Jm + + + eps + Mcm + + + + + mass + Jm + + + + + + + + + + + + + + + + + + + + + + + + + + + + + kmih + PClb + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + SBF + + + kisbf_prime + + + kisbf_doubleprime + Clb + + + + + + jisbf + SBF + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + SBFin + + + + + kasbf_prime + mass + + + + kasbf_doubleprime + Clg + + + + + + jasbf + SBFin + + + + + + + + + + + + + + + + + + + + + + + + + + + + IE + kiie + + + + jiie + IE + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + kaie + IEin + Clb + + + + jaie + IEin + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Cdc20a + kicdc20 + + + + jicdc20 + Cdc20a + + + + + + + + + + + + + + + + + + + + + + + + + + kdcdc20 + Cdc20a + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + kacdc20 + Cdc20 + IE + + + + jacdc20 + Cdc20 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Cdh1 + + + + + kicdh + Clb + + + + kicdh_prime + Clg + + + + + + jicdh + Cdh1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Cdh1in + + + Kacdh_prime + + + Kacdh_doubleprime + Cdc20a + + + + + + jacdh + Cdh1in + + + + + + + + + + + + + + + + + + + + + + + + + + + + + khsl1 + BUD + Swe1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + khsl1 + BUD + PSwe1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Viwee + Swe1 + Clb + + + + Jiwee + Swe1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Viwee + Swe1M + Clb + + + + Jiwee + Swe1M + + + + + + + + + + + + + + + + + + + + + + + + + + kdswe_prime + Swe1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + khsl1r + Swe1M + + + + + + + + + + + + + + + + + + + + + + + + + + + + khsl1r + PSwe1M + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + PSwe1 + Vawee + + + + Jawee + PSwe1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + PSwe1M + Vawee + + + + Jawee + PSwe1M + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ksswe + SBF + + + + + + + + + + + + + + + + + + + + + + + kssweC + + + + + + + + + + + + + + + + + + + + + + + + kdswe_prime + PSwe1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + kdiss + PTrim + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + PTrim + + + + + kdclb_doubleprime + Cdh1 + + + + kdclb_tripleprime + Cdc20a + + kdclb_prime + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + PTrim + + + + + kdsic_prime + Clg + + + + kdsic_doubleprime + Clb + + kdsic + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + kass + PClb + Sic + + + + + + + + + + + + + + + + + + + + + + + + + + + Mih1a + Vimih + + + + jimih + Mih1a + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Vamih + Mih1 + Clb + + + + Jamih + Mih1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + Mcm + kimcm + + + + jimcm + Mcm + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Mcmin + Clb + kamcm + + + + jamcm + Mcmin + + + + + + + + + + + + + + + + + + + + + + + + + + kdbud + BE + + + + + + + + + + + + + + + + ksbud + Clg + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Sic + + + + + kdsic_prime + Clg + + + + kdsic_doubleprime + Clb + + kdsic + + + + + + + + + + + + + + + + + + + + + + + + kssic + + + + + + + + + + + + + + + + + + + + + + + + kdclg + Clg + + + + + + + + + + + + + + + + + + + + + + + + + + + + ksclg + SBF + + + + + + + + + + + + + + + + + + + + + + + + + kdswe_prime + Swe1M + + + + + + + + + + + + + + + + + + + + + + + + + kdcdc20 + Cdc20 + + + + + + + + + + + + + + + + + + + + + + + + + + + + kscdc20_prime + + + + + kscdc20_doubleprime + + + Clb + 4 + + + + + + + jscdc20 + 4 + + + + Clb + 4 + + + + + + + + + + + + + + + + + + + + + + + + + + + + kdswe_doubleprime + PSwe1M + + + + + + + + + + + + + + + + + + + + + + + + + + + + + PClb + + + + + kdclb_doubleprime + Cdh1 + + + + kdclb_tripleprime + Cdc20a + + kdclb_prime + + + + + + + + + + + + + + + + + + + + + + + + + + mu + mass + + + + + + + + + + + + Clb + 0.2 + + + + + + + 1 + + + + + + + + + + + + Clb + 0.2 + + + + flag + 0 + + + + + + + + 0 + + + + + + + 0.5 + mass + + + + + + + + + + + + + Clb + 0.2 + + + + BE + 0.6 + + + + + + + + 1 + + + + + + + + + + + + Clb + 0.2 + + + + BE + 0.6 + + + + + + + + 0 + + + + + +
+
\ No newline at end of file diff --git a/tests/requirements.txt b/tests/requirements.txt index 973fa84..d0e2aa7 100644 --- a/tests/requirements.txt +++ b/tests/requirements.txt @@ -1,3 +1,4 @@ biosimulators_utils[containers] numpy pypdf2 +pyyaml diff --git a/tests/test_config.py b/tests/test_config.py index 6015cb5..126c01e 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -1,5 +1,5 @@ from biosimulators_tellurium.config import Config -from biosimulators_tellurium.data_model import PlottingEngine +from biosimulators_tellurium.data_model import SedmlInterpreter, PlottingEngine from unittest import mock import os import unittest @@ -7,6 +7,18 @@ class ConfigTestCase(unittest.TestCase): def test_Config(self): + # SED-ML interpeter + with mock.patch.dict(os.environ, {'SEDML_INTERPRETER': SedmlInterpreter.biosimulators.value}): + self.assertEqual(Config().sedml_interpreter, SedmlInterpreter.biosimulators) + + with mock.patch.dict(os.environ, {'SEDML_INTERPRETER': SedmlInterpreter.tellurium.value}): + self.assertEqual(Config().sedml_interpreter, SedmlInterpreter.tellurium) + + with mock.patch.dict(os.environ, {'SEDML_INTERPRETER': 'unsupported'}): + with self.assertRaises(NotImplementedError): + Config() + + # plotting engine with mock.patch.dict(os.environ, {'PLOTTING_ENGINE': PlottingEngine.matplotlib.value}): self.assertEqual(Config().plotting_engine, PlottingEngine.matplotlib) @@ -15,4 +27,4 @@ def test_Config(self): with mock.patch.dict(os.environ, {'PLOTTING_ENGINE': 'unsupported'}): with self.assertRaises(NotImplementedError): - self.assertEqual(Config().plotting_engine, PlottingEngine.plotly) + Config() diff --git a/tests/test_core_main.py b/tests/test_core_main.py index f943d70..0a0f2d5 100644 --- a/tests/test_core_main.py +++ b/tests/test_core_main.py @@ -8,21 +8,43 @@ from biosimulators_tellurium import __main__ from biosimulators_tellurium import core +from biosimulators_tellurium.data_model import SedmlInterpreter from biosimulators_utils.archive.io import ArchiveReader +from biosimulators_utils.combine import data_model as combine_data_model +from biosimulators_utils.combine.io import CombineArchiveWriter +from biosimulators_utils.config import get_config from biosimulators_utils.report.io import ReportReader +from biosimulators_utils.report import data_model as report_data_model +from biosimulators_utils.sedml import data_model as sedml_data_model from biosimulators_utils.sedml.data_model import Report, DataSet +from biosimulators_utils.sedml.io import SedmlSimulationWriter +from biosimulators_utils.sedml.utils import append_all_nested_children_to_doc from biosimulators_utils.simulator.exec import exec_sedml_docs_in_archive_with_containerized_simulator +from biosimulators_utils.simulator.specs import gen_algorithms_from_specs +from biosimulators_utils.warnings import BioSimulatorsWarning +from kisao.exceptions import AlgorithmCannotBeSubstitutedException +from kisao.warnings import AlgorithmSubstitutedWarning from unittest import mock +import copy +import json import numpy +import numpy.testing import os import PyPDF2 import shutil import tellurium.sedml.tesedml import tempfile import unittest +import yaml -class CliTestCase(unittest.TestCase): +class CoreTestCase(unittest.TestCase): + FIXTURES_DIRNAME = os.path.join(os.path.dirname(__file__), 'fixtures') + EXAMPLE_MODEL_FILENAME = os.path.join(os.path.dirname(__file__), 'fixtures', 'BIOMD0000000003_url.xml') + NAMESPACES = { + 'sbml': 'http://www.sbml.org/sbml/level2/version4', + } + SPECIFICATIONS_FILENAME = os.path.join(os.path.dirname(__file__), '..', 'biosimulators.json') DOCKER_IMAGE = 'ghcr.io/biosimulators/biosimulators_tellurium/tellurium:latest' def setUp(self): @@ -31,38 +53,573 @@ def setUp(self): def tearDown(self): shutil.rmtree(self.dirname) + # BioSimulators SED-ML interpreter + def test_exec_sed_task_successfully_with_biosimulators(self): + # configure simulation + task = sedml_data_model.Task( + model=sedml_data_model.Model( + source=self.EXAMPLE_MODEL_FILENAME, + language=sedml_data_model.ModelLanguage.SBML.value, + ), + simulation=sedml_data_model.UniformTimeCourseSimulation( + initial_time=0., + output_start_time=0., + output_end_time=10., + number_of_points=10, + algorithm=sedml_data_model.Algorithm( + kisao_id='KISAO_0000019', + changes=[ + sedml_data_model.AlgorithmParameterChange( + kisao_id='KISAO_0000209', + new_value='1e-8', + ) + ] + ), + ), + ) + + variables = [ + sedml_data_model.Variable( + id='Time', + symbol=sedml_data_model.Symbol.time, + task=task), + sedml_data_model.Variable( + id='C', + target="/sbml:sbml/sbml:model/sbml:listOfSpecies/sbml:species[@id='C']", + target_namespaces=self.NAMESPACES, + task=task), + sedml_data_model.Variable( + id='V1', + target="/sbml:sbml/sbml:model/sbml:listOfParameters/sbml:parameter[@id='V1']", + target_namespaces=self.NAMESPACES, + task=task), + sedml_data_model.Variable( + id='VM1', + target="/sbml:sbml/sbml:model/sbml:listOfParameters/sbml:parameter[@id='VM1']", + target_namespaces=self.NAMESPACES, + task=task), + sedml_data_model.Variable( + id='reaction1', + target="/sbml:sbml/sbml:model/sbml:listOfReactions/sbml:reaction[@id='reaction1']", + target_namespaces=self.NAMESPACES, + task=task), + sedml_data_model.Variable( + id='cell', + target="/sbml:sbml/sbml:model/sbml:listOfCompartments/sbml:compartment[@id='cell']", + target_namespaces=self.NAMESPACES, + task=task), + ] + + # execute simulation + variable_results, log = core.exec_sed_task(task, variables) + + # check that the simulation was executed correctly + self.assertEqual(set(variable_results.keys()), set(['Time', 'C', 'V1', 'VM1', 'reaction1', 'cell'])) + for variable_result in variable_results.values(): + self.assertFalse(numpy.any(numpy.isnan(variable_result))) + numpy.testing.assert_allclose( + variable_results['Time'], + numpy.linspace( + task.simulation.output_start_time, + task.simulation.output_end_time, + task.simulation.number_of_points + 1, + )) + numpy.testing.assert_allclose( + variable_results['VM1'], + numpy.full((task.simulation.number_of_points + 1,), 3.)) + numpy.testing.assert_allclose( + variable_results['cell'], + numpy.full((task.simulation.number_of_points + 1,), 1.)) + + # check that log can be serialized to JSON + self.assertEqual(log.algorithm, 'KISAO_0000019') + self.assertEqual(log.simulator_details['integrator'], 'cvode') + self.assertEqual(log.simulator_details['relative_tolerance'], 1e-8) + + json.dumps(log.to_json()) + + log.out_dir = self.dirname + log.export() + with open(os.path.join(self.dirname, get_config().LOG_PATH), 'rb') as file: + log_data = yaml.load(file, Loader=yaml.Loader) + json.dumps(log_data) + + def test_exec_sed_task_positive_initial_time_with_biosimulators(self): + # configure simulation + task = sedml_data_model.Task( + model=sedml_data_model.Model( + source=self.EXAMPLE_MODEL_FILENAME, + language=sedml_data_model.ModelLanguage.SBML.value, + ), + simulation=sedml_data_model.UniformTimeCourseSimulation( + initial_time=10., + output_start_time=10., + output_end_time=20., + number_of_points=10, + algorithm=sedml_data_model.Algorithm( + kisao_id='KISAO_0000019', + ), + ), + ) + + variables = [ + sedml_data_model.Variable( + id='Time', + symbol=sedml_data_model.Symbol.time, + task=task), + sedml_data_model.Variable( + id='C', + target="/sbml:sbml/sbml:model/sbml:listOfSpecies/sbml:species[@id='C']", + target_namespaces=self.NAMESPACES, + task=task), + ] + + # execute simulation + variable_results, log = core.exec_sed_task(task, variables) + + # check that the simulation was executed correctly + self.assertEqual(set(variable_results.keys()), set(['Time', 'C'])) + for variable_result in variable_results.values(): + self.assertFalse(numpy.any(numpy.isnan(variable_result))) + numpy.testing.assert_allclose( + variable_results['Time'], + numpy.linspace( + task.simulation.output_start_time, + task.simulation.output_end_time, + task.simulation.number_of_points + 1, + )) + + @unittest.expectedFailure + def test_exec_sed_task_negative_initial_time_with_biosimulators(self): + # configure simulation + task = sedml_data_model.Task( + model=sedml_data_model.Model( + source=self.EXAMPLE_MODEL_FILENAME, + language=sedml_data_model.ModelLanguage.SBML.value, + ), + simulation=sedml_data_model.UniformTimeCourseSimulation( + initial_time=-10., + output_start_time=-10., + output_end_time=10., + number_of_points=10, + algorithm=sedml_data_model.Algorithm( + kisao_id='KISAO_0000019', + ), + ), + ) + + variables = [ + sedml_data_model.Variable( + id='Time', + symbol=sedml_data_model.Symbol.time, + task=task), + sedml_data_model.Variable( + id='C', + target="/sbml:sbml/sbml:model/sbml:listOfSpecies/sbml:species[@id='C']", + target_namespaces=self.NAMESPACES, + task=task), + ] + + # execute simulation + variable_results, log = core.exec_sed_task(task, variables) + + # check that the simulation was executed correctly + self.assertEqual(set(variable_results.keys()), set(['Time', 'C'])) + for variable_result in variable_results.values(): + self.assertFalse(numpy.any(numpy.isnan(variable_result))) + numpy.testing.assert_allclose( + variable_results['Time'], + numpy.linspace( + task.simulation.output_start_time, + task.simulation.output_end_time, + task.simulation.number_of_points + 1, + )) + + def test_exec_sed_task_positive_output_start_time_time_with_biosimulators(self): + # configure simulation + task = sedml_data_model.Task( + model=sedml_data_model.Model( + source=self.EXAMPLE_MODEL_FILENAME, + language=sedml_data_model.ModelLanguage.SBML.value, + ), + simulation=sedml_data_model.UniformTimeCourseSimulation( + initial_time=10., + output_start_time=20., + output_end_time=30., + number_of_points=10, + algorithm=sedml_data_model.Algorithm( + kisao_id='KISAO_0000019', + ), + ), + ) + + variables = [ + sedml_data_model.Variable( + id='Time', + symbol=sedml_data_model.Symbol.time, + task=task), + sedml_data_model.Variable( + id='C', + target="/sbml:sbml/sbml:model/sbml:listOfSpecies/sbml:species[@id='C']", + target_namespaces=self.NAMESPACES, + task=task), + ] + + # execute simulation + variable_results, log = core.exec_sed_task(task, variables) + + # check that the simulation was executed correctly + self.assertEqual(set(variable_results.keys()), set(['Time', 'C'])) + for variable_result in variable_results.values(): + self.assertFalse(numpy.any(numpy.isnan(variable_result))) + numpy.testing.assert_allclose( + variable_results['Time'], + numpy.linspace( + task.simulation.output_start_time, + task.simulation.output_end_time, + task.simulation.number_of_points + 1, + )) + + def test_exec_sed_task_steady_state_with_biosimulators(self): + # configure simulation + task = sedml_data_model.Task( + model=sedml_data_model.Model( + source=self.EXAMPLE_MODEL_FILENAME, + language=sedml_data_model.ModelLanguage.SBML.value, + ), + simulation=sedml_data_model.SteadyStateSimulation( + algorithm=sedml_data_model.Algorithm( + kisao_id='KISAO_0000408', + ), + ), + ) + + variables = [ + sedml_data_model.Variable( + id='C', + target="/sbml:sbml/sbml:model/sbml:listOfSpecies/sbml:species[@id='C']", + target_namespaces=self.NAMESPACES, + task=task), + sedml_data_model.Variable( + id='M', + target="/sbml:sbml/sbml:model/sbml:listOfSpecies/sbml:species[@id='M']", + target_namespaces=self.NAMESPACES, + task=task), + ] + + # execute simulation + variable_results, log = core.exec_sed_task(task, variables) + + # check that the simulation was executed correctly + self.assertEqual(set(variable_results.keys()), set(['C', 'M'])) + for variable_result in variable_results.values(): + self.assertFalse(numpy.any(numpy.isnan(variable_result))) + self.assertGreater(variable_results['C'], 0) + self.assertGreater(variable_results['M'], 0) + + def test_exec_sed_task_alg_substitution_with_biosimulators(self): + # configure simulation + task = sedml_data_model.Task( + model=sedml_data_model.Model( + source=self.EXAMPLE_MODEL_FILENAME, + language=sedml_data_model.ModelLanguage.SBML.value, + ), + simulation=sedml_data_model.UniformTimeCourseSimulation( + initial_time=0., + output_start_time=0., + output_end_time=10., + number_of_points=10, + algorithm=sedml_data_model.Algorithm( + kisao_id='KISAO_0000019', + ), + ), + ) + + variables = [ + sedml_data_model.Variable( + id='Time', + symbol=sedml_data_model.Symbol.time, + task=task), + sedml_data_model.Variable( + id='C', + target="/sbml:sbml/sbml:model/sbml:listOfSpecies/sbml:species[@id='C']", + target_namespaces=self.NAMESPACES, + task=task), + ] + + # execute simulation + task_2 = copy.deepcopy(task) + task_2.simulation.algorithm.kisao_id = 'KISAO_0000088' + with mock.patch.dict('os.environ', {'ALGORITHM_SUBSTITUTION_POLICY': 'NONE'}): + with self.assertRaises(AlgorithmCannotBeSubstitutedException): + core.exec_sed_task(task_2, variables) + + task_2 = copy.deepcopy(task) + task_2.simulation.algorithm.kisao_id = 'KISAO_0000088' + with mock.patch.dict('os.environ', {'ALGORITHM_SUBSTITUTION_POLICY': 'SIMILAR_VARIABLES'}): + with self.assertWarns(AlgorithmSubstitutedWarning): + core.exec_sed_task(task_2, variables) + + task_2 = copy.deepcopy(task) + task_2.simulation.algorithm.changes.append(sedml_data_model.AlgorithmParameterChange( + kisao_id='KISAO_0000488', + new_value='1', + )) + with mock.patch.dict('os.environ', {'ALGORITHM_SUBSTITUTION_POLICY': 'NONE'}): + with self.assertRaises(NotImplementedError): + core.exec_sed_task(task_2, variables) + + with mock.patch.dict('os.environ', {'ALGORITHM_SUBSTITUTION_POLICY': 'SIMILAR_VARIABLES'}): + with self.assertWarns(BioSimulatorsWarning): + core.exec_sed_task(task_2, variables) + + task_2 = copy.deepcopy(task) + task_2.simulation.algorithm.changes.append(sedml_data_model.AlgorithmParameterChange( + kisao_id='KISAO_0000209', + new_value='abc', + )) + with mock.patch.dict('os.environ', {'ALGORITHM_SUBSTITUTION_POLICY': 'NONE'}): + with self.assertRaises(ValueError): + core.exec_sed_task(task_2, variables) + + with mock.patch.dict('os.environ', {'ALGORITHM_SUBSTITUTION_POLICY': 'SIMILAR_VARIABLES'}): + with self.assertWarns(BioSimulatorsWarning): + core.exec_sed_task(task_2, variables) + + def test_exec_sed_task_error_handling_with_biosimulators(self): + # configure simulation + task = sedml_data_model.Task( + model=sedml_data_model.Model( + source=self.EXAMPLE_MODEL_FILENAME, + language=sedml_data_model.ModelLanguage.SBML.value, + ), + simulation=sedml_data_model.UniformTimeCourseSimulation( + initial_time=0., + output_start_time=0., + output_end_time=10., + number_of_points=10, + algorithm=sedml_data_model.Algorithm( + kisao_id='KISAO_0000019', + ), + ), + ) + + variables = [ + sedml_data_model.Variable( + id='Time', + symbol=sedml_data_model.Symbol.time, + task=task), + sedml_data_model.Variable( + id='C', + target="/sbml:sbml/sbml:model/sbml:listOfSpecies/sbml:species[@id='C']", + target_namespaces=self.NAMESPACES, + task=task), + ] + + # execute simulation + with self.assertRaises(NotImplementedError): + variable_results, log = core.exec_sed_task(task, variables, sedml_interpreter=SedmlInterpreter.tellurium) + + variables_2 = copy.deepcopy(variables) + variables_2[0].symbol = 'mass' + with self.assertRaises(NotImplementedError): + variable_results, log = core.exec_sed_task(task, variables_2, sedml_interpreter=SedmlInterpreter.biosimulators) + + variables_2 = copy.deepcopy(variables) + variables_2[1].target = '/sbml:sbml' + with self.assertRaisesRegex(ValueError, 'targets are not supported'): + variable_results, log = core.exec_sed_task(task, variables_2, sedml_interpreter=SedmlInterpreter.biosimulators) + + task_2 = copy.deepcopy(task) + task_2.simulation.output_start_time = 1.5 + with self.assertRaises(NotImplementedError): + variable_results, log = core.exec_sed_task(task_2, variables, sedml_interpreter=SedmlInterpreter.biosimulators) + + def test_exec_sedml_docs_in_combine_archive_successfully_with_biosimulators(self): + doc, archive_filename = self._build_combine_archive() + + out_dir = os.path.join(self.dirname, 'out') + core.exec_sedml_docs_in_combine_archive(archive_filename, out_dir, + report_formats=[ + report_data_model.ReportFormat.h5, + ], + bundle_outputs=True, + keep_individual_outputs=True) + + self._assert_combine_archive_outputs(doc, out_dir) + + def _build_combine_archive(self, algorithm=None): + doc = self._build_sed_doc(algorithm=algorithm) + + archive_dirname = os.path.join(self.dirname, 'archive') + if not os.path.isdir(archive_dirname): + os.mkdir(archive_dirname) + + model_filename = os.path.join(archive_dirname, 'model.xml') + shutil.copyfile(self.EXAMPLE_MODEL_FILENAME, model_filename) + + sim_filename = os.path.join(archive_dirname, 'sim.sedml') + SedmlSimulationWriter().run(doc, sim_filename) + + archive = combine_data_model.CombineArchive( + contents=[ + combine_data_model.CombineArchiveContent( + 'model.xml', combine_data_model.CombineArchiveContentFormat.SBML.value), + combine_data_model.CombineArchiveContent( + 'sim.sedml', combine_data_model.CombineArchiveContentFormat.SED_ML.value), + ], + ) + archive_filename = os.path.join(self.dirname, 'archive.omex') + CombineArchiveWriter().run(archive, archive_dirname, archive_filename) + + return (doc, archive_filename) + + def _build_sed_doc(self, algorithm=None): + if algorithm is None: + algorithm = sedml_data_model.Algorithm( + kisao_id='KISAO_0000019', + ) + + doc = sedml_data_model.SedDocument() + doc.models.append(sedml_data_model.Model( + id='model', + source='model.xml', + language=sedml_data_model.ModelLanguage.SBML.value, + )) + if algorithm.kisao_id == 'KISAO_0000408': + doc.simulations.append(sedml_data_model.SteadyStateSimulation( + id='sim_steady_state', + algorithm=algorithm, + )) + else: + doc.simulations.append(sedml_data_model.UniformTimeCourseSimulation( + id='sim_time_course', + initial_time=0., + output_start_time=0., + output_end_time=10., + number_of_points=10, + algorithm=algorithm, + )) + + doc.tasks.append(sedml_data_model.Task( + id='task_1', + model=doc.models[0], + simulation=doc.simulations[0], + )) + + if algorithm.kisao_id != 'KISAO_0000408': + doc.data_generators.append(sedml_data_model.DataGenerator( + id='data_gen_time', + variables=[ + sedml_data_model.Variable( + id='var_time', + symbol=sedml_data_model.Symbol.time.value, + task=doc.tasks[0], + ), + ], + math='var_time', + )) + doc.data_generators.append(sedml_data_model.DataGenerator( + id='data_gen_C', + variables=[ + sedml_data_model.Variable( + id='var_C', + target="/sbml:sbml/sbml:model/sbml:listOfSpecies/sbml:species[@id='C']", + target_namespaces=self.NAMESPACES, + task=doc.tasks[0], + ), + ], + math='var_C', + )) + + report = sedml_data_model.Report(id='report') + doc.outputs.append(report) + if algorithm.kisao_id != 'KISAO_0000408': + report.data_sets.append(sedml_data_model.DataSet(id='data_set_time', label='Time', data_generator=doc.data_generators[0])) + report.data_sets.append(sedml_data_model.DataSet(id='data_set_C', label='C', data_generator=doc.data_generators[-1])) + + append_all_nested_children_to_doc(doc) + + return doc + + def _assert_combine_archive_outputs(self, doc, out_dir): + self.assertEqual(set(['reports.h5']).difference(set(os.listdir(out_dir))), set()) + + report = ReportReader().run(doc.outputs[0], out_dir, 'sim.sedml/report', format=report_data_model.ReportFormat.h5) + + self.assertEqual(sorted(report.keys()), sorted([d.id for d in doc.outputs[0].data_sets])) + + sim = doc.tasks[0].simulation + if doc.simulations[0].algorithm.kisao_id == 'KISAO_0000408': + self.assertIn(report[doc.outputs[0].data_sets[0].id].shape, [(), (1,)]) + self.assertIsInstance(report[doc.outputs[0].data_sets[0].id].tolist(), (float, list)) + else: + self.assertEqual(len(report[doc.outputs[0].data_sets[0].id]), sim.number_of_points + 1) + + for data_set_result in report.values(): + self.assertFalse(numpy.any(numpy.isnan(data_set_result))) + + if doc.simulations[0].algorithm.kisao_id != 'KISAO_0000408': + self.assertIn('data_set_time', report) + numpy.testing.assert_allclose(report[doc.outputs[0].data_sets[0].id], + numpy.linspace(sim.output_start_time, sim.output_end_time, sim.number_of_points + 1)) + + # all SED-ML interpreters def test_exec_sedml_docs_in_combine_archive(self): - # with reports - archive_filename = 'tests/fixtures/BIOMD0000000297-with-reports.omex' + for sedml_interpreter in SedmlInterpreter.__members__.values(): + # with reports + archive_filename = 'tests/fixtures/BIOMD0000000297-with-reports.omex' - dirname = os.path.join(self.dirname, 'reports') - core.exec_sedml_docs_in_combine_archive(archive_filename, dirname) + dirname = os.path.join(self.dirname, sedml_interpreter.name, 'reports') + core.exec_sedml_docs_in_combine_archive(archive_filename, dirname, sedml_interpreter=sedml_interpreter) - self._assert_combine_archive_outputs(dirname, reports=True, plots=False) + self._assert_curated_combine_archive_outputs(dirname, reports=True, plots=False) - # with plots - archive_filename = 'tests/fixtures/BIOMD0000000297-with-plots.omex' + # with plots + archive_filename = 'tests/fixtures/BIOMD0000000297-with-plots.omex' - dirname = os.path.join(self.dirname, 'plots') - core.exec_sedml_docs_in_combine_archive(archive_filename, dirname) + dirname = os.path.join(self.dirname, sedml_interpreter.name, 'plots') + core.exec_sedml_docs_in_combine_archive(archive_filename, dirname, sedml_interpreter=sedml_interpreter) - self._assert_combine_archive_outputs(dirname, reports=False, plots=True) + self._assert_curated_combine_archive_outputs(dirname, reports=False, plots=True) - # with reports and plots - archive_filename = 'tests/fixtures/BIOMD0000000297-with-reports-and-plots.omex' + # with reports and plots + archive_filename = 'tests/fixtures/BIOMD0000000297-with-reports-and-plots.omex' + + dirname = os.path.join(self.dirname, sedml_interpreter.name, 'reports-and-plots') + core.exec_sedml_docs_in_combine_archive(archive_filename, dirname, sedml_interpreter=sedml_interpreter) - dirname = os.path.join(self.dirname, 'reports-and-plots') - core.exec_sedml_docs_in_combine_archive(archive_filename, dirname) + self._assert_curated_combine_archive_outputs(dirname, reports=True, plots=True) - self._assert_combine_archive_outputs(dirname, reports=True, plots=True) + def test_exec_sedml_docs_in_combine_archive_with_all_algorithms(self): + for sedml_interpreter in SedmlInterpreter.__members__.values(): + for alg in gen_algorithms_from_specs(self.SPECIFICATIONS_FILENAME).values(): + doc, archive_filename = self._build_combine_archive(algorithm=alg) + out_dir = os.path.join(self.dirname, sedml_interpreter.name, alg.kisao_id) + core.exec_sedml_docs_in_combine_archive(archive_filename, out_dir, + sedml_interpreter=sedml_interpreter, + report_formats=[ + report_data_model.ReportFormat.h5, + ], + bundle_outputs=True, + keep_individual_outputs=True) + self._assert_combine_archive_outputs(doc, out_dir) - def test_exec_sedml_docs_in_combine_archive_with_tellurium_error(self): + def test_exec_sed_doc(self): + with mock.patch('biosimulators_tellurium.core.exec_sed_doc_with_biosimulators', return_value=None): + core.exec_sed_doc(None, None, None) + with self.assertRaises(NotImplementedError): + core.exec_sed_doc(None, None, None, sedml_interpreter='undefine') + + # tellurium error handling + def test_exec_sedml_docs_in_combine_archive_with_tellurium_error_handling(self): archive_filename = 'tests/fixtures/BIOMD0000000297-with-reports-and-plots.omex' with self.assertRaisesRegex(Exception, 'my error'): with mock.patch.object(tellurium.sedml.tesedml.SEDMLCodeFactory, 'executePython', side_effect=Exception('my error')): - core.exec_sedml_docs_in_combine_archive(archive_filename, self.dirname) + core.exec_sedml_docs_in_combine_archive(archive_filename, self.dirname, sedml_interpreter=SedmlInterpreter.tellurium) + # CLI and Docker image def test_exec_sedml_docs_in_combine_archive_with_cli(self): archive_filename = 'tests/fixtures/BIOMD0000000297-with-reports-and-plots.omex' env = self._get_combine_archive_exec_env() @@ -71,7 +628,7 @@ def test_exec_sedml_docs_in_combine_archive_with_cli(self): with __main__.App(argv=['-i', archive_filename, '-o', self.dirname]) as app: app.run() - self._assert_combine_archive_outputs(self.dirname, reports=True, plots=True) + self._assert_curated_combine_archive_outputs(self.dirname, reports=True, plots=True) def test_sim_with_docker_image(self): archive_filename = 'tests/fixtures/BIOMD0000000297-with-reports-and-plots.omex' @@ -80,14 +637,15 @@ def test_sim_with_docker_image(self): exec_sedml_docs_in_archive_with_containerized_simulator( archive_filename, self.dirname, self.DOCKER_IMAGE, environment=env, pull_docker_image=False) - self._assert_combine_archive_outputs(self.dirname, reports=True, plots=True) + self._assert_curated_combine_archive_outputs(self.dirname, reports=True, plots=True) + # helper methods def _get_combine_archive_exec_env(self): return { 'REPORT_FORMATS': 'h5,csv' } - def _assert_combine_archive_outputs(self, dirname, reports=True, plots=True): + def _assert_curated_combine_archive_outputs(self, dirname, reports=True, plots=True): expected_files = set() if reports or plots: @@ -171,6 +729,8 @@ def _assert_combine_archive_outputs(self, dirname, reports=True, plots=True): with open(archive_file.local_path, 'rb') as file: PyPDF2.PdfFileReader(file) + +class CliTestCase(unittest.TestCase): def test_raw_cli(self): with mock.patch('sys.argv', ['', '--help']): with self.assertRaises(SystemExit) as context: diff --git a/tests/test_data_model.py b/tests/test_data_model.py index b4cd519..8a375b8 100644 --- a/tests/test_data_model.py +++ b/tests/test_data_model.py @@ -1,8 +1,50 @@ -from biosimulators_tellurium.data_model import PlottingEngine +from biosimulators_tellurium.data_model import SedmlInterpreter, PlottingEngine, KISAO_ALGORITHM_MAP +from biosimulators_utils.utils.core import parse_value import unittest +import json +import os class DataModelTestCase(unittest.TestCase): + def test_SedmlInterpreter(self): + self.assertEqual(SedmlInterpreter.biosimulators.value, 'biosimulators') + self.assertEqual(SedmlInterpreter.tellurium.value, 'tellurium') + def test_PlottingEngine(self): self.assertEqual(PlottingEngine.matplotlib.value, 'matplotlib') self.assertEqual(PlottingEngine.plotly.value, 'plotly') + + def test_data_model_matches_specs(self): + specs_filename = os.path.join(os.path.dirname(__file__), '..', 'biosimulators.json') + with open(specs_filename, 'r') as file: + specs = json.load(file) + + alg_kisao_ids = [alg_specs['kisaoId']['id'] for alg_specs in specs['algorithms']] + self.assertEqual(len(alg_kisao_ids), len(set(alg_kisao_ids))) + self.assertEqual(set(KISAO_ALGORITHM_MAP.keys()), + set(alg_kisao_ids)) + + for alg_specs in specs['algorithms']: + alg_props = KISAO_ALGORITHM_MAP[alg_specs['kisaoId']['id']] + param_kisao_ids = [param_specs['kisaoId']['id'] for param_specs in alg_specs['parameters']] + self.assertEqual(len(param_kisao_ids), len(set(param_kisao_ids)), alg_specs['kisaoId']['id']) + self.assertEqual(set(alg_props['parameters'].keys()), + set(param_kisao_ids), + 'Algorithm: `{}`'.format(alg_specs['kisaoId']['id'])) + + param_ids = [ + param_specs['id'] + for param_specs in alg_specs['parameters'] + if param_specs['id'] is not None and 'BioSimulators Docker image' in param_specs['availableSoftwareInterfaceTypes'] + ] + self.assertEqual(len(param_ids), len(set(param_ids)), 'Algorithm: `{}`'.format(alg_specs['kisaoId']['id'])) + + for param_specs in alg_specs['parameters']: + param_props = alg_props['parameters'][param_specs['kisaoId']['id']] + self.assertEqual(param_props['type'].value, param_specs['type']) + self.assertEqual(param_props['default'], + None if param_specs['value'] is None else parse_value(param_specs['value'], param_specs['type']), + '{}: {}'.format(alg_specs['kisaoId']['id'], param_specs['kisaoId']['id'])) + self.assertEqual(param_props['id'], param_specs.get('id', None), + 'Algorithm: `{}`, Parameter: `{}`'.format(alg_specs['kisaoId']['id'], param_specs['kisaoId']['id']) + )