From 21480c876820e6e0caa821b0e5e08c2b4ee873cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20K=C3=B6nig?= Date: Tue, 2 Apr 2024 22:30:26 +0200 Subject: [PATCH] updating sbmlsim --- src/sbmlsim/_deprecated/__init__.py | 0 src/sbmlsim/_deprecated/pk/__init__.py | 0 src/sbmlsim/_deprecated/pk/pkpd.py | 82 - src/sbmlsim/_deprecated/tesedml.py | 2315 ------ src/sbmlsim/experiment/experiment.py | 6 +- src/sbmlsim/experiment/runner.py | 4 +- src/sbmlsim/model/model_roadrunner.py | 70 +- src/sbmlsim/oven/model_nan.py | 89 - src/sbmlsim/oven/model_state.py | 44 + src/sbmlsim/oven/nan_species.xml | 158 - src/sbmlsim/oven/nan_species.zip | Bin 2709 -> 0 bytes src/sbmlsim/oven/nan_species_roadrunner.py | 23 - src/sbmlsim/oven/omeprazole_body_flat.xml | 7993 ++++++++++++++++++++ src/sbmlsim/oven/rr_tolerances_issues.py | 15 - src/sbmlsim/oven/tiny_example_1.png | Bin 56654 -> 0 bytes src/sbmlsim/oven/tiny_example_1.xml | 298 - src/sbmlsim/simulator/simulation_serial.py | 16 +- 17 files changed, 8091 insertions(+), 3022 deletions(-) delete mode 100644 src/sbmlsim/_deprecated/__init__.py delete mode 100644 src/sbmlsim/_deprecated/pk/__init__.py delete mode 100644 src/sbmlsim/_deprecated/pk/pkpd.py delete mode 100644 src/sbmlsim/_deprecated/tesedml.py delete mode 100644 src/sbmlsim/oven/model_nan.py create mode 100644 src/sbmlsim/oven/model_state.py delete mode 100644 src/sbmlsim/oven/nan_species.xml delete mode 100644 src/sbmlsim/oven/nan_species.zip delete mode 100644 src/sbmlsim/oven/nan_species_roadrunner.py create mode 100644 src/sbmlsim/oven/omeprazole_body_flat.xml delete mode 100644 src/sbmlsim/oven/rr_tolerances_issues.py delete mode 100644 src/sbmlsim/oven/tiny_example_1.png delete mode 100644 src/sbmlsim/oven/tiny_example_1.xml diff --git a/src/sbmlsim/_deprecated/__init__.py b/src/sbmlsim/_deprecated/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/src/sbmlsim/_deprecated/pk/__init__.py b/src/sbmlsim/_deprecated/pk/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/src/sbmlsim/_deprecated/pk/pkpd.py b/src/sbmlsim/_deprecated/pk/pkpd.py deleted file mode 100644 index 9792fa2c..00000000 --- a/src/sbmlsim/_deprecated/pk/pkpd.py +++ /dev/null @@ -1,82 +0,0 @@ -"""Methods specific to pkdb models.""" -import re -from typing import Dict - -import roadrunner -from sbmlutils import log - - -logger = log.get_logger(__name__) - -# ------------------------------------------------------------------------------------------------- -# Initial values -# ------------------------------------------------------------------------------------------------- -# Helper functions for setting initial values in the model. -# Substances which are transported in the body can hereby be initialized in all -# tissues to identical values (which removes the distribution kinetics). -# ------------------------------------------------------------------------------------------------- - - -def init_concentrations_changes(r: roadrunner.RoadRunner, skey, value: float): - """Get changes to set initial concentrations for skey.""" - return _set_initial_values(r, skey, value, method="concentration") - - -def init_amounts_changes(r: roadrunner.RoadRunner, skey, value): - """Set initial amounts for skey.""" - return _set_initial_values(r, skey, value, method="amount") - - -def _set_initial_values( - r: roadrunner.RoadRunner, sid, value, method="concentration" -) -> Dict: - """Set the initial concentration of a distributing substance. - - Takes care of all the compartment values so starting close/in steady state. - Units are in model units - - return: changeset for model - """ - if method not in ["amount", "concentration"]: - raise ValueError - - species_ids = r.model.getFloatingSpeciesIds() + r.model.getBoundarySpeciesIds() - species_keys = get_species_keys(sid, species_ids) - changeset = {} - - for key in species_keys: - - if method == "concentration": - rkey = f"[{key}]" - - if "urine" in rkey: - logger.debug("urinary values are not set") - continue - - changeset[rkey] = value - - return changeset - - -def get_species_keys(skey, species_ids): - """Get keys of substance in given list of ids. - - Relies on naming patterns of ids. This does not get the species ids of the submodels, - but only of the top model. - - :param skey: substance key - :param species_ids: list of species ids to filter from - :return: - """ - keys = [] - for species_id in species_ids: - # use regular expression to find ids - # This pattern is not very robust !!! FIXME (e.g. blood vs. plasma) - pattern = r"^[AC][a-z]+(_plasma)*\_{}$".format(skey) - - match = re.search(pattern, species_id) - if match: - # print("match:", species_id) - keys.append(species_id) - - return keys diff --git a/src/sbmlsim/_deprecated/tesedml.py b/src/sbmlsim/_deprecated/tesedml.py deleted file mode 100644 index 484feec0..00000000 --- a/src/sbmlsim/_deprecated/tesedml.py +++ /dev/null @@ -1,2315 +0,0 @@ -""" -Tellurium SED-ML support. - -This module implements SED-ML support for tellurium. - ----------------- -Overview SED-ML ----------------- -SED-ML is build of main classes - the Model Class, - the Simulation Class, - the Task Class, - the DataGenerator Class, - and the Output Class. - -The Model Class - The Model class is used to reference the models used in the simulation experiment. - SED-ML itself is independent of the model encoding underlying the models. The only - requirement is that the model needs to be referenced by using an unambiguous identifier - which allows for finding it, for example using a MIRIAM URI. To specify the language in - which the model is encoded, a set of predefined language URNs is provided. - The SED-ML Change class allows the application of changes to the referenced models, - including changes on the XML attributes, e.g. changing the value of an observable, - computing the change of a value using mathematics, or general changes on any XML element - of the model representation that is addressable by XPath expressions, e.g. substituting - a piece of XML by an updated one. - -TODO: DATA CLASS - - -The Simulation Class - The Simulation class defines the simulation settings and the steps taken during simulation. - These include the particular type of simulation and the algorithm used for the execution of - the simulation; preferably an unambiguous reference to such an algorithm should be given, - using a controlled vocabulary, or ontologies. One example for an ontology of simulation - algorithms is the Kinetic Simulation Algorithm Ontology KiSAO. Further information encodable - in the Simulation class includes the step size, simulation duration, and other - simulation-type dependent information. - -The Task Class - SED-ML makes use of the notion of a Task class to combine a defined model (from the Model class) - and a defined simulation setting (from the Simulation class). A task always holds one reference each. - To refer to a specific model and to a specific simulation, the corresponding IDs are used. - -The DataGenerator Class - The raw simulation result sometimes does not correspond to the desired output of the simulation, - e.g. one might want to normalise a plot before output, or apply post-processing like mean-value calculation. - The DataGenerator class allows for the encoding of such post-processings which need to be applied to the - simulation result before output. To define data generators, any addressable variable or parameter - of any defined model (from instances of the Model class) may be referenced, and new entities might - be specified using MathML definitions. - -The Output Class - The Output class defines the output of the simulation, in the sense that it specifies what shall be - plotted in the output. To do so, an output type is defined, e.g. 2D-plot, 3D-plot or data table, - and the according axes or columns are all assigned to one of the formerly specified instances - of the DataGenerator class. - -For information about SED-ML please refer to http://www.sed-ml.org/ -and the SED-ML specification. - ------------------------------------- -SED-ML in tellurium: Implementation ------------------------------------- -SED-ML support in tellurium is based on Combine Archives. -The SED-ML files in the Archive can be executed and stored with results. - ----------------------------------------- -SED-ML in tellurium: Supported Features ----------------------------------------- -Tellurium supports SED-ML L1V3 with SBML as model format. - -SBML models are fully supported, whereas for CellML models only basic support -is implemented (when additional support is requested it be implemented). -CellML models are transformed to SBML models which results in different XPath expressions, -so that targets, selections cannot be easily resolved in the CellMl-SBML. - -Supported input for SED-ML are either SED-ML files ('.sedml' extension), -SED-ML XML strings or combine archives ('.sedx'|'.omex' extension). -Executable python code is generated from the SED-ML which allows the -execution of the defined simulation experiment. - - In the current implementation all SED-ML constructs with exception of - XML transformation changes of the model - - Change.RemoveXML - - Change.AddXML - - Change.ChangeXML - are supported. - -------- -Notice -------- -The main maintainer for SED-ML support is Matthias König. -Please let changes to this file be reviewed and make sure that all SED-ML related tests are working. -""" -from __future__ import absolute_import, division, print_function - -import datetime -import importlib -import os.path -import platform -import re -import shutil -import sys -import tempfile -import traceback -import warnings -import zipfile -from collections import namedtuple - -import jinja2 -import libsedml -import numpy as np - - -importlib.reload(libsedml) - - -import tellurium as te -from tellurium.utils import omex - -from sbmlsim.oven.mathml import evaluableMathML - - -try: - # required imports in generated python code - import matplotlib.pyplot as plt - import mpl_toolkits.mplot3d - import pandas -except ImportError: - warnings.warn("Dependencies for SEDML code execution not fulfilled.") - print(traceback.format_exc()) - -###################################################################################################################### -# KISAO MAPPINGS -###################################################################################################################### - -KISAOS_CVODE = [ # 'cvode' - "KISAO:0000019", # CVODE - "KISAO:0000433", # CVODE-like method - "KISAO:0000407", - "KISAO:0000099", - "KISAO:0000035", - "KISAO:0000071", - "KISAO:0000288", # "BDF" cvode, stiff=true - "KISAO:0000280", # "Adams-Moulton" cvode, stiff=false -] - -KISAOS_RK4 = [ # 'rk4' - "KISAO:0000032", # RK4 explicit fourth-order Runge-Kutta method - "KISAO:0000064", # Runge-Kutta based method -] - -KISAOS_RK45 = [ # 'rk45' - "KISAO:0000086", # RKF45 embedded Runge-Kutta-Fehlberg 5(4) method -] - -KISAOS_LSODA = [ # 'lsoda' - "KISAO:0000088", # roadrunner doesn't have an lsoda solver so use cvode -] - -KISAOS_GILLESPIE = [ # 'gillespie' - "KISAO:0000241", # Gillespie-like method - "KISAO:0000029", - "KISAO:0000319", - "KISAO:0000274", - "KISAO:0000333", - "KISAO:0000329", - "KISAO:0000323", - "KISAO:0000331", - "KISAO:0000027", - "KISAO:0000082", - "KISAO:0000324", - "KISAO:0000350", - "KISAO:0000330", - "KISAO:0000028", - "KISAO:0000038", - "KISAO:0000039", - "KISAO:0000048", - "KISAO:0000074", - "KISAO:0000081", - "KISAO:0000045", - "KISAO:0000351", - "KISAO:0000084", - "KISAO:0000040", - "KISAO:0000046", - "KISAO:0000003", - "KISAO:0000051", - "KISAO:0000335", - "KISAO:0000336", - "KISAO:0000095", - "KISAO:0000022", - "KISAO:0000076", - "KISAO:0000015", - "KISAO:0000075", - "KISAO:0000278", -] - -KISAOS_NLEQ = [ # 'nleq' - "KISAO:0000099", - "KISAO:0000274", - "KISAO:0000282", - "KISAO:0000283", - "KISAO:0000355", - "KISAO:0000356", - "KISAO:0000407", - "KISAO:0000408", - "KISAO:0000409", - "KISAO:0000410", - "KISAO:0000411", - "KISAO:0000412", - "KISAO:0000413", - "KISAO:0000432", - "KISAO:0000437", -] - -# allowed algorithms for simulation type -KISAOS_STEADYSTATE = KISAOS_NLEQ -KISAOS_UNIFORMTIMECOURSE = ( - KISAOS_CVODE + KISAOS_RK4 + KISAOS_RK45 + KISAOS_GILLESPIE + KISAOS_LSODA -) -KISAOS_ONESTEP = KISAOS_UNIFORMTIMECOURSE - -# supported algorithm parameters -KISAOS_ALGORITHMPARAMETERS = { - "KISAO:0000209": ("relative_tolerance", float), # the relative tolerance - "KISAO:0000211": ("absolute_tolerance", float), # the absolute tolerance - "KISAO:0000220": ("maximum_bdf_order", int), # the maximum BDF (stiff) order - "KISAO:0000219": ( - "maximum_adams_order", - int, - ), # the maximum Adams (non-stiff) order - "KISAO:0000415": ( - "maximum_num_steps", - int, - ), # the maximum number of steps that can be taken before exiting - "KISAO:0000467": ( - "maximum_time_step", - float, - ), # the maximum time step that can be taken - "KISAO:0000485": ( - "minimum_time_step", - float, - ), # the minimum time step that can be taken - "KISAO:0000332": ( - "initial_time_step", - float, - ), # the initial value of the time step for algorithms that change this value - "KISAO:0000107": ( - "variable_step_size", - bool, - ), # whether or not the algorithm proceeds with an adaptive step size or not - "KISAO:0000486": ( - "maximum_iterations", - int, - ), # [nleq] the maximum number of iterations the algorithm should take before exiting - "KISAO:0000487": ("minimum_damping", float), # [nleq] minimum damping value - "KISAO:0000488": ("seed", int), # the seed for stochastic runs of the algorithm -} - - -###################################################################################################################### -# Interface functions -###################################################################################################################### -# The functions listed in this section are the only functions one should interact with this module. -# We try to keep these back-wards compatible and keep the function signatures. -# -# All other function and class signatures can change. -###################################################################################################################### - - -def sedmlToPython(inputStr, workingDir=None): - """Convert sedml file to python code. - - :param inputStr: full path name to SedML model or SED-ML string - :type inputStr: path - :return: generated python code - """ - factory = SEDMLCodeFactory(inputStr, workingDir=workingDir) - return factory.toPython() - - -def executeSEDML(inputStr, workingDir=None): - """Run a SED-ML file or combine archive with results. - - If a workingDir is provided the files and results are written in the workingDir. - - :param inputStr: - :type inputStr: - :return: - :rtype: - """ - # execute the sedml - factory = SEDMLCodeFactory(inputStr, workingDir=workingDir) - factory.executePython() - - -def combineArchiveToPython(omexPath): - """All python code generated from given combine archive. - - :param omexPath: - :return: dictionary of { sedml_location: pycode } - """ - tmp_dir = tempfile.mkdtemp() - pycode = {} - try: - omex.extract_combine_archive(omexPath, directory=tmp_dir, method="zip") - locations = omex.locations_by_format(omexPath, "sed-ml") - sedml_files = [os.path.join(tmp_dir, loc) for loc in locations] - - for k, sedml_file in enumerate(sedml_files): - pystr = sedmlToPython(sedml_file) - pycode[locations[k]] = pystr - - finally: - shutil.rmtree(tmp_dir) - return pycode - - -def executeCombineArchive( - omexPath, - workingDir=None, - printPython=False, - createOutputs=True, - saveOutputs=False, - outputDir=None, - plottingEngine=None, -): - """Run all SED-ML simulations in given COMBINE archive. - - If no workingDir is provided execution is performed in temporary directory - which is cleaned afterwards. - The executed code can be printed via the 'printPython' flag. - - :param omexPath: OMEX Combine archive - :param workingDir: directory to extract archive to - :param printPython: boolean switch to print executed python code - :param createOutputs: boolean flag if outputs should be created, i.e. report and plots - :param saveOutputs: flag if the outputs should be saved to file - :param outputDir: directory where the outputs should be written - :param plottingEngin: string of which plotting engine to use; uses set plotting engine otherwise - :return dictionary of sedmlFile:data generators - """ - - # combine archives are zip format - if zipfile.is_zipfile(omexPath): - try: - tmp_dir = tempfile.mkdtemp() - if workingDir is None: - extractDir = tmp_dir - else: - if not os.path.exists(workingDir): - raise IOError( - "workingDir does not exist, make sure to create the directoy: '{}'".format( - workingDir - ) - ) - extractDir = workingDir - - # extract - omex.extract_combine_archive(omex_path=omexPath, directory=extractDir) - - # get sedml locations by omex - sedml_locations = omex.locations_by_format( - omex_path=omexPath, format_key="sed-ml", method="omex" - ) - if len(sedml_locations) == 0: - - # falling back to zip archive - sedml_locations = omex.locations_by_format( - omex_path=omexPath, format_key="sed-ml", method="zip" - ) - warnings.warn( - "No SED-ML files in COMBINE archive based on manifest '{}'; Guessed SED-ML {}".format( - omexPath, sedml_locations - ) - ) - - # run all sedml files - results = {} - sedml_paths = [os.path.join(extractDir, loc) for loc in sedml_locations] - for sedmlFile in sedml_paths: - factory = SEDMLCodeFactory( - sedmlFile, - workingDir=os.path.dirname(sedmlFile), - createOutputs=createOutputs, - saveOutputs=saveOutputs, - outputDir=outputDir, - plottingEngine=plottingEngine, - ) - if printPython: - code = factory.toPython() - print(code) - - results[sedmlFile] = factory.executePython() - - return results - finally: - shutil.rmtree(tmp_dir) - else: - if not os.path.exists(omexPath): - raise FileNotFoundError("File does not exist: {}".format(omexPath)) - else: - raise IOError( - "File is not an OMEX Combine Archive in zip format: {}".format(omexPath) - ) - - -###################################################################################################################### - - -class SEDMLCodeFactory(object): - """Code Factory generating executable code.""" - - # template location - TEMPLATE_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "templates") - - def __init__( - self, - inputStr, - workingDir=None, - createOutputs=True, - saveOutputs=False, - outputDir=None, - plottingEngine=None, - ): - """Create CodeFactory for given input. - - :param inputStr: - :param workingDir: - :param createOutputs: if outputs should be created - - :return: - :rtype: - """ - self.inputStr = inputStr - self.workingDir = workingDir - self.python = sys.version - self.platform = platform.platform() - self.createOutputs = createOutputs - self.saveOutputs = saveOutputs - self.outputDir = outputDir - self.plotFormat = "pdf" - self.reportFormat = "csv" - - if not plottingEngine: - plottingEngine = te.getPlottingEngine() - self.plottingEngine = plottingEngine - - if self.outputDir: - if not os.path.exists(outputDir): - raise IOError("outputDir does not exist: {}".format(outputDir)) - - info = SEDMLTools.readSEDMLDocument(inputStr, workingDir) - self.doc = info["doc"] - self.inputType = info["inputType"] - self.workingDir = info["workingDir"] - - # parse the models (resolve the source models & the applied changes for all models) - model_sources, model_changes = SEDMLTools.resolveModelChanges(self.doc) - self.model_sources = model_sources - self.model_changes = model_changes - - def __str__(self): - """Print. - - :return: - :rtype: - """ - lines = [ - "{}".format(self.__class__), - "doc: {}".format(self.doc), - "workingDir: {}".format(self.workingDir), - "inputType: {}".format(self.inputType), - ] - if self.inputType != SEDMLTools.INPUT_TYPE_STR: - lines.append("input: {}".format(self.inputStr)) - return "\n".join(lines) - - def sedmlString(self): - """Get the SEDML XML string of the current document. - - :return: SED-ML XML - :rtype: str - """ - return libsedml.writeSedMLToString(self.doc) - - def toPython(self, python_template="tesedml_template.template"): - """Create python code by rendering the python template. - Uses the information in the SED-ML document to create - python code - - Renders the respective template. - - :return: returns the rendered template - :rtype: str - """ - # template environment - env = jinja2.Environment( - loader=jinja2.FileSystemLoader(self.TEMPLATE_DIR), - extensions=[], - trim_blocks=True, - lstrip_blocks=True, - ) - - # additional filters - # for key in sedmlfilters.filters: - # env.filters[key] = getattr(sedmlfilters, key) - template = env.get_template(python_template) - env.globals["modelToPython"] = self.modelToPython - env.globals["dataDescriptionToPython"] = self.dataDescriptionToPython - env.globals["taskToPython"] = self.taskToPython - env.globals["dataGeneratorToPython"] = self.dataGeneratorToPython - env.globals["outputToPython"] = self.outputToPython - - # timestamp - time = datetime.datetime.now() - timestamp = time.strftime("%Y-%m-%dT%H:%M:%S") - - # Context - c = { - "version": te.getTelluriumVersion(), - "timestamp": timestamp, - "factory": self, - "doc": self.doc, - "model_sources": self.model_sources, - "model_changes": self.model_changes, - } - pysedml = template.render(c) - - return pysedml - - def executePython(self): - """Executes python code. - - The python code is created during the function call. - See :func:`createpython` - - :return: returns dictionary of information with keys - """ - result = {} - code = self.toPython() - result["code"] = code - result["platform"] = platform.platform() - - # FIXME: better solution for exec traceback - filename = os.path.join(tempfile.gettempdir(), "te-generated-sedml.py") - - try: - # Use of exec carries the usual security warnings - symbols = {} - exec(compile(code, filename, "exec"), symbols) - - # read information from exec symbols - dg_data = {} - for dg in self.doc.getListOfDataGenerators(): - dg_id = dg.getId() - dg_data[dg_id] = symbols[dg_id] - result["dataGenerators"] = dg_data - return result - - except: - # leak this tempfile just so we can see a full stack trace. freaking python. - with open(filename, "w") as f: - f.write(code) - raise - - def modelToPython(self, model): - """Python code for SedModel. - - :param model: SedModel instance - :type model: SedModel - :return: python str - :rtype: str - """ - lines = [] - mid = model.getId() - language = model.getLanguage() - source = self.model_sources[mid] - - if not language: - warnings.warn( - "No model language specified, defaulting to SBML for: {}".format(source) - ) - - def isUrn(): - return source.startswith("urn") or source.startswith("URN") - - def isHttp(): - return source.startswith("http") or source.startswith("HTTP") - - # read SBML - if "sbml" in language or len(language) == 0: - if isUrn(): - lines.append("import tellurium.temiriam as temiriam") - lines.append( - "__{}_sbml = temiriam.getSBMLFromBiomodelsURN('{}')".format( - mid, source - ) - ) - lines.append("{} = te.loadSBMLModel(__{}_sbml)".format(mid, mid)) - elif isHttp(): - lines.append("{} = te.loadSBMLModel('{}')".format(mid, source)) - else: - lines.append( - "{} = te.loadSBMLModel(os.path.join(workingDir, '{}'))".format( - mid, source - ) - ) - # read CellML - elif "cellml" in language: - warnings.warn( - "CellML model encountered. Tellurium CellML support is very limited.".format( - language - ) - ) - if isHttp(): - lines.append("{} = te.loadCellMLModel('{}')".format(mid, source)) - else: - lines.append( - "{} = te.loadCellMLModel(os.path.join(workingDir, '{}'))".format( - mid, self.model_sources[mid] - ) - ) - # other - else: - warnings.warn("Unsupported model language: '{}'.".format(language)) - - # apply model changes - for change in self.model_changes[mid]: - lines.extend(SEDMLCodeFactory.modelChangeToPython(model, change)) - - return "\n".join(lines) - - @staticmethod - def modelChangeToPython(model, change): - """Creates the apply change python string for given model and change. - - Currently only a very limited subset of model changes is supported. - Namely changes of parameters and concentrations within a SedChangeAttribute. - - :param model: given model - :type model: SedModel - :param change: model change - :type change: SedChange - :return: - :rtype: str - """ - lines = [] - mid = model.getId() - xpath = change.getTarget() - - if change.getTypeCode() == libsedml.SEDML_CHANGE_ATTRIBUTE: - # resolve target change - value = change.getNewValue() - lines.append("# {} {}".format(xpath, value)) - lines.append(SEDMLCodeFactory.targetToPython(xpath, value, modelId=mid)) - - elif change.getTypeCode() == libsedml.SEDML_CHANGE_COMPUTECHANGE: - variables = {} - for par in change.getListOfParameters(): - variables[par.getId()] = par.getValue() - for var in change.getListOfVariables(): - vid = var.getId() - selection = SEDMLCodeFactory.selectionFromVariable(var, mid) - expr = selection.id - if selection.type == "concentration": - expr = "init([{}])".format(selection.id) - elif selection.type == "amount": - expr = "init({})".format(selection.id) - lines.append("__var__{} = {}['{}']".format(vid, mid, expr)) - variables[vid] = "__var__{}".format(vid) - - # value is calculated with the current state of model - value = evaluableMathML(change.getMath(), variables=variables) - lines.append(SEDMLCodeFactory.targetToPython(xpath, value, modelId=mid)) - - elif change.getTypeCode() in [ - libsedml.SEDML_CHANGE_REMOVEXML, - libsedml.SEDML_CHANGE_ADDXML, - libsedml.SEDML_CHANGE_CHANGEXML, - ]: - lines.append("# Unsupported change: {}".format(change.getElementName())) - warnings.warn("Unsupported change: {}".format(change.getElementName())) - else: - lines.append("# Unsupported change: {}".format(change.getElementName())) - warnings.warn("Unsupported change: {}".format(change.getElementName())) - - return lines - - def dataDescriptionToPython(self, dataDescription): - """Python code for DataDescription. - - :param dataDescription: SedModel instance - :type dataDescription: DataDescription - :return: python str - :rtype: str - """ - lines = [] - - from tellurium.sedml.data import DataDescriptionParser - - data_sources = DataDescriptionParser.parse(dataDescription, self.workingDir) - - def data_to_string(data): - info = np.array2string(data) - # cleaner string and NaN handling - info = info.replace("\n", ", ").replace("\r", "").replace("nan", "np.nan") - return info - - for sid, data in data_sources.items(): - # handle the 1D shapes - if len(data.shape) == 1: - data = np.reshape(data.values, (data.shape[0], 1)) - - array_str = data_to_string(data) - lines.append("{} = np.array({})".format(sid, array_str)) - - return "\n".join(lines) - - ################################################################################################ - # Here the main work is done, - # transformation of tasks to python code - ################################################################################################ - @staticmethod - def taskToPython(doc, task): - """Create python for arbitrary task (repeated or simple). - - :param doc: - :type doc: - :param task: - :type task: - :return: - :rtype: - """ - # If no DataGenerator references the task, no execution is necessary - dgs = SEDMLCodeFactory.getDataGeneratorsForTask(doc, task) - if len(dgs) == 0: - return "# not part of any DataGenerator: {}".format(task.getId()) - - # tasks contain other subtasks, which can contain subtasks. This - # results in a tree of task dependencies where the - # simple tasks are the node leaves. These tree has to be resolved to - # generate code for more complex task dependencies. - - # resolve task tree (order & dependency of tasks) & generate code - taskTree = SEDMLCodeFactory.createTaskTree(doc, rootTask=task) - return SEDMLCodeFactory.taskTreeToPython(doc, tree=taskTree) - - class TaskNode(object): - """Tree implementation of task tree.""" - - def __init__(self, task, depth): - self.task = task - self.depth = depth - self.children = [] - self.parent = None - - def add_child(self, obj): - obj.parent = self - self.children.append(obj) - - def is_leaf(self): - return len(self.children) == 0 - - def __str__(self): - lines = [ - "<[{}] {} ({})>".format( - self.depth, self.task.getId(), self.task.getElementName() - ) - ] - for child in self.children: - child_str = child.__str__() - lines.extend(["\t{}".format(line) for line in child_str.split("\n")]) - return "\n".join(lines) - - def info(self): - return "<[{}] {} ({})>".format( - self.depth, self.task.getId(), self.task.getElementName() - ) - - def __iter__(self): - """Depth-first iterator which yields TaskNodes.""" - yield self - for child in self.children: - for node in child: - yield node - - class Stack(object): - """Stack implementation for nodes.""" - - def __init__(self): - self.items = [] - - def isEmpty(self): - return self.items == [] - - def push(self, item): - self.items.append(item) - - def pop(self): - return self.items.pop() - - def peek(self): - return self.items[len(self.items) - 1] - - def size(self): - return len(self.items) - - def __str__(self): - return "stack: " + str([item.info() for item in self.items]) - - @staticmethod - def createTaskTree(doc, rootTask): - """Creates the task tree. - Required for resolution of order of all simulations. - """ - - def add_children(node): - typeCode = node.task_id.getTypeCode() - if typeCode == libsedml.SEDML_TASK: - return # no children - elif typeCode == libsedml.SEDML_TASK_REPEATEDTASK: - # add the ordered list of subtasks as children - subtasks = SEDMLCodeFactory.getOrderedSubtasks(node.task_id) - for st in subtasks: - # get real task for subtask - t = doc.getTask(st.getTask()) - child = SEDMLCodeFactory.TaskNode(t, depth=node.depth + 1) - node.add_child(child) - # recursive adding of children - add_children(child) - else: - raise IOError( - "Unsupported task type: {}".format(node.task_id.getElementName()) - ) - - # create root - root = SEDMLCodeFactory.TaskNode(rootTask, depth=0) - # recursive adding of children - add_children(root) - return root - - @staticmethod - def getOrderedSubtasks(task): - """Ordered list of subtasks for task.""" - subtasks = task.getListOfSubTasks() - subtaskOrder = [st.getOrder() for st in subtasks] - # sort by order, if all subtasks have order (not required) - if all(subtaskOrder) != None: - subtasks = [st for (stOrder, st) in sorted(zip(subtaskOrder, subtasks))] - return subtasks - - @staticmethod - def taskTreeToPython(doc, tree): - """Python code generation from task tree.""" - - # go forward through task tree - lines = [] - nodeStack = SEDMLCodeFactory.Stack() - treeNodes = [n for n in tree] - - # iterate over the tree - for kn, node in enumerate(treeNodes): - taskType = node.task_id.getTypeCode() - - # Create information for task - # We are going down in the tree - if taskType == libsedml.SEDML_TASK_REPEATEDTASK: - taskLines = SEDMLCodeFactory.repeatedTaskToPython(doc, node=node) - - elif taskType == libsedml.SEDML_TASK: - tid = node.task_id.getId() - taskLines = SEDMLCodeFactory.simpleTaskToPython(doc=doc, node=node) - else: - lines.append("# Unsupported task: {}".format(taskType)) - warnings.warn("Unsupported task: {}".format(taskType)) - - lines.extend([" " * node.depth + line for line in taskLines]) - - ''' - @staticmethod - def simpleTaskToPython(doc, task): - """ Create python for simple task. """ - for ksub, subtask in enumerate(subtasks): - t = doc.getTask(subtask.getTask()) - - resultVariable = "__subtask__".format(t.getId()) - selections = SEDMLCodeFactory.selectionsForTask(doc=doc, task=task) - if t.getTypeCode() == libsedml.SEDML_TASK: - forLines.extend(SEDMLCodeFactory.subtaskToPython(doc, task=t, - selections=selections, - resultVariable=resultVariable)) - forLines.append("{}.extend([__subtask__])".format(task.getId())) - - elif t.getTypeCode() == libsedml.SEDML_TASK_REPEATEDTASK: - forLines.extend(SEDMLCodeFactory.repeatedTaskToPython(doc, task=t)) - forLines.append("{}.extend({})".format(task.getId(), t.getId())) - ''' - - # Collect information - # We have to go back up - # Look at next node in the treeNodes (this is the next one to write) - if kn == (len(treeNodes) - 1): - nextNode = None - else: - nextNode = treeNodes[kn + 1] - - # The next node is further up in the tree, or there is no next node - # and still nodes on the stack - if (nextNode is None) or (nextNode.depth < node.depth): - - # necessary to pop nodes from the stack and close the code - test = True - while test is True: - # stack is empty - if nodeStack.size() == 0: - test = False - continue - # try to pop next one - peek = nodeStack.peek() - if (nextNode is None) or (peek.depth > nextNode.depth): - # TODO: reset evaluation has to be defined here - # determine if it's steady state - # if taskType == libsedml.SEDML_TASK_REPEATEDTASK: - # print('task {}'.format(node.task.getId())) - # print(' peek {}'.format(peek.task.getId())) - if ( - node.task_id.getTypeCode() - == libsedml.SEDML_TASK_REPEATEDTASK - ): - # if peek.task.getTypeCode() == libsedml.SEDML_TASK_REPEATEDTASK: - # sid = task.getSimulationReference() - # simulation = doc.getSimulation(sid) - # simType = simulation.getTypeCode() - # if simType is libsedml.SEDML_SIMULATION_STEADYSTATE: - terminator = "terminate_trace({})".format( - node.task_id.getId() - ) - else: - terminator = "{}".format(node.task_id.getId()) - - lines.extend( - [ - "", - # " "*node.depth + "{}.extend({})".format(peek.task.getId(), terminator), - " " * node.depth - + "{}.extend({})".format( - peek.task_id.getId(), node.task_id.getId() - ), - ] - ) - node = nodeStack.pop() - - else: - test = False - else: - # we are going done or next subtask -> put node on stack - nodeStack.push(node) - - return "\n".join(lines) - - @staticmethod - def simpleTaskToPython(doc, node): - """Creates the simulation python code for a given taskNode. - - The taskNodes are required to handle the relationships between - RepeatedTasks, SubTasks and SimpleTasks (Task). - - :param doc: sedml document - :type doc: SEDDocument - :param node: taskNode of the current task - :type node: TaskNode - :return: - :rtype: - """ - lines = [] - task = node.task_id - lines.append("# Task: <{}>".format(task.getId())) - lines.append("{} = [None]".format(task.getId())) - - mid = task.getModelReference() - sid = task.getSimulationReference() - simulation = doc.getSimulation(sid) - - simType = simulation.getTypeCode() - algorithm = simulation.getAlgorithm() - if algorithm is None: - warnings.warn( - "Algorithm missing on simulation, defaulting to 'cvode: KISAO:0000019'" - ) - algorithm = simulation.createAlgorithm() - algorithm.setKisaoID("KISAO:0000019") - kisao = algorithm.getKisaoID() - - # is supported algorithm - if not SEDMLCodeFactory.isSupportedAlgorithmForSimulationType( - kisao=kisao, simType=simType - ): - warnings.warn( - "Algorithm {} unsupported for simulation {} type {} in task {}".format( - kisao, simulation.getId(), simType, task.getId() - ) - ) - lines.append( - "# Unsupported Algorithm {} for SimulationType {}".format( - kisao, simulation.getElementName() - ) - ) - return lines - - # set integrator/solver - integratorName = SEDMLCodeFactory.getIntegratorNameForKisaoID(kisao) - if not integratorName: - warnings.warn("No integrator exists for {} in roadrunner".format(kisao)) - return lines - - if simType is libsedml.SEDML_SIMULATION_STEADYSTATE: - lines.append("{}.setSteadyStateSolver('{}')".format(mid, integratorName)) - else: - lines.append("{}.setIntegrator('{}')".format(mid, integratorName)) - - # use fixed step by default for stochastic sims - if integratorName == "gillespie": - lines.append( - "{}.integrator.setValue('{}', {})".format( - mid, "variable_step_size", False - ) - ) - - if kisao == "KISAO:0000288": # BDF - lines.append("{}.integrator.setValue('{}', {})".format(mid, "stiff", True)) - elif kisao == "KISAO:0000280": # Adams-Moulton - lines.append("{}.integrator.setValue('{}', {})".format(mid, "stiff", False)) - - # integrator/solver settings (AlgorithmParameters) - for par in algorithm.getListOfAlgorithmParameters(): - pkey = SEDMLCodeFactory.algorithmParameterToParameterKey(par) - # only set supported algorithm paramters - if pkey: - if pkey.dtype is str: - value = "'{}'".format(pkey.value) - else: - value = pkey.value - - if value == str("inf") or pkey.value == float("inf"): - value = "float('inf')" - else: - pass - - if simType is libsedml.SEDML_SIMULATION_STEADYSTATE: - lines.append( - "{}.steadyStateSolver.setValue('{}', {})".format( - mid, pkey.key, value - ) - ) - else: - lines.append( - "{}.integrator.setValue('{}', {})".format(mid, pkey.key, value) - ) - - if simType is libsedml.SEDML_SIMULATION_STEADYSTATE: - lines.append( - "if {model}.conservedMoietyAnalysis == False: {model}.conservedMoietyAnalysis = True".format( - model=mid - ) - ) - else: - lines.append( - "if {model}.conservedMoietyAnalysis == True: {model}.conservedMoietyAnalysis = False".format( - model=mid - ) - ) - - # get parents - parents = [] - parent = node.parent - while parent is not None: - parents.append(parent) - parent = parent.parent - - # of all parents - # --------------------------- - selections = SEDMLCodeFactory.selectionsForTask(doc=doc, task=node.task_id) - for p in parents: - selections.update( - SEDMLCodeFactory.selectionsForTask(doc=doc, task=p.task_id) - ) - - # of all parents - # --------------------------- - # apply changes based on current variables, parameters and range variables - for parent in reversed(parents): - rangeId = parent.task_id.getRangeId() - helperRanges = {} - for r in parent.task_id.getListOfRanges(): - if r.getId() != rangeId: - helperRanges[r.getId()] = r - - for setValue in parent.task_id.getListOfTaskChanges(): - variables = {} - # range variables - variables[rangeId] = "__value__{}".format(rangeId) - for key in helperRanges.keys(): - variables[key] = "__value__{}".format(key) - # parameters - for par in setValue.getListOfParameters(): - variables[par.getId()] = par.getValue() - for var in setValue.getListOfVariables(): - vid = var.getId() - mid = var.getModelReference() - selection = SEDMLCodeFactory.selectionFromVariable(var, mid) - expr = selection.id - if selection.type == "concentration": - expr = "init([{}])".format(selection.id) - elif selection.type == "amount": - expr = "init({})".format(selection.id) - - # create variable - lines.append("__value__{} = {}['{}']".format(vid, mid, expr)) - # variable for replacement - variables[vid] = "__value__{}".format(vid) - - # value is calculated with the current state of model - lines.append( - SEDMLCodeFactory.targetToPython( - xpath=setValue.getTarget(), - value=evaluableMathML(setValue.getMath(), variables=variables), - modelId=setValue.getModelReference(), - ) - ) - - # handle result variable - resultVariable = "{}[0]".format(task.getId()) - - # ------------------------------------------------------------------------- - # - # ------------------------------------------------------------------------- - if simType == libsedml.SEDML_SIMULATION_UNIFORMTIMECOURSE: - lines.append("{}.timeCourseSelections = {}".format(mid, list(selections))) - - initialTime = simulation.getInitialTime() - outputStartTime = simulation.getOutputStartTime() - outputEndTime = simulation.getOutputEndTime() - numberOfPoints = simulation.getNumberOfPoints() - - # reset before simulation (see https://github.com/sys-bio/tellurium/issues/193) - lines.append("{}.reset()".format(mid)) - - # throw some points away - if abs(outputStartTime - initialTime) > 1e-6: - lines.append( - "{}.simulate(start={}, end={}, points=2)".format( - mid, initialTime, outputStartTime - ) - ) - # real simulation - lines.append( - "{} = {}.simulate(start={}, end={}, steps={})".format( - resultVariable, mid, outputStartTime, outputEndTime, numberOfPoints - ) - ) - # ------------------------------------------------------------------------- - # - # ------------------------------------------------------------------------- - elif simType == libsedml.SEDML_SIMULATION_ONESTEP: - lines.append("{}.timeCourseSelections = {}".format(mid, list(selections))) - step = simulation.getStep() - lines.append( - "{} = {}.simulate(start={}, end={}, points=2)".format( - resultVariable, mid, 0.0, step - ) - ) - - # ------------------------------------------------------------------------- - # - # ------------------------------------------------------------------------- - elif simType == libsedml.SEDML_SIMULATION_STEADYSTATE: - lines.append( - "{}.steadyStateSolver.setValue('{}', {})".format( - mid, "allow_presimulation", False - ) - ) - lines.append("{}.steadyStateSelections = {}".format(mid, list(selections))) - lines.append( - "{}.simulate()".format(mid) - ) # for stability of the steady state solver - lines.append("{} = {}.steadyStateNamedArray()".format(resultVariable, mid)) - # no need to turn this off because it will be checked before the next simulation - # lines.append("{}.conservedMoietyAnalysis = False".format(mid)) - - # ------------------------------------------------------------------------- - # - # ------------------------------------------------------------------------- - else: - lines.append("# Unsupported simulation: {}".format(simType)) - - return lines - - @staticmethod - def repeatedTaskToPython(doc, node): - """Create python for RepeatedTask. - - Must create - - the ranges (Ranges) - - apply all changes (SetValues) - """ - # storage of results - task = node.task_id - lines = ["", "{} = []".format(task.getId())] - - # - # master range - rangeId = task.getRangeId() - masterRange = task.getRange(rangeId) - if masterRange.getTypeCode() == libsedml.SEDML_RANGE_UNIFORMRANGE: - lines.extend(SEDMLCodeFactory.uniformRangeToPython(masterRange)) - elif masterRange.getTypeCode() == libsedml.SEDML_RANGE_VECTORRANGE: - lines.extend(SEDMLCodeFactory.vectorRangeToPython(masterRange)) - elif masterRange.getTypeCode() == libsedml.SEDML_RANGE_FUNCTIONALRANGE: - warnings.warn("FunctionalRange for master range not supported in task.") - # lock-in ranges - for r in task.getListOfRanges(): - if r.getId() != rangeId: - if r.getTypeCode() == libsedml.SEDML_RANGE_UNIFORMRANGE: - lines.extend(SEDMLCodeFactory.uniformRangeToPython(r)) - elif r.getTypeCode() == libsedml.SEDML_RANGE_VECTORRANGE: - lines.extend(SEDMLCodeFactory.vectorRangeToPython(r)) - - # - # iterate master range - lines.append( - "for __k__{}, __value__{} in enumerate(__range__{}):".format( - rangeId, rangeId, rangeId - ) - ) - - # Everything from now on is done in every iteration of the range - # We have to collect & intent all lines in the loop) - forLines = [] - - # definition of lock-in ranges - helperRanges = {} - for r in task.getListOfRanges(): - if r.getId() != rangeId: - helperRanges[r.getId()] = r - if r.getTypeCode() in [ - libsedml.SEDML_RANGE_UNIFORMRANGE, - libsedml.SEDML_RANGE_VECTORRANGE, - ]: - forLines.append( - "__value__{} = __range__{}[__k__{}]".format( - r.getId(), r.getId(), rangeId - ) - ) - - # - if r.getTypeCode() == libsedml.SEDML_RANGE_FUNCTIONALRANGE: - variables = {} - # range variables - variables[rangeId] = "__value__{}".format(rangeId) - for key in helperRanges.keys(): - variables[key] = "__value__{}".format(key) - # parameters - for par in r.getListOfParameters(): - variables[par.getId()] = par.getValue() - for var in r.getListOfVariables(): - vid = var.getId() - mid = var.getModelReference() - selection = SEDMLCodeFactory.selectionFromVariable(var, mid) - expr = selection.id - if selection.type == "concentration": - expr = "[{}]".format(selection.id) - lines.append("__value__{} = {}['{}']".format(vid, mid, expr)) - variables[vid] = "__value__{}".format(vid) - - # value is calculated with the current state of model - value = evaluableMathML(r.getMath(), variables=variables) - forLines.append("__value__{} = {}".format(r.getId(), value)) - - # - # models to reset via task tree below node - mids = set([]) - for child in node: - t = child.task_id - if t.getTypeCode() == libsedml.SEDML_TASK: - mids.add(t.getModelReference()) - # reset models referenced in tree below task - for mid in mids: - if task.getResetModel(): - # reset before every iteration - forLines.append("{}.reset()".format(mid)) - else: - # reset before first iteration - forLines.append("if __k__{} == 0:".format(rangeId)) - forLines.append(" {}.reset()".format(mid)) - - # add lines - lines.extend(" " + line for line in forLines) - - return lines - - ################################################################################################ - - @staticmethod - def getDataGeneratorsForTask(doc, task): - """Get the DataGenerators which reference the given task. - - :param doc: - :type doc: - :param task: - :type task: - :return: - :rtype: - """ - dgs = [] - for dg in doc.getListOfDataGenerators(): - for var in dg.getListOfVariables(): - if var.getTaskReference() == task.getId(): - dgs.append(dg) - break # the DataGenerator is added, no need to look at rest of variables - return dgs - - @staticmethod - def selectionsForTask(doc, task): - """Populate variable lists from the data generators for the given task. - - These are the timeCourseSelections and steadyStateSelections - in RoadRunner. - - Search all data generators for variables which have to be part of the simulation. - """ - modelId = task.getModelReference() - selections = set() - for dg in doc.getListOfDataGenerators(): - for var in dg.getListOfVariables(): - if var.getTaskReference() == task.getId(): - selection = SEDMLCodeFactory.selectionFromVariable(var, modelId) - expr = selection.id - if selection.type == "concentration": - expr = "[{}]".format(selection.id) - selections.add(expr) - - return selections - - @staticmethod - def uniformRangeToPython(r): - """Create python lines for uniform range. - :param r: - :type r: - :return: - :rtype: - """ - lines = [] - rId = r.getId() - rStart = r.getStart() - rEnd = r.getEnd() - rPoints = r.getNumberOfPoints() + 1 # One point more than number of points - rType = r.getType() - if rType in ["Linear", "linear"]: - lines.append( - "__range__{} = np.linspace(start={}, stop={}, num={})".format( - rId, rStart, rEnd, rPoints - ) - ) - elif rType in ["Log", "log"]: - lines.append( - "__range__{} = np.logspace(start={}, stop={}, num={})".format( - rId, rStart, rEnd, rPoints - ) - ) - else: - warnings.warn("Unsupported range type in UniformRange: {}".format(rType)) - return lines - - @staticmethod - def vectorRangeToPython(r): - lines = [] - __range = np.zeros(shape=[r.getNumValues()]) - for k, v in enumerate(r.getValues()): - __range[k] = v - lines.append("__range__{} = {}".format(r.getId(), list(__range))) - return lines - - @staticmethod - def isSupportedAlgorithmForSimulationType(kisao, simType): - """Check Algorithm Kisao Id is supported for simulation. - - :return: is supported - :rtype: bool - """ - supported = [] - if simType == libsedml.SEDML_SIMULATION_UNIFORMTIMECOURSE: - supported = KISAOS_UNIFORMTIMECOURSE - elif simType == libsedml.SEDML_SIMULATION_ONESTEP: - supported = KISAOS_ONESTEP - elif simType == libsedml.SEDML_SIMULATION_STEADYSTATE: - supported = KISAOS_STEADYSTATE - return kisao in supported - - @staticmethod - def getIntegratorNameForKisaoID(kid): - """RoadRunner integrator name for algorithm KisaoID. - - :param kid: KisaoID - :type kid: str - :return: RoadRunner integrator name. - :rtype: str - """ - if kid in KISAOS_NLEQ: - return "nleq2" - if kid in KISAOS_CVODE: - return "cvode" - if kid in KISAOS_GILLESPIE: - return "gillespie" - if kid in KISAOS_RK4: - return "rk4" - if kid in KISAOS_RK45: - return "rk45" - if kid in KISAOS_LSODA: - warnings.warn("Tellurium does not support LSODA. Using CVODE instead.") - return "cvode" # just use cvode - return None - - @staticmethod - def algorithmParameterToParameterKey(par): - """Resolve the mapping between parameter keys and roadrunner integrator keys.""" - ParameterKey = namedtuple("ParameterKey", "key value dtype") - kid = par.getKisaoID() - value = par.getValue() - - if kid in KISAOS_ALGORITHMPARAMETERS: - # algorithm parameter is in the list of parameters - key, dtype = KISAOS_ALGORITHMPARAMETERS[kid] - if dtype is bool: - # transform manually ! (otherwise all strings give True) - if value == "true": - value = True - elif value == "false": - value = False - else: - # cast to data type of parameter - value = dtype(value) - return ParameterKey(key, value, dtype) - else: - # algorithm parameter not supported - warnings.warn("Unsupported AlgorithmParameter: {} = {})".format(kid, value)) - return None - - @staticmethod - def targetToPython(xpath, value, modelId): - """Creates python line for given xpath target and value. - :param xpath: - :type xpath: - :param value: - :type value: - :return: - :rtype: - """ - target = SEDMLCodeFactory._resolveXPath(xpath, modelId) - if target: - # initial concentration - if target.type == "concentration": - expr = "init([{}])".format(target.id) - # initial amount - elif target.type == "amount": - expr = "init({})".format(target.id) - # other (parameter, flux, ...) - else: - expr = target.id - line = "{}['{}'] = {}".format(modelId, expr, value) - else: - line = "# Unsupported target xpath: {}".format(xpath) - - return line - - @staticmethod - def selectionFromVariable(var, modelId): - """Resolves the selection for the given variable. - - First checks if the variable is a symbol and returns the symbol. - If no symbol is set the xpath of the target is resolved - and used in the selection - - :param var: variable to resolve - :type var: SedVariable - :return: a single selection - :rtype: Selection (namedtuple: id type) - """ - Selection = namedtuple("Selection", "id type") - - # parse symbol expression - if var.isSetSymbol(): - cvs = var.getSymbol() - astr = cvs.rsplit("symbol:") - sid = astr[1] - return Selection(sid, "symbol") - # use xpath - elif var.isSetTarget(): - xpath = var.getTarget() - target = SEDMLCodeFactory._resolveXPath(xpath, modelId) - return Selection(target.id, target.type) - - else: - warnings.warn("Unrecognized Selection in variable") - return None - - @staticmethod - def _resolveXPath(xpath, modelId): - """Resolve the target from the xpath expression. - - A single target in the model corresponding to the modelId is resolved. - Currently, the model is not used for xpath resolution. - - :param xpath: xpath expression. - :type xpath: str - :param modelId: id of model in which xpath should be resolved - :type modelId: str - :return: single target of xpath expression - :rtype: Target (namedtuple: id type) - """ - # TODO: via better xpath expression - # get type from the SBML document for the given id. - # The xpath expression can be very general and does not need to contain the full - # xml path - # For instance: - # /sbml:sbml/sbml:model/descendant::*[@id='S1'] - # has to resolve to species. - # TODO: figure out concentration or amount (from SBML document) - # FIXME: getting of sids, pids not very robust, handle more cases (rules, reactions, ...) - - Target = namedtuple("Target", "id type") - - def getId(xpath): - xpath = xpath.replace('"', "'") - match = re.findall(r"id='(.*?)'", xpath) - if (match is None) or (len(match) is 0): - warnings.warn("Xpath could not be resolved: {}".format(xpath)) - return match[0] - - # parameter value change - if ("model" in xpath) and ("parameter" in xpath): - return Target(getId(xpath), "parameter") - # species concentration change - elif ("model" in xpath) and ("species" in xpath): - return Target(getId(xpath), "concentration") - # other - elif ("model" in xpath) and ("id" in xpath): - return Target(getId(xpath), "other") - # cannot be parsed - else: - raise ValueError("Unsupported target in xpath: {}".format(xpath)) - - @staticmethod - def dataGeneratorToPython(doc, generator): - """Create variable from the data generators and the simulation results and data sources. - - The data of repeatedTasks is handled differently depending - on if reset=True or reset=False. - reset=True: - every repeat is a single curve, i.e. the data is a list of data - reset=False: - all curves belong to a single simulation and are concatenated to one dataset - """ - lines = [] - gid = generator.getId() - mathml = generator.getMath() - - # create variables - variables = {} - for par in generator.getListOfParameters(): - variables[par.getId()] = par.getValue() - for var in generator.getListOfVariables(): - varId = var.getId() - variables[varId] = "__var__{}".format(varId) - - # create python for variables - for var in generator.getListOfVariables(): - varId = var.getId() - taskId = var.getTaskReference() - task = doc.getTask(taskId) - - # simulation data - if task is not None: - modelId = task.getModelReference() - - selection = SEDMLCodeFactory.selectionFromVariable(var, modelId) - isTime = False - if selection.type == "symbol" and selection.id == "time": - isTime = True - - resetModel = True - if task.getTypeCode() == libsedml.SEDML_TASK_REPEATEDTASK: - resetModel = task.getResetModel() - - sid = selection.id - if selection.type == "concentration": - sid = "[{}]".format(selection.id) - - # Series of curves - if resetModel is True: - # If each entry in the task consists of a single point (e.g. steady state scan) - # , concatenate the points. Otherwise, plot as separate curves. - import os - - if "PROCESS_TRACE" in os.environ and os.environ["PROCESS_TRACE"]: - lines.append( - "__var__{} = np.concatenate([process_trace(sim['{}']) for sim in {}])".format( - varId, sid, taskId - ) - ) - else: - lines.append( - "__var__{} = np.concatenate([sim['{}'] for sim in {}])".format( - varId, sid, taskId - ) - ) - else: - # One curve via time adjusted concatenate - if isTime is True: - lines.append( - "__offsets__{} = np.cumsum(np.array([sim['{}'][-1] for sim in {}]))".format( - taskId, sid, taskId - ) - ) - lines.append( - "__offsets__{} = np.insert(__offsets__{}, 0, 0)".format( - taskId, taskId - ) - ) - lines.append( - "__var__{} = np.transpose(np.array([sim['{}']+__offsets__{}[k] for k, sim in enumerate({})]))".format( - varId, sid, taskId, taskId - ) - ) - lines.append( - "__var__{} = np.concatenate(np.transpose(__var__{}))".format( - varId, varId - ) - ) - else: - lines.append( - "__var__{} = np.transpose(np.array([sim['{}'] for sim in {}]))".format( - varId, sid, taskId - ) - ) - lines.append( - "__var__{} = np.concatenate(np.transpose(__var__{}))".format( - varId, varId - ) - ) - lines.append("if len(__var__{}.shape) == 1:".format(varId)) - lines.append(" __var__{}.shape += (1,)".format(varId)) - - # check for data sources - else: - target = var.getTarget() - if target.startswith("#"): - sid = target[1:] - lines.append("__var__{} = {}".format(varId, sid)) - else: - warnings.warn( - "Unknown target in variable, no reference to SId: {}".format( - target - ) - ) - - # calculate data generator - value = evaluableMathML(mathml, variables=variables, array=True) - lines.append("{} = {}".format(gid, value)) - - return "\n".join(lines) - - def outputToPython(self, doc, output): - """Create output""" - lines = [] - typeCode = output.getTypeCode() - if typeCode == libsedml.SEDML_OUTPUT_REPORT: - lines.extend(SEDMLCodeFactory.outputReportToPython(self, doc, output)) - elif typeCode == libsedml.SEDML_OUTPUT_PLOT2D: - lines.extend(SEDMLCodeFactory.outputPlot2DToPython(self, doc, output)) - elif typeCode == libsedml.SEDML_OUTPUT_PLOT3D: - lines.extend(SEDMLCodeFactory.outputPlot3DToPython(self, doc, output)) - else: - warnings.warn( - "# Unsupported output type '{}' in output {}".format( - output.getElementName(), output.getId() - ) - ) - return "\n".join(lines) - - def outputReportToPython(self, doc, output): - """OutputReport - - :param doc: - :type doc: SedDocument - :param output: - :type output: SedOutputReport - :return: list of python lines - :rtype: list(str) - """ - lines = [] - headers = [] - dgIds = [] - columns = [] - for dataSet in output.getListOfDataSets(): - # these are the columns - headers.append(dataSet.getLabel()) - # data generator (the id is the id of the data in python) - dgId = dataSet.getDataReference() - dgIds.append(dgId) - columns.append("{}[:,k]".format(dgId)) - # create data frames for the repeats - lines.append("__dfs__{} = []".format(output.getId())) - lines.append("for k in range({}.shape[1]):".format(dgIds[0])) - lines.append( - " __df__k = pandas.DataFrame(np.column_stack(" - + str(columns).replace("'", "") - + "), \n columns=" - + str(headers) - + ")" - ) - lines.append(" __dfs__{}.append(__df__k)".format(output.getId())) - # save as variable in Tellurium - lines.append(" te.setLastReport(__df__k)".format(output.getId())) - if self.saveOutputs and self.createOutputs: - - lines.append( - " filename = os.path.join('{}', '{}.{}')".format( - self.outputDir, output.getId(), self.reportFormat - ) - ) - lines.append( - " __df__k.to_csv(filename, sep=',', index=False)".format( - output.getId() - ) - ) - lines.append( - " print('Report {}: {{}}'.format(filename))".format(output.getId()) - ) - return lines - - @staticmethod - def outputPlotSettings(): - """Settings for all plot types. - - :return: - :rtype: - """ - PlotSettings = namedtuple( - "PlotSettings", - "colors, figsize, dpi, facecolor, edgecolor, linewidth, marker, markersize, alpha", - ) - - # all lines of same cuve have same color - settings = PlotSettings( - colors=[ - u"#1f77b4", - u"#ff7f0e", - u"#2ca02c", - u"#d62728", - u"#9467bd", - u"#8c564b", - u"#e377c2", - u"#7f7f7f", - u"#bcbd22", - u"#17becf", - ], - figsize=(9, 5), - dpi=80, - facecolor="w", - edgecolor="k", - linewidth=1.5, - marker="", - markersize=3.0, - alpha=1.0, - ) - return settings - - def outputPlot2DToPython(self, doc, output): - """OutputReport - - If workingDir is provided the plot is saved in the workingDir. - :param doc: - :type doc: SedDocument - :param output: - :type output: SedOutputReport - :return: list of python lines - :rtype: list(str) - """ - - # TODO: logX and logY not applied - lines = [] - settings = SEDMLCodeFactory.outputPlotSettings() - - # figure title - title = output.getId() - if output.isSetName(): - title = "{}".format(output.getName()) - - # xtitle - oneXLabel = True - allXLabel = None - for kc, curve in enumerate(output.getListOfCurves()): - xId = curve.getXDataReference() - dgx = doc.getDataGenerator(xId) - xLabel = xId - if dgx.isSetName(): - xLabel = "{}".format(dgx.getName()) - - # do all curves have the same xLabel - if kc == 0: - allXLabel = xLabel - elif xLabel != allXLabel: - oneXLabel = False - xtitle = "" - if oneXLabel: - xtitle = allXLabel - - lines.append("_stacked = False") - # stacking, currently disabled - # lines.append("_stacked = False") - # lines.append("_engine = te.getPlottingEngine()") - # for kc, curve in enumerate(output.getListOfCurves()): - # xId = curve.getXDataReference() - # lines.append("if {}.shape[1] > 1 and te.getDefaultPlottingEngine() == 'plotly':".format(xId)) - # lines.append(" stacked=True") - lines.append("if _stacked:") - lines.append( - " tefig = te.getPlottingEngine().newStackedFigure(title='{}', xtitle='{}')".format( - title, xtitle - ) - ) - lines.append("else:") - lines.append( - " tefig = te.nextFigure(title='{}', xtitle='{}')\n".format(title, xtitle) - ) - - for kc, curve in enumerate(output.getListOfCurves()): - logX = curve.getLogX() - logY = curve.getLogY() - xId = curve.getXDataReference() - yId = curve.getYDataReference() - dgx = doc.getDataGenerator(xId) - dgy = doc.getDataGenerator(yId) - color = settings.colors[kc % len(settings.colors)] - tag = "tag{}".format(kc) - - yLabel = yId - if curve.isSetName(): - yLabel = "{}".format(curve.getName()) - elif dgy.isSetName(): - yLabel = "{}".format(dgy.getName()) - - # FIXME: add all the additional information to the plot, i.e. the settings and styles for a given curve - - lines.append("for k in range({}.shape[1]):".format(xId)) - lines.append(" extra_args = {}") - lines.append(" if k == 0:") - lines.append(" extra_args['name'] = '{}'".format(yLabel)) - lines.append( - " tefig.addXYDataset({xarr}[:,k], {yarr}[:,k], color='{color}', tag='{tag}', logx={logx}, logy={logy}, **extra_args)".format( - xarr=xId, yarr=yId, color=color, tag=tag, logx=logX, logy=logY - ) - ) - - # FIXME: endpoints must be handled via plotting functions - # lines.append(" fix_endpoints({}[:,k], {}[:,k], color='{}', tag='{}', fig=tefig)".format(xId, yId, color, tag)) - lines.append("if te.tiledFigure():\n") - lines.append(" if te.tiledFigure().renderIfExhausted():\n") - lines.append(" te.clearTiledFigure()\n") - lines.append("else:\n") - lines.append(" fig = tefig.render()\n") - - if self.saveOutputs and self.createOutputs: - # FIXME: only working for matplotlib - lines.append( - "if str(te.getPlottingEngine()) == '':".format( - self.outputDir, output.getId(), self.plotFormat - ) - ) - lines.append( - " filename = os.path.join('{}', '{}.{}')".format( - self.outputDir, output.getId(), self.plotFormat - ) - ) - lines.append( - " fig.savefig(filename, format='{}', bbox_inches='tight')".format( - self.plotFormat - ) - ) - lines.append( - " print('Figure {}: {{}}'.format(filename))".format(output.getId()) - ) - lines.append("") - return lines - - def outputPlot3DToPython(self, doc, output): - """OutputPlot3D - - :param doc: - :type doc: SedDocument - :param output: - :type output: SedOutputPlot3D - :return: list of python lines - :rtype: list(str) - """ - # TODO: handle mix of log and linear axis - settings = SEDMLCodeFactory.outputPlotSettings() - lines = [] - lines.append("from mpl_toolkits.mplot3d import Axes3D") - lines.append( - "fig = plt.figure(num=None, figsize={}, dpi={}, facecolor='{}', edgecolor='{}')".format( - settings.figsize, settings.dpi, settings.facecolor, settings.edgecolor - ) - ) - lines.append("from matplotlib import gridspec") - lines.append("__gs = gridspec.GridSpec(1, 2, width_ratios=[3, 1])") - lines.append("ax = plt.subplot(__gs[0], projection='3d')") - # lines.append("ax = fig.gca(projection='3d')") - - title = output.getId() - if output.isSetName(): - title = output.getName() - - oneXLabel = True - oneYLabel = True - allXLabel = None - allYLabel = None - - for kc, surf in enumerate(output.getListOfSurfaces()): - logX = surf.getLogX() - logY = surf.getLogY() - logZ = surf.getLogZ() - xId = surf.getXDataReference() - yId = surf.getYDataReference() - zId = surf.getZDataReference() - dgx = doc.getDataGenerator(xId) - dgy = doc.getDataGenerator(yId) - dgz = doc.getDataGenerator(zId) - color = settings.colors[kc % len(settings.colors)] - - zLabel = zId - if surf.isSetName(): - zLabel = surf.getName() - elif dgy.isSetName(): - zLabel = dgz.getName() - - xLabel = xId - if dgx.isSetName(): - xLabel = dgx.getName() - yLabel = yId - if dgy.isSetName(): - yLabel = dgy.getName() - - # do all curves have the same xLabel & yLabel - if kc == 0: - allXLabel = xLabel - allYLabel = yLabel - if xLabel != allXLabel: - oneXLabel = False - if yLabel != allYLabel: - oneYLabel = False - - lines.append("for k in range({}.shape[1]):".format(xId)) - lines.append(" if k == 0:") - lines.append( - " ax.plot({}[:,k], {}[:,k], {}[:,k], marker = '{}', color='{}', linewidth={}, markersize={}, alpha={}, label='{}')".format( - xId, - yId, - zId, - settings.marker, - color, - settings.linewidth, - settings.markersize, - settings.alpha, - zLabel, - ) - ) - lines.append(" else:") - lines.append( - " ax.plot({}[:,k], {}[:,k], {}[:,k], marker = '{}', color='{}', linewidth={}, markersize={}, alpha={})".format( - xId, - yId, - zId, - settings.marker, - color, - settings.linewidth, - settings.markersize, - settings.alpha, - ) - ) - - lines.append("ax.set_title('{}', fontweight='bold')".format(title)) - if oneXLabel: - lines.append("ax.set_xlabel('{}', fontweight='bold')".format(xLabel)) - if oneYLabel: - lines.append("ax.set_ylabel('{}', fontweight='bold')".format(yLabel)) - if len(output.getListOfSurfaces()) == 1: - lines.append("ax.set_zlabel('{}', fontweight='bold')".format(zLabel)) - - lines.append( - "__lg = plt.legend(bbox_to_anchor=(1.05, 1), loc=2, borderaxespad=0.)" - ) - lines.append("__lg.draw_frame(False)") - lines.append("plt.setp(__lg.get_texts(), fontsize='small')") - lines.append("plt.setp(__lg.get_texts(), fontweight='bold')") - lines.append("plt.tick_params(axis='both', which='major', labelsize=10)") - lines.append("plt.tick_params(axis='both', which='minor', labelsize=8)") - lines.append( - "plt.savefig(os.path.join(workingDir, '{}.png'), dpi=100)".format( - output.getId() - ) - ) - lines.append("plt.show()".format(title)) - - return lines - - -################################################################################################## -class SEDMLTools(object): - """Helper functions to work with sedml.""" - - INPUT_TYPE_STR = "SEDML_STRING" - INPUT_TYPE_FILE_SEDML = "SEDML_FILE" - INPUT_TYPE_FILE_COMBINE = "COMBINE_FILE" # includes .sedx archives - - @classmethod - def checkSEDMLDocument(cls, doc): - """Checks the SedDocument for errors. - Raises IOError if error exists. - :param doc: - :type doc: - """ - errorlog = doc.getErrorLog() - msg = errorlog.toString() - if doc.getErrorLog().getNumFailsWithSeverity(libsedml.LIBSEDML_SEV_ERROR) > 0: - # FIXME: workaround for https://github.com/fbergmann/libSEDML/issues/47 - warnings.warn(msg) - # raise IOError(msg) - if errorlog.getNumFailsWithSeverity(libsedml.LIBSEDML_SEV_FATAL) > 0: - # raise IOError(msg) - warnings.warn(msg) - if errorlog.getNumFailsWithSeverity(libsedml.LIBSEDML_SEV_WARNING) > 0: - warnings.warn(msg) - if errorlog.getNumFailsWithSeverity(libsedml.LIBSEDML_SEV_SCHEMA_ERROR) > 0: - warnings.warn(msg) - if errorlog.getNumFailsWithSeverity(libsedml.LIBSEDML_SEV_GENERAL_WARNING) > 0: - warnings.warn(msg) - - @classmethod - def readSEDMLDocument(cls, inputStr, workingDir): - """Parses SedMLDocument from given input. - - :return: dictionary of SedDocument, inputType and working directory. - :rtype: {doc, inputType, workingDir} - """ - - # SEDML-String - if not os.path.exists(inputStr): - try: - from xml.etree import ElementTree - - x = ElementTree.fromstring(inputStr) - # is parsable xml string - doc = libsedml.readSedMLFromString(inputStr) - inputType = cls.INPUT_TYPE_STR - if workingDir is None: - workingDir = os.getcwd() - - except ElementTree.ParseError: - if not os.path.exists(inputStr): - raise IOError("SED-ML String is not valid XML:", inputStr) - - # SEDML-File - else: - filename, extension = os.path.splitext(os.path.basename(inputStr)) - - # Archive - if zipfile.is_zipfile(inputStr): - omexPath = inputStr - inputType = cls.INPUT_TYPE_FILE_COMBINE - - # in case of sedx and combine a working directory is created - # in which the files are extracted - if workingDir is None: - extractDir = os.path.join( - os.path.dirname(os.path.realpath(omexPath)), - "_te_{}".format(filename), - ) - else: - extractDir = workingDir - - # TODO: refactor this - # extract the archive to working directory - CombineArchive.extractArchive(omexPath, extractDir) - # get SEDML files from archive - sedmlFiles = CombineArchive.filePathsFromExtractedArchive( - extractDir, filetype="sed-ml" - ) - - if len(sedmlFiles) == 0: - raise IOError("No SEDML files found in archive.") - - # FIXME: there could be multiple SEDML files in archive (currently only first used) - # analogue to executeOMEX - if len(sedmlFiles) > 1: - warnings.warn( - "More than one sedml file in archive, only processing first one." - ) - - sedmlFile = sedmlFiles[0] - doc = libsedml.readSedMLFromFile(sedmlFile) - # we have to work relative to the SED-ML file - workingDir = os.path.dirname(sedmlFile) - - cls.checkSEDMLDocument(doc) - - # SEDML single file - elif os.path.isfile(inputStr): - if extension not in [".sedml", ".xml"]: - raise IOError( - "SEDML file should have [.sedml|.xml] extension:", inputStr - ) - inputType = cls.INPUT_TYPE_FILE_SEDML - doc = libsedml.readSedMLFromFile(inputStr) - cls.checkSEDMLDocument(doc) - # working directory is where the sedml file is - if workingDir is None: - workingDir = os.path.dirname(os.path.realpath(inputStr)) - - return {"doc": doc, "inputType": inputType, "workingDir": workingDir} - - @staticmethod - def resolveModelChanges(doc): - """Resolves the original source model and full change lists for models. - - Going through the tree of model upwards until root is reached and - collecting changes on the way (example models m* and changes c*) - m1 (source) -> m2 (c1, c2) -> m3 (c3, c4) - resolves to - m1 (source) [] - m2 (source) [c1,c2] - m3 (source) [c1,c2,c3,c4] - The order of changes is important (at least between nodes on different - levels of hierarchies), because later changes of derived models could - reverse earlier changes. - - Uses recursive search strategy, which should be okay as long as the model tree hierarchy is - not getting to big. - """ - # initial dicts (handle source & change information for single node) - model_sources = {} - model_changes = {} - - for m in doc.getListOfModels(): - mid = m.getId() - source = m.getSource() - model_sources[mid] = source - changes = [] - # store the changes unique for this model - for c in m.getListOfChanges(): - changes.append(c) - model_changes[mid] = changes - - # recursive search for original model and store the - # changes which have to be applied in the list of changes - def findSource(mid, changes): - # mid is node above - if mid in model_sources and not model_sources[mid] == mid: - # add changes for node - for c in model_changes[mid]: - changes.append(c) - # keep looking deeper - return findSource(model_sources[mid], changes) - # the source is no longer a key in the sources, it is the source - return mid, changes - - all_changes = {} - - mids = [m.getId() for m in doc.getListOfModels()] - for mid in mids: - source, changes = findSource(mid, changes=list()) - model_sources[mid] = source - all_changes[mid] = changes[::-1] - - return model_sources, all_changes - - -def process_trace(trace): - """If each entry in the task consists of a single point - (e.g. steady state scan), concatenate the points. - Otherwise, plot as separate curves.""" - if trace.size > 1: - if len(trace.shape) == 1: - return np.concatenate((np.atleast_1d(trace), np.atleast_1d(np.nan))) - elif len(trace.shape) == 2: - result = np.vstack( - (np.atleast_1d(trace), np.full((1, trace.shape[-1]), np.nan)) - ) - return result - else: - return np.atleast_1d(trace) - - -def terminate_trace(trace): - """If each entry in the task consists of a single point - (e.g. steady state scan), concatenate the points. - Otherwise, plot as separate curves.""" - warnings.warn("don't use this", DeprecationWarning) - - if isinstance(trace, list): - if ( - len(trace) > 0 - and not isinstance(trace[-1], list) - and not isinstance(trace[-1], dict) - ): - # if len(trace) > 2 and isinstance(trace[-1], dict): - # e = np.array(trace[-1], copy=True) - e = {} - for name in trace[-1].colnames: - e[name] = np.atleast_1d(np.nan) - # print('e:') - # print(e) - return trace + [e] - return trace - - -def fix_endpoints(x, y, color, tag, fig): - """Adds endpoint markers wherever there is a discontinuity in the data.""" - warnings.warn("don't use this", DeprecationWarning) - # expect x and y to be 1d - if len(x.shape) > 1: - raise RuntimeError("Expected x to be 1d") - if len(y.shape) > 1: - raise RuntimeError("Expected y to be 1d") - x_aug = np.concatenate( - (np.atleast_1d(np.nan), np.atleast_1d(x), np.atleast_1d(np.nan)) - ) - y_aug = np.concatenate( - (np.atleast_1d(np.nan), np.atleast_1d(y), np.atleast_1d(np.nan)) - ) - w = np.argwhere(np.isnan(x_aug)) - - endpoints_x = [] - endpoints_y = [] - - for begin, end in ((int(w[k] + 1), int(w[k + 1])) for k in range(w.shape[0] - 1)): - if begin != end: - # print('begin {}, end {}'.format(begin, end)) - x_values = x_aug[begin:end] - x_identical = np.all(x_values == x_values[0]) - y_values = y_aug[begin:end] - y_identical = np.all(y_values == y_values[0]) - # print('x_values') - # print(x_values) - # print('x identical? {}'.format(x_identical)) - # print('y_values') - # print(y_values) - # print('y identical? {}'.format(y_identical)) - - if x_identical and y_identical: - # get the coords for the new markers - x_begin = x_values[0] - x_end = x_values[-1] - y_begin = y_values[0] - y_end = y_values[-1] - - # append to the lists - endpoints_x += [x_begin, x_end] - endpoints_y += [y_begin, y_end] - - if endpoints_x: - fig.addXYDataset( - np.array(endpoints_x), - np.array(endpoints_y), - color=color, - tag=tag, - mode="markers", - ) - - -################################################################################################## -if __name__ == "__main__": - import os - - from tellurium.tests.testdata import OMEX_TEST_DIR, SEDML_TEST_DIR - - def testInput(sedmlInput): - """Test function run on inputStr.""" - print("\n", "*" * 100) - print(sedmlInput) - print("*" * 100) - factory = SEDMLCodeFactory(sedmlInput) - - # create python file - python_str = factory.toPython() - realPath = os.path.realpath(sedmlInput) - with open(sedmlInput + ".py", "w") as f: - f.write(python_str) - - # execute python - factory.executePython() - - # testInput(os.path.join(sedmlDir, "sedMLBIOM21.sedml")) - - # Check sed-ml files - for fname in sorted(os.listdir(SEDML_TEST_DIR)): - if fname.endswith(".sedml"): - testInput(os.path.join(SEDML_TEST_DIR, fname)) - - # Check sedx archives - for fname in sorted(os.listdir(OMEX_TEST_DIR)): - if fname.endswith(".sedx"): - testInput(os.path.join(OMEX_TEST_DIR, fname)) diff --git a/src/sbmlsim/experiment/experiment.py b/src/sbmlsim/experiment/experiment.py index 595b4e26..f591935f 100644 --- a/src/sbmlsim/experiment/experiment.py +++ b/src/sbmlsim/experiment/experiment.py @@ -451,7 +451,7 @@ def _run_tasks(self, simulator, reduced_selections: bool = True): # load model in simulator model: AbstractModel = self._models[model_id] - logger.info(f"set model: {type(model)}: {model=}") + # logger.info(f"set model: {type(model)}: {model=}") simulator.set_model(model=model) logger.info("set selections") @@ -471,7 +471,7 @@ def _run_tasks(self, simulator, reduced_selections: bool = True): # use the complete selection simulator.set_timecourse_selections(selections=None) - logger.info("normalize changes") + logger.debug("normalize changes") # normalize model changes (these must be set in simulation!) model.normalize(uinfo=model.uinfo) @@ -490,7 +490,7 @@ def _run_tasks(self, simulator, reduced_selections: bool = True): sim = deepcopy(sim) sim.add_model_changes(model.changes) - logger.info("running timecourse simulation") + logger.debug("running timecourse simulation") if isinstance(sim, TimecourseSim): self._results[task_key] = simulator.run_timecourse(sim) elif isinstance(sim, ScanSim): diff --git a/src/sbmlsim/experiment/runner.py b/src/sbmlsim/experiment/runner.py index ce8e02b1..7ca1b3fa 100644 --- a/src/sbmlsim/experiment/runner.py +++ b/src/sbmlsim/experiment/runner.py @@ -12,6 +12,7 @@ from typing import Dict, List, Optional, Tuple, Type, Union, Set from sbmlutils import log +from sbmlutils.console import console from sbmlsim.experiment import ExperimentResult, SimulationExperiment from sbmlsim.model import RoadrunnerSBMLModel @@ -130,7 +131,8 @@ def run_experiments( exp_results = [] experiment: SimulationExperiment for sid, experiment in self.experiments.items(): - logger.info(f"Running SimulationExperiment: {sid}") + console.rule(style="white") + logger.info(f"Running SimulationExperiment: '{sid}'") # ExperimentResult used to create report result = experiment.run( diff --git a/src/sbmlsim/model/model_roadrunner.py b/src/sbmlsim/model/model_roadrunner.py index 6d8da532..31a6874c 100644 --- a/src/sbmlsim/model/model_roadrunner.py +++ b/src/sbmlsim/model/model_roadrunner.py @@ -54,17 +54,20 @@ def __init__( raise ValueError(f"language_type not supported '{self.language_type}'.") # load model - self.state_path: Path = self.get_state_path() + # logger.info("load model") self.r: Optional[roadrunner.RoadRunner] = self.load_roadrunner_model( - source=self.source, state_path=self.state_path + source=self.source ) + # logger.info(self.r) # set selections + # logger.info("set selections") self.selections = self.set_timecourse_selections( self.r, selections=self.selections ) # set integrator settings + # logger.info("set integrator settings") if settings: RoadrunnerSBMLModel.set_integrator_settings(self.r, **settings) @@ -97,20 +100,9 @@ def from_abstract_model( settings=settings, ) - def get_state_path(self) -> Optional[Path]: - """Get path of the state file. - - The state file is a binary file which allows fast model loading. - """ - if self.source.is_path(): - md5 = md5_for_path(self.source.path) - return Path(f"{self.source.path}_rr{roadrunner.__version__}_{md5}.state") - else: - return None - @classmethod def load_roadrunner_model( - cls, source: Source, state_path: Path = None + cls, source: Source, ) -> roadrunner.RoadRunner: """Load model from given source. @@ -123,31 +115,47 @@ def load_roadrunner_model( # load model if source.is_path(): - if state_path and state_path.exists(): - logger.debug(f"Load model from state: '{state_path}'") - r = roadrunner.RoadRunner() - r.loadState(str(state_path)) - logger.debug(f"Model loaded from state: '{state_path}'") - else: - logger.info(f"Load model from SBML: '{source.path.resolve()}'") - r = roadrunner.RoadRunner(str(source.path)) - # save state path - if state_path: - r.saveState(str(state_path)) - logger.info(f"Save state: '{state_path}'") + + sbml_path: Path = source.path + state_path: Path = RoadrunnerSBMLModel.get_state_path( + sbml_path=sbml_path + ) + + r = roadrunner.RoadRunner(str(sbml_path)) + # FIXME: see https://github.com/sys-bio/roadrunner/issues/963 + # if state_path.exists(): + # logger.debug(f"Load model from state: '{state_path}'") + # r = roadrunner.RoadRunner() + # r.loadState(str(state_path)) + # # with open(state_path, "rb") as fin: + # # r.loadStateS(fin.read()) + # logger.debug(f"Model loaded from state: '{state_path}'") + # else: + # logger.info(f"Load model from SBML: '{sbml_path}'") + # r = roadrunner.RoadRunner(str(sbml_path)) + # # save state + # r.saveState(str(state_path)) + # # with open(state_path, "wb") as fout: + # # fout.write(r.saveStateS(opt="b")) + # logger.info(f"Save state: '{state_path}'") elif source.is_content(): r = roadrunner.RoadRunner(str(source.content)) return r - @classmethod - def copy_roadrunner_model(cls, r: roadrunner.RoadRunner) -> roadrunner.RoadRunner: - """Copy roadrunner model by using the state. + @staticmethod + def get_state_path(sbml_path: Path) -> Optional[Path]: + """Get path of the state file. - :param r: - :return: + The state file is a binary file which allows fast model loading. """ + md5 = md5_for_path(sbml_path) + return Path(f"{sbml_path}_rr{roadrunner.__version__}_{md5}.state") + + @classmethod + def copy_roadrunner_model(cls, r: roadrunner.RoadRunner) -> roadrunner.RoadRunner: + """Copy roadrunner model by using the state.""" ftmp = tempfile.NamedTemporaryFile() filename = ftmp.name r.saveState(filename) diff --git a/src/sbmlsim/oven/model_nan.py b/src/sbmlsim/oven/model_nan.py deleted file mode 100644 index 53af1ec1..00000000 --- a/src/sbmlsim/oven/model_nan.py +++ /dev/null @@ -1,89 +0,0 @@ -"""Model with NaN example.""" -from pathlib import Path - -from sbmlutils.cytoscape import visualize_sbml -from sbmlutils.factory import * -from sbmlutils.metadata import * -from sbmlutils.examples.templates import terms_of_use - - -class U(Units): - mmole = UnitDefinition("mmole", "mmole") - min = UnitDefinition("min", "min") - mmole_per_min = UnitDefinition("mmole_per_min", "mmole/min") - mM = UnitDefinition("mM", "mmole/liter") - - -_m = Model( - "nan_species", - name="NaN species example", - notes=terms_of_use, - units=U, - model_units=ModelUnits( - time=U.min, - substance=U.mmole, - extent=U.mmole, - volume=U.liter, - ), -) - - -_m.compartments = [ - Compartment( - "Vext", - value=NaN, - name="plasma", - sboTerm=SBO.PHYSICAL_COMPARTMENT, - unit=U.liter, - ), -] - -_m.species = [ - Species( - "dex_ext", - initialConcentration=0.0, - name="dextromethorphan (plasma)", - compartment="Vext", - substanceUnit=U.mmole, - hasOnlySubstanceUnits=False, - sboTerm=SBO.SIMPLE_CHEMICAL, - ), -] - -_m.reactions = [ - Reaction( - "DEXEX", - name="DEX export", - equation="dex_ext ->", - sboTerm=SBO.TRANSPORT_REACTION, - rules=[ - ], - pars=[ - Parameter( - "DEXEX_Vmax", - 1, - unit=U.mmole_per_min, - sboTerm=SBO.MAXIMAL_VELOCITY, - ), - Parameter( - "DEXEX_Km_dex", - 0.1, - unit=U.mM, - sboTerm=SBO.MICHAELIS_CONSTANT, - ), - ], - formula=( - "DEXEX_Vmax * dex_ext/(DEXEX_Km_dex + dex_ext)", - U.mmole_per_min, - ), - ), -] - -model_nan = _m - -if __name__ == "__main__": - - result: FactoryResult = create_model( - output_dir=Path(".").parent, models=model_nan - ) - diff --git a/src/sbmlsim/oven/model_state.py b/src/sbmlsim/oven/model_state.py new file mode 100644 index 00000000..bed8e2c2 --- /dev/null +++ b/src/sbmlsim/oven/model_state.py @@ -0,0 +1,44 @@ +import hashlib +from pathlib import Path +from typing import Optional + +import roadrunner +print(f"roadrunner version: {roadrunner.__version__}") + +def md5_for_path(path): + """Calculate MD5 of file content.""" + + # Open,close, read file and calculate MD5 on its contents + with open(path, "rb") as f_check: + # read contents of the file + data = f_check.read() + # pipe contents of the file through + return hashlib.md5(data).hexdigest() + +def get_state_path(sbml_path: Path) -> Optional[Path]: + """Get path of the state file. + + The state file is a binary file which allows fast model loading. + """ + md5 = md5_for_path(sbml_path) + return Path(f"{sbml_path}_rr{roadrunner.__version__}_{md5}.state") + + +sbml_path = "omeprazole_body_flat.xml" +state_path = get_state_path(sbml_path) + +# state saving and loading +r = roadrunner.RoadRunner() + +if state_path.exists(): + r.loadState(str(state_path)) + print(f"Model loaded from state: '{state_path}'") +else: + print(f"Load model from SBML: '{sbml_path}'") + r = roadrunner.RoadRunner(str(sbml_path)) + # save state + r.saveState(str(state_path)) + +print(f"Load from state: '{state_path}'") +r.loadState(str(state_path)) +print(f"Model loaded from state: '{state_path}'") diff --git a/src/sbmlsim/oven/nan_species.xml b/src/sbmlsim/oven/nan_species.xml deleted file mode 100644 index 49481bfc..00000000 --- a/src/sbmlsim/oven/nan_species.xml +++ /dev/null @@ -1,158 +0,0 @@ - - - - -

Created with https://github.com/matthiaskoenig/sbmlutils. - - DOI

- -
- - - -

Terms of use

-

The content of this model has been carefully created in a manual research effort. -This file has been created by Matthias König -using - sbmlutils - . -For questions contact koenigmx@hu-berlin.de.

- - - -Copyright © 2021 Matthias König. - - Creative Commons License -
This work is licensed under a Creative Commons Attribution 4.0 International License. -

Redistribution and use of any part of this model, with or without modification, -are permitted provided that the following conditions are met:

-
    -
  1. Redistributions of this SBML file must retain the above copyright notice, this -list of conditions and the following disclaimer.
  2. -
  3. Redistributions in a different form must reproduce the above copyright notice, -this list of conditions and the following disclaimer in the documentation and/or -other materials provided with the distribution.
  4. -
-

This model is distributed in the hope that it will be useful, but WITHOUT ANY -WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -FOR A PARTICULAR PURPOSE.

-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - DEXEX_Vmax - dex_ext - - - - DEXEX_Km_dex - dex_ext - - - - - - -
-
diff --git a/src/sbmlsim/oven/nan_species.zip b/src/sbmlsim/oven/nan_species.zip deleted file mode 100644 index 8ff095688342b0ba9c87c0b3b172459743eccf71..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2709 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 + + + +

Created with https://github.com/matthiaskoenig/sbmlutils. + + DOI

+ +
+ + + +

Model for omeprazole distribution and elimination

+

Terms of use

+

The content of this model has been carefully created in a manual research effort. +This file has been created by Matthias König +using sbmlutils. +For questions contact koenigmx@hu-berlin.de. Copyright © 2024 Matthias König.

+ + Creative Commons License + +
This work is licensed under a Creative Commons Attribution 4.0 International License. +

Redistribution and use of any part of this model, with or without modification, +are permitted provided that the following conditions are met:

+
    +
  1. Redistributions of this SBML file must retain the above copyright notice, this +list of conditions and the following disclaimer.
  2. +
  3. Redistributions in a different form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution.
  4. +
+

This model is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE.

+
+ + + + + + + + Leal Pujol + Helen + + lepujolh@hu-berlin.de + Humboldt-University Berlin, Institute for Theoretical Biology + + + + Balci + Sükrü + + balcisue@hu-berlin.de + Humboldt-University Berlin, Institute for Theoretical Biology + + + + König + Matthias + + koenigmx@hu-berlin.de + Humboldt-University Berlin, Institute for Theoretical Biology + + + + + 1900-01-01T00:00:00Z + + + 1900-01-01T00:00:00Z + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Urinary species are in amounts.

+ +
+ + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Typical stomach pH of: pH = 1-3; pH=2.0 +Calculation of proton concentration as:

+
+              pH = -log10(H+) => H+ = 10^-pH = 10^-2 = 0.01 [mole/litre] = 10 [mmole/l] 
+   = 10 [mM]
+
+            
+ +
+ + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Parameter controls the health state/renal function +(1.0 reference function, < 1.0 reduced function, > 1.0 increased function)

+ +
+ + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

loss of stomach fluid to intestine

+ +
+ + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + +

Fraction absorbed, i.e., only a fraction of the S-omeprazole in the intestinal lumen +is absorbed. This parameter determines how much of the S-omeprazole is excreted in the feces.

+

+ F_some_abs of dose is absorbed. (1-F_some_abs) is excreted in feces.

+ +
+ + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Fast application to shift applied dose in the stomach.

+ +
+ + + + + + + + + + + +
+ + + + + + + + + + + + + + + + +

Fraction absorbed, i.e., only a fraction of the R-omeprazole in the intestinal lumen +is absorbed. This parameter determines how much of the R-omeprazole is excreted in the feces.

+

+ F_rome_abs of dose is absorbed. (1-F_rome_abs) is excreted in feces.

+ +
+ + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Fast application to shift applied dose in the stomach.

+ +
+ + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + Cve_some + Cve_rome + + + + + + +

Used to compare to total metabolites in plasma often based on radioactive assay.

+ +
+ + + + Cve_some + Cve_rome + Cve_omeoh + Cve_omesul + + +
+ + + + + 1 + + + FVgu + FVki + FVli + FVlu + FVsp + FVpa + FVve + FVar + + + + + + + + + 1 + + + FQki + FQh + + + + + + + + + 0.024265 + + + + + BW + 1 + + 0.5378 + + + + + + HEIGHT + 1 + + 0.3964 + + + + + + + + + + + BW + COBW + + + + + + + + HR + HRrest + + COHRI + + 60 + + + + + + + + + + + CO + 1000 + + 60 + + + + + + + + BW + FVgu + + + + + + + + BW + FVki + + + + + + + + BW + FVli + + + + + + + + BW + FVlu + + + + + + + + BW + FVsp + + + + + + + + BW + FVpa + + + + + + + + BW + FVre + + + + + + + + + + 1 + HCT + + + + + + BW + FVve + + + + + + FVve + + + FVar + FVve + FVpo + FVhv + + + BW + Fblood + + + 1 + + + FVar + FVve + FVpo + FVhv + + + + + + + + + + + + + + 1 + HCT + + + + + + BW + FVar + + + + + + FVar + + + FVar + FVve + FVpo + FVhv + + + BW + Fblood + + + 1 + + + FVar + FVve + FVpo + FVhv + + + + + + + + + + + + + + 1 + HCT + + + + + + BW + FVpo + + + + + + FVpo + + + FVar + FVve + FVpo + FVhv + + + BW + Fblood + + + 1 + + + FVar + FVve + FVpo + FVhv + + + + + + + + + + + + + + 1 + HCT + + + + + + BW + FVhv + + + + + + FVhv + + + FVar + FVve + FVpo + FVhv + + + BW + Fblood + + + 1 + + + FVar + FVve + FVpo + FVhv + + + + + + + + + + + + QC + FQgu + + + + + + + + QC + FQki + + + + + + + + QC + FQh + + + + + + + + + + + + Qh + Qgu + + Qsp + + Qpa + + + + + + + + QC + FQlu + + + + + + + + QC + FQsp + + + + + + + + QC + FQpa + + + + + + + + QC + FQre + + + + + + + + Qsp + Qpa + Qgu + + + + + + + + Vgu + Fblood + + + 1 + HCT + + + + + + + + + Vki + Fblood + + + 1 + HCT + + + + + + + + + Vli + Fblood + + + 1 + HCT + + + + + + + + + Vlu + Fblood + + + 1 + HCT + + + + + + + + + Vpa + Fblood + + + 1 + HCT + + + + + + + + + Vsp + Fblood + + + 1 + HCT + + + + + + + + + Vre + Fblood + + + 1 + HCT + + + + + + + +

log(2) ~ 0.693 used for conversion of half-life injection time to +injection rate.

+ +
+ + + + + + 0.693 + ti_some + + 60 + + +
+ + + + + Cgu_plasma_some + Vgu_plasma + + + + + + + + Cgu_plasma_some + fup_some + + + + + + + + Agu_plasma_some + Mr_some + + + + + + + + + + Agu_plasma_some + Vgu_plasma + + Mr_some + + + + + + + + Cki_plasma_some + Vki_plasma + + + + + + + + Cki_plasma_some + fup_some + + + + + + + + Aki_plasma_some + Mr_some + + + + + + + + + + Aki_plasma_some + Vki_plasma + + Mr_some + + + + + + + + Cli_plasma_some + Vli_plasma + + + + + + + + Cli_plasma_some + fup_some + + + + + + + + Ali_plasma_some + Mr_some + + + + + + + + + + Ali_plasma_some + Vli_plasma + + Mr_some + + + + + + + + Clu_plasma_some + Vlu_plasma + + + + + + + + Clu_plasma_some + fup_some + + + + + + + + Alu_plasma_some + Mr_some + + + + + + + + + + Alu_plasma_some + Vlu_plasma + + Mr_some + + + + + + + + Cpa_plasma_some + Vpa_plasma + + + + + + + + Cpa_plasma_some + fup_some + + + + + + + + Apa_plasma_some + Mr_some + + + + + + + + + + Apa_plasma_some + Vpa_plasma + + Mr_some + + + + + + + + Csp_plasma_some + Vsp_plasma + + + + + + + + Csp_plasma_some + fup_some + + + + + + + + Asp_plasma_some + Mr_some + + + + + + + + + + Asp_plasma_some + Vsp_plasma + + Mr_some + + + + + + + + Cre_plasma_some + Vre_plasma + + + + + + + + Cre_plasma_some + fup_some + + + + + + + + Are_plasma_some + Mr_some + + + + + + + + + + Are_plasma_some + Vre_plasma + + Mr_some + + + + + + + + Car_some + Var + + + + + + + + Car_some + fup_some + + + + + + + + Aar_some + Mr_some + + + + + + + + + + Aar_some + Var + + Mr_some + + + + + + + + Cve_some + Vve + + + + + + + + Cve_some + fup_some + + + + + + + + Ave_some + Mr_some + + + + + + + + + + Ave_some + Vve + + Mr_some + + + + + + + + Cpo_some + Vpo + + + + + + + + Cpo_some + fup_some + + + + + + + + Apo_some + Mr_some + + + + + + + + + + Apo_some + Vpo + + Mr_some + + + + + + + + Chv_some + Vhv + + + + + + + + Chv_some + fup_some + + + + + + + + Ahv_some + Mr_some + + + + + + + + + + Ahv_some + Vhv + + Mr_some + + + + + + +

log(2) ~ 0.693 used for conversion of half-life injection time to +injection rate.

+ +
+ + + + + + 0.693 + ti_rome + + 60 + + +
+ + + + + Cgu_plasma_rome + Vgu_plasma + + + + + + + + Cgu_plasma_rome + fup_rome + + + + + + + + Agu_plasma_rome + Mr_rome + + + + + + + + + + Agu_plasma_rome + Vgu_plasma + + Mr_rome + + + + + + + + Cki_plasma_rome + Vki_plasma + + + + + + + + Cki_plasma_rome + fup_rome + + + + + + + + Aki_plasma_rome + Mr_rome + + + + + + + + + + Aki_plasma_rome + Vki_plasma + + Mr_rome + + + + + + + + Cli_plasma_rome + Vli_plasma + + + + + + + + Cli_plasma_rome + fup_rome + + + + + + + + Ali_plasma_rome + Mr_rome + + + + + + + + + + Ali_plasma_rome + Vli_plasma + + Mr_rome + + + + + + + + Clu_plasma_rome + Vlu_plasma + + + + + + + + Clu_plasma_rome + fup_rome + + + + + + + + Alu_plasma_rome + Mr_rome + + + + + + + + + + Alu_plasma_rome + Vlu_plasma + + Mr_rome + + + + + + + + Cpa_plasma_rome + Vpa_plasma + + + + + + + + Cpa_plasma_rome + fup_rome + + + + + + + + Apa_plasma_rome + Mr_rome + + + + + + + + + + Apa_plasma_rome + Vpa_plasma + + Mr_rome + + + + + + + + Csp_plasma_rome + Vsp_plasma + + + + + + + + Csp_plasma_rome + fup_rome + + + + + + + + Asp_plasma_rome + Mr_rome + + + + + + + + + + Asp_plasma_rome + Vsp_plasma + + Mr_rome + + + + + + + + Cre_plasma_rome + Vre_plasma + + + + + + + + Cre_plasma_rome + fup_rome + + + + + + + + Are_plasma_rome + Mr_rome + + + + + + + + + + Are_plasma_rome + Vre_plasma + + Mr_rome + + + + + + + + Car_rome + Var + + + + + + + + Car_rome + fup_rome + + + + + + + + Aar_rome + Mr_rome + + + + + + + + + + Aar_rome + Var + + Mr_rome + + + + + + + + Cve_rome + Vve + + + + + + + + Cve_rome + fup_rome + + + + + + + + Ave_rome + Mr_rome + + + + + + + + + + Ave_rome + Vve + + Mr_rome + + + + + + + + Cpo_rome + Vpo + + + + + + + + Cpo_rome + fup_rome + + + + + + + + Apo_rome + Mr_rome + + + + + + + + + + Apo_rome + Vpo + + Mr_rome + + + + + + + + Chv_rome + Vhv + + + + + + + + Chv_rome + fup_rome + + + + + + + + Ahv_rome + Mr_rome + + + + + + + + + + Ahv_rome + Vhv + + Mr_rome + + + + + + + + Cgu_plasma_omeoh + Vgu_plasma + + + + + + + + Cgu_plasma_omeoh + fup_omeoh + + + + + + + + Agu_plasma_omeoh + Mr_omeoh + + + + + + + + + + Agu_plasma_omeoh + Vgu_plasma + + Mr_omeoh + + + + + + + + Cki_plasma_omeoh + Vki_plasma + + + + + + + + Cki_plasma_omeoh + fup_omeoh + + + + + + + + Aki_plasma_omeoh + Mr_omeoh + + + + + + + + + + Aki_plasma_omeoh + Vki_plasma + + Mr_omeoh + + + + + + + + Cli_plasma_omeoh + Vli_plasma + + + + + + + + Cli_plasma_omeoh + fup_omeoh + + + + + + + + Ali_plasma_omeoh + Mr_omeoh + + + + + + + + + + Ali_plasma_omeoh + Vli_plasma + + Mr_omeoh + + + + + + + + Clu_plasma_omeoh + Vlu_plasma + + + + + + + + Clu_plasma_omeoh + fup_omeoh + + + + + + + + Alu_plasma_omeoh + Mr_omeoh + + + + + + + + + + Alu_plasma_omeoh + Vlu_plasma + + Mr_omeoh + + + + + + + + Cpa_plasma_omeoh + Vpa_plasma + + + + + + + + Cpa_plasma_omeoh + fup_omeoh + + + + + + + + Apa_plasma_omeoh + Mr_omeoh + + + + + + + + + + Apa_plasma_omeoh + Vpa_plasma + + Mr_omeoh + + + + + + + + Csp_plasma_omeoh + Vsp_plasma + + + + + + + + Csp_plasma_omeoh + fup_omeoh + + + + + + + + Asp_plasma_omeoh + Mr_omeoh + + + + + + + + + + Asp_plasma_omeoh + Vsp_plasma + + Mr_omeoh + + + + + + + + Cre_plasma_omeoh + Vre_plasma + + + + + + + + Cre_plasma_omeoh + fup_omeoh + + + + + + + + Are_plasma_omeoh + Mr_omeoh + + + + + + + + + + Are_plasma_omeoh + Vre_plasma + + Mr_omeoh + + + + + + + + Car_omeoh + Var + + + + + + + + Car_omeoh + fup_omeoh + + + + + + + + Aar_omeoh + Mr_omeoh + + + + + + + + + + Aar_omeoh + Var + + Mr_omeoh + + + + + + + + Cve_omeoh + Vve + + + + + + + + Cve_omeoh + fup_omeoh + + + + + + + + Ave_omeoh + Mr_omeoh + + + + + + + + + + Ave_omeoh + Vve + + Mr_omeoh + + + + + + + + Cpo_omeoh + Vpo + + + + + + + + Cpo_omeoh + fup_omeoh + + + + + + + + Apo_omeoh + Mr_omeoh + + + + + + + + + + Apo_omeoh + Vpo + + Mr_omeoh + + + + + + + + Chv_omeoh + Vhv + + + + + + + + Chv_omeoh + fup_omeoh + + + + + + + + Ahv_omeoh + Mr_omeoh + + + + + + + + + + Ahv_omeoh + Vhv + + Mr_omeoh + + + + + + + + Cgu_plasma_omesul + Vgu_plasma + + + + + + + + Cgu_plasma_omesul + fup_omesul + + + + + + + + Agu_plasma_omesul + Mr_omesul + + + + + + + + + + Agu_plasma_omesul + Vgu_plasma + + Mr_omesul + + + + + + + + Cki_plasma_omesul + Vki_plasma + + + + + + + + Cki_plasma_omesul + fup_omesul + + + + + + + + Aki_plasma_omesul + Mr_omesul + + + + + + + + + + Aki_plasma_omesul + Vki_plasma + + Mr_omesul + + + + + + + + Cli_plasma_omesul + Vli_plasma + + + + + + + + Cli_plasma_omesul + fup_omesul + + + + + + + + Ali_plasma_omesul + Mr_omesul + + + + + + + + + + Ali_plasma_omesul + Vli_plasma + + Mr_omesul + + + + + + + + Clu_plasma_omesul + Vlu_plasma + + + + + + + + Clu_plasma_omesul + fup_omesul + + + + + + + + Alu_plasma_omesul + Mr_omesul + + + + + + + + + + Alu_plasma_omesul + Vlu_plasma + + Mr_omesul + + + + + + + + Cpa_plasma_omesul + Vpa_plasma + + + + + + + + Cpa_plasma_omesul + fup_omesul + + + + + + + + Apa_plasma_omesul + Mr_omesul + + + + + + + + + + Apa_plasma_omesul + Vpa_plasma + + Mr_omesul + + + + + + + + Csp_plasma_omesul + Vsp_plasma + + + + + + + + Csp_plasma_omesul + fup_omesul + + + + + + + + Asp_plasma_omesul + Mr_omesul + + + + + + + + + + Asp_plasma_omesul + Vsp_plasma + + Mr_omesul + + + + + + + + Cre_plasma_omesul + Vre_plasma + + + + + + + + Cre_plasma_omesul + fup_omesul + + + + + + + + Are_plasma_omesul + Mr_omesul + + + + + + + + + + Are_plasma_omesul + Vre_plasma + + Mr_omesul + + + + + + + + Car_omesul + Var + + + + + + + + Car_omesul + fup_omesul + + + + + + + + Aar_omesul + Mr_omesul + + + + + + + + + + Aar_omesul + Var + + Mr_omesul + + + + + + + + Cve_omesul + Vve + + + + + + + + Cve_omesul + fup_omesul + + + + + + + + Ave_omesul + Mr_omesul + + + + + + + + + + Ave_omesul + Vve + + Mr_omesul + + + + + + + + Cpo_omesul + Vpo + + + + + + + + Cpo_omesul + fup_omesul + + + + + + + + Apo_omesul + Mr_omesul + + + + + + + + + + Apo_omesul + Vpo + + Mr_omesul + + + + + + + + Chv_omesul + Vhv + + + + + + + + Chv_omesul + fup_omesul + + + + + + + + Ahv_omesul + Mr_omesul + + + + + + + + + + Ahv_omesul + Vhv + + Mr_omesul + + + + + + + + + + + + iv_some + + Mr_some + + Ri_some + + + + + + Ri_some + + + + + + + + + + + iv_rome + + Mr_rome + + Ri_rome + + + + + + Ri_rome + + + + + + + LI__cyp2c19_ac + + + LI__cyp2c19_allele1 + + + 1 + + + + + LI__cyp2c19_a0 + + + LI__cyp2c19_allele1 + 0 + + + + LI__cyp2c19_a1 + + + LI__cyp2c19_allele1 + 1 + + + + LI__cyp2c19_a2 + + + LI__cyp2c19_allele1 + 2 + + + + LI__cyp2c19_a3 + + + LI__cyp2c19_allele1 + 3 + + + + LI__cyp2c19_a4 + + + LI__cyp2c19_allele1 + 4 + + + + LI__cyp2c19_a5 + + + LI__cyp2c19_allele1 + 5 + + + + LI__cyp2c19_ac + + + + + + + + + LI__cyp2c19_ac + + + LI__cyp2c19_allele2 + + + 1 + + + + + LI__cyp2c19_a0 + + + LI__cyp2c19_allele2 + 0 + + + + LI__cyp2c19_a1 + + + LI__cyp2c19_allele2 + 1 + + + + LI__cyp2c19_a2 + + + LI__cyp2c19_allele2 + 2 + + + + LI__cyp2c19_a3 + + + LI__cyp2c19_allele2 + 3 + + + + LI__cyp2c19_a4 + + + LI__cyp2c19_allele2 + 4 + + + + LI__cyp2c19_a5 + + + LI__cyp2c19_allele2 + 5 + + + + LI__cyp2c19_ac + + + + + + + + + + + LI__cyp2c19_activity1 + LI__cyp2c19_activity2 + + 2 + + + + + + +

Used to compare to total metabolites in urine often based on radioactive assay. +No S-omeprazole or R-omeprazole excreted.

+ +
+ + + + Aurine_omeoh + Aurine_omesul + + +
+ + + + + KI__OMESULEX + KI__OMEOHEX + + + + + + + + + + + + + + + + + + + GU__pp_active + GU__pp_inactive + + + + + + +

pH calculation with conversion of stomach proton concentration: +[mmole/l] -> [mole/l]

+ +
+ + + + + + + + + + + + + + + + + + + 10 + + + + GU__protons_stomach + 1000 + + + + +
+ + + +

Using sigmoidal rule interpolating between F_ome_pH2 and F_ome_pH6. +Ensures non-negative values and capping at pH6.

+ +
+ + + + + + GU__F_ome_pH2 + + + + + + + GU__F_ome_pH6 + GU__F_ome_pH2 + + + + + + 0 + + + pH + 2 + + + GU__F_ome_n + + + + + + + + + 0 + + + pH + 2 + + + GU__F_ome_n + + + + GU__F_ome_KpH + GU__F_ome_n + + + + + GU__F_ome_solution + + + GU__F_ome_pH6 + + + +
+ + + + + GU__SOMEABS_k + Vgu + Cgu_some + + + + + + + + + + + + Ka_dis_some + 60 + + GU__POSTOMACH_some + + GU__Mr_some + + + + + + + + GU__ROMEABS_k + Vgu + Cgu_rome + + + + + + + + + + + + Ka_dis_rome + 60 + + GU__POSTOMACH_rome + + GU__Mr_rome + + + + + + + + Afeces_rome + Afeces_some + + + + + + + + + + GU__application_some + + GU__Mr_some + + + + + + + + + + GU__application_some + GU__dissolution_some + + GU__Mr_some + + + + + + + + + + GU__application_rome + + GU__Mr_rome + + + + + + + + + + GU__application_rome + GU__dissolution_rome + + GU__Mr_rome + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + Ki_some + IVDOSE_some + + Mr_some + + + + + + + + + + + + + + + + Qgu + Car_some + + + + + + + + + + + + + + + + Qgu + Cgu_plasma_some + BP_some + + + + + + + + + + + + + + + + Qki + Car_some + + + + + + + + + + + + + + + + Qki + Cki_plasma_some + BP_some + + + + + + + + + + + + + + + + Qha + Car_some + + + + + + + + + + + + + + + + Qpo + Cpo_some + BP_some + + + + + + + + + + + + + + + + Qh + Cli_plasma_some + BP_some + + + + + + + + + + + + + + + + Qh + Chv_some + BP_some + + + + + + + + + + + + + + + + Qlu + Cve_some + + + + + + + + + + + + + + + + Qlu + Clu_plasma_some + BP_some + + + + + + + + + + + + + + + + Qpa + Car_some + + + + + + + + + + + + + + + + Qpa + Cpa_plasma_some + BP_some + + + + + + + + + + + + + + + + Qsp + Car_some + + + + + + + + + + + + + + + + Qsp + Csp_plasma_some + BP_some + + + + + + + + + + + + + + + + Qre + Car_some + + + + + + + + + + + + + + + + Qre + Cre_plasma_some + BP_some + + + + + + + + + + + + + + + + + + + + + + + + + + + Ki_rome + IVDOSE_rome + + Mr_rome + + + + + + + + + + + + + + + + Qgu + Car_rome + + + + + + + + + + + + + + + + Qgu + Cgu_plasma_rome + BP_rome + + + + + + + + + + + + + + + + Qki + Car_rome + + + + + + + + + + + + + + + + Qki + Cki_plasma_rome + BP_rome + + + + + + + + + + + + + + + + Qha + Car_rome + + + + + + + + + + + + + + + + Qpo + Cpo_rome + BP_rome + + + + + + + + + + + + + + + + Qh + Cli_plasma_rome + BP_rome + + + + + + + + + + + + + + + + Qh + Chv_rome + BP_rome + + + + + + + + + + + + + + + + Qlu + Cve_rome + + + + + + + + + + + + + + + + Qlu + Clu_plasma_rome + BP_rome + + + + + + + + + + + + + + + + Qpa + Car_rome + + + + + + + + + + + + + + + + Qpa + Cpa_plasma_rome + BP_rome + + + + + + + + + + + + + + + + Qsp + Car_rome + + + + + + + + + + + + + + + + Qsp + Csp_plasma_rome + BP_rome + + + + + + + + + + + + + + + + Qre + Car_rome + + + + + + + + + + + + + + + + Qre + Cre_plasma_rome + BP_rome + + + + + + + + + + + + + + + + Qgu + Car_omeoh + + + + + + + + + + + + + + + + Qgu + Cgu_plasma_omeoh + BP_omeoh + + + + + + + + + + + + + + + + Qki + Car_omeoh + + + + + + + + + + + + + + + + Qki + Cki_plasma_omeoh + BP_omeoh + + + + + + + + + + + + + + + + Qha + Car_omeoh + + + + + + + + + + + + + + + + Qpo + Cpo_omeoh + BP_omeoh + + + + + + + + + + + + + + + + Qh + Cli_plasma_omeoh + BP_omeoh + + + + + + + + + + + + + + + + Qh + Chv_omeoh + BP_omeoh + + + + + + + + + + + + + + + + Qlu + Cve_omeoh + + + + + + + + + + + + + + + + Qlu + Clu_plasma_omeoh + BP_omeoh + + + + + + + + + + + + + + + + Qpa + Car_omeoh + + + + + + + + + + + + + + + + Qpa + Cpa_plasma_omeoh + BP_omeoh + + + + + + + + + + + + + + + + Qsp + Car_omeoh + + + + + + + + + + + + + + + + Qsp + Csp_plasma_omeoh + BP_omeoh + + + + + + + + + + + + + + + + Qre + Car_omeoh + + + + + + + + + + + + + + + + Qre + Cre_plasma_omeoh + BP_omeoh + + + + + + + + + + + + + + + + Qgu + Car_omesul + + + + + + + + + + + + + + + + Qgu + Cgu_plasma_omesul + BP_omesul + + + + + + + + + + + + + + + + Qki + Car_omesul + + + + + + + + + + + + + + + + Qki + Cki_plasma_omesul + BP_omesul + + + + + + + + + + + + + + + + Qha + Car_omesul + + + + + + + + + + + + + + + + Qpo + Cpo_omesul + BP_omesul + + + + + + + + + + + + + + + + Qh + Cli_plasma_omesul + BP_omesul + + + + + + + + + + + + + + + + Qh + Chv_omesul + BP_omesul + + + + + + + + + + + + + + + + Qlu + Cve_omesul + + + + + + + + + + + + + + + + Qlu + Clu_plasma_omesul + BP_omesul + + + + + + + + + + + + + + + + Qpa + Car_omesul + + + + + + + + + + + + + + + + Qpa + Cpa_plasma_omesul + BP_omesul + + + + + + + + + + + + + + + + Qsp + Car_omesul + + + + + + + + + + + + + + + + Qsp + Csp_plasma_omesul + BP_omesul + + + + + + + + + + + + + + + + Qre + Car_omesul + + + + + + + + + + + + + + + + Qre + Cre_plasma_omesul + BP_omesul + + + + + + + +

Irreversible transport of S-omeprazole from plasma in the liver.

+ +
+ + + + + + + + + + + + + + + + + + + + + + + + LI__OMEIM_Vmax + Vli_tissue + Cli_plasma_some + + + + LI__OMEIM_Km_ome + Cli_plasma_some + + + + +
+ + + +

Irreversible transport of R-omeprazole from plasma in the liver.

+ +
+ + + + + + + + + + + + + + + + + + + + + + + + LI__OMEIM_Vmax + Vli_tissue + Cli_plasma_rome + + + + LI__OMEIM_Km_ome + Cli_plasma_rome + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + LI__f_CYP2C19 + LI__cyp2c19_activity + LI__SOMECYP2C19_Vmax + Vli_tissue + LI__some + + + + LI__SOMECYP2C19_Km_some + LI__some + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + LI__f_CYP2C19 + LI__cyp2c19_activity + LI__ROMECYP2C19_Vmax + Vli_tissue + LI__rome + + + + LI__ROMECYP2C19_Km_rome + LI__rome + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + LI__SOMECYP3A4_Vmax + Vli_tissue + LI__some + + + + LI__SOMECYP3A4_Km_some + LI__some + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + LI__ROMECYP3A4_Vmax + Vli_tissue + LI__rome + + + + LI__ROMECYP3A4_Km_rome + LI__rome + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + LI__OMEOHEX_Vmax + Vli_tissue + LI__omeoh + + + + LI__OMEOHEX_Km_omeoh + LI__omeoh + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + LI__OMESUL_Vmax + Vli_tissue + LI__omesul + + + + LI__OMESUL_Km_omesul + LI__omesul + + + + + + + + + + + + + + + + + + + + + + + + + + + + + KI__OMEOHEX_k + Vki_tissue + Cki_plasma_omeoh + + + + + + + + + + + + + + + + + + + + + + + + + + + + KI__OMESULEX_k + Vki_tissue + Cki_plasma_omesul + + + + + + + + + + + + + + + + + + + + + + + + + + + GU__pp_rate_k + + + 10 + + + + + + + 1.1 + + + + GU__pp_active + 0.1 + + + 1 + + + + + + + + + + + + + + + + + + + + + + + + + + GU__Qstomach + GU__protons_stomach + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + GU__ppi_k + GU__pp_active + + + Cgu_plasma_some + Cgu_plasma_rome + + + + + + + + +

+ steady state +

+
+              pp_synthesis = pp_active_degradation (with pp_active = 1 [dimensionless])
+pp_synthesis_k = pp_degradation_k * pp_active
+pp_active = pp_synthesis_k/pp_degradation_k
+
+            
+ +
+ + + + + + + + + + + + + + + + + GU__pp_synthesis_k + + +
+ + + + + + + + + + + + + + + + + + + + GU__pp_degradation_k + GU__pp_active + + + + + + + +

Identical kinetics to degradation of active PP.

+ +
+ + + + + + + + + + + + + + + + + + + GU__pp_degradation_k + GU__pp_inactive + + + +
+ + + + + + + + + + + + + + + + + + + + + + + GU__F_some_abs + GU__absorption_some + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + GU__F_some_abs + + GU__absorption_some + + + + + + + + + + + + + + + + + GU__Ka_application_some + 60 + + PODOSE_some + + GU__Mr_some + + + + + + + + + + + + + + + + GU__F_ome_pH + GU__dissolution_some_rate + + + + + + + + + + + + + + + + + + 1 + GU__F_ome_pH + + GU__dissolution_some_rate + + + + + + + + + + + + + + + + + + + + + + + + + + + GU__F_rome_abs + GU__absorption_rome + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + GU__F_rome_abs + + GU__absorption_rome + + + + + + + + + + + + + + + + + GU__Ka_application_rome + 60 + + PODOSE_rome + + GU__Mr_rome + + + + + + + + + + + + + + + + GU__F_ome_pH + GU__dissolution_rome_rate + + + + + + + + + + + + + + + + + + 1 + GU__F_ome_pH + + GU__dissolution_rome_rate + + + + +
+
+
diff --git a/src/sbmlsim/oven/rr_tolerances_issues.py b/src/sbmlsim/oven/rr_tolerances_issues.py deleted file mode 100644 index 41e15d75..00000000 --- a/src/sbmlsim/oven/rr_tolerances_issues.py +++ /dev/null @@ -1,15 +0,0 @@ -import roadrunner -import pandas as pd - -tiny_sbml = "tiny_example_1.xml" -r = roadrunner.RoadRunner(tiny_sbml) -s = r.simulate(0, 100, steps=100) -df = pd.DataFrame(s, columns=s.colnames) -r.plot() - -r = roadrunner.RoadRunner(tiny_sbml) -r.integrator.setValue("relative_tolerance", 1E-18) -r.integrator.setValue("absolute_tolerance", 1E-18) -s = r.simulate(0, 100, steps=100) -df = pd.DataFrame(s, columns=s.colnames) -r.plot() diff --git a/src/sbmlsim/oven/tiny_example_1.png b/src/sbmlsim/oven/tiny_example_1.png deleted file mode 100644 index dea4a8e1528b8430c1215a5e7806d0566cf77d5a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 56654 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 XTr^Yv-a=M%{fMZrB&^_#_NV^^7v~r7 diff --git a/src/sbmlsim/oven/tiny_example_1.xml b/src/sbmlsim/oven/tiny_example_1.xml deleted file mode 100644 index 238d5258..00000000 --- a/src/sbmlsim/oven/tiny_example_1.xml +++ /dev/null @@ -1,298 +0,0 @@ - - - - - - - - - - - - - - - - -

Description

-
-
The content of this model has been carefully created in a manual research effort.
-
This file has been created by - SBML team.
-

Terms of use

-
Copyright © 2019 SBML team.
-
-

Redistribution and use of any part of this model, with or without modification, are permitted provided - that the following conditions are met: -

    -
  1. Redistributions of this SBML file must retain the above copyright notice, this list of conditions and - the following disclaimer.
  2. -
  3. Redistributions in a different form must reproduce the above copyright notice, this list of conditions - and the following disclaimer in the documentation and/or other materials provided - with the distribution.
  4. -
- This model is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the - implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -

-
- - - - - - - - - - Koenig - Matthias - - koenigmx@hu-berlin.de - - Humboldt-University Berlin, Institute for Theoretical Biology - - - - - - 2019-02-07T17:15:12Z - - - 2019-02-07T17:15:12Z - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Vmax - glc - - - - Km_glc - glc - - - atp - - - - Km_atp - atp - - - - - - - - diff --git a/src/sbmlsim/simulator/simulation_serial.py b/src/sbmlsim/simulator/simulation_serial.py index 2096b506..77c3e90f 100644 --- a/src/sbmlsim/simulator/simulation_serial.py +++ b/src/sbmlsim/simulator/simulation_serial.py @@ -44,22 +44,29 @@ def __init__(self, model: Union[str|Path|RoadrunnerSBMLModel|AbstractModel] = No def set_model(self, model: Union[str|Path|RoadrunnerSBMLModel|AbstractModel]): """Set model for simulator and updates the integrator settings.""" - logger.info("SimulatorSerial.set_model") + # logger.info("SimulatorSerial.set_model") self.model = None if model is not None: if isinstance(model, RoadrunnerSBMLModel): + # logger.info("SimulatorSerial.set_model from RoadrunnerSBMLModel") self.model = model elif isinstance(model, AbstractModel): + # logger.info("SimulatorSerial.set_model from AbstractModel") self.model = RoadrunnerSBMLModel.from_abstract_model( abstract_model=model ) elif isinstance(model, (str, Path)): + # logger.info("SimulatorSerial.set_model from Path") self.model = RoadrunnerSBMLModel( source=model, ) - self.r = model.r + # logger.info(f"get roadrunner instance from model: {type(self.model)}") + self.r = self.model.r + # logger.info("set integrator settings") self.set_integrator_settings(**self.integrator_settings) + # logger.info("model loading finished") + def set_integrator_settings(self, **kwargs): """Set settings in the integrator.""" @@ -192,11 +199,6 @@ def _timecourse(self, simulation: TimecourseSim) -> pd.DataFrame: self.r[key] = float(item) logger.debug(f"\t{key} = {item}") - # debug model state - # FIXME: report issue - # sbml_str = self.r.getCurrentSBML() - # with open("/home/mkoenig/git/pkdb_models/pkdb_models/models/dextromethorphan/results/debug/test.xml", "w") as f_out: - # f_out.write(sbml_str) # run simulation integrator = self.r.integrator

A minimal example in SBML format. -