Source code for sbmlutils.factory

"""Factory for creating SBML objects.

This module provides definitions of helper functions for the creation of
SBML objects. These are the low level helpers to create models from scratch
and are used in the higher level SBML factories.

The general workflow to create new SBML models isto create a lists/iterables of
SBMLObjects by using the respective classes in this module,
e.g. Compartment, Parameter, Species.

The actual SBase objects are than created in the SBMLDocument/Model by calling
    create_objects(model, objects)
These functions DO NOT take care of the order of the creation, but the order
must be correct in the model definition files.
To create complete models one should use the modelcreator functionality,
which takes care of the order of object creation.
"""
from __future__ import annotations

import datetime
import inspect
import json
from collections import namedtuple
from copy import deepcopy
from dataclasses import dataclass
from enum import Enum
from pathlib import Path
from typing import Any, Dict, Iterable, List, Optional, Set, Tuple, Type, Union

import libsbml
import numpy as np
import xmltodict  # type: ignore
from numpy import NaN
from pint import UndefinedUnitError, UnitRegistry
from pydantic import BaseModel
from pymetadata.core.creator import Creator

from sbmlutils.console import console
from sbmlutils.io import write_sbml
from sbmlutils.log import get_logger
from sbmlutils.metadata import *
from sbmlutils.metadata import annotator
from sbmlutils.metadata.annotator import Annotation
from sbmlutils.notes import Notes, NotesFormat
from sbmlutils.reaction_equation import EquationPart, ReactionEquation
from sbmlutils.utils import FrozenClass, create_metaid, deprecated
from sbmlutils.validation import ValidationOptions, check


try:
    from typing import TypedDict
except ImportError:
    from typing_extensions import TypedDict


logger = get_logger(__name__)

ureg = UnitRegistry()
Q_ = ureg.Quantity
ureg.define("item = 1 dimensionless")

# FIXME: make complete import of all DISTRIB constants

__all__ = [
    "SBML_LEVEL",
    "SBML_VERSION",
    "PORT_SUFFIX",
    "PORT_UNIT_SUFFIX",
    "PortType",
    "ModelUnits",
    "Units",
    "Creator",
    "Compartment",
    "UnitDefinition",
    "Function",
    "Species",
    "Parameter",
    "InitialAssignment",
    "AssignmentRule",
    "RateRule",
    "AlgebraicRule",
    "Event",
    "Constraint",
    "Reaction",
    "Formula",
    "ReactionEquation",
    "ExchangeReaction",
    "Uncertainty",
    "UncertParameter",
    "UncertSpan",
    "UserDefinedConstraintComponent",
    "UserDefinedConstraint",
    "FluxObjective",
    "Objective",
    "GeneProduct",
    "KeyValuePair",
    "ExternalModelDefinition",
    "ModelDefinition",
    "Submodel",
    "Deletion",
    "ReplacedElement",
    "ReplacedBy",
    "Port",
    "Package",
    "ModelDict",
    "Model",
    "Document",
    "UnitType",
    "NaN",
    "create_model",
    "ValidationOptions",
    "FactoryResult",
]


[docs]SBML_LEVEL = 3 # default SBML level
[docs]SBML_VERSION = 1 # default SBML version
[docs]PORT_SUFFIX = "_port"
[docs]PORT_UNIT_SUFFIX = "_unit_port"
PREFIX_EXCHANGE_REACTION = "EX_" def create_objects( model: libsbml.Model, obj_iter: List[Any], key: Optional[str] = None ) -> Dict[str, libsbml.SBase]: """Create the objects in the model. This function calls the respective create_sbml function of all objects in the order of the objects. :param model: SBMLModel instance :param obj_iter: iterator of given model object classes like Parameter, ... :param key: object key :return: dictionary of SBML objects """ sbml_objects: Dict[str, libsbml.SBase] = {} for obj in obj_iter: if obj is None: logger.error( f"Trying to create None object, " f"check for incorrect terminating ',' on objects: " f"'{sbml_objects}'" ) try: sbml_obj: libsbml.SBase = obj.create_sbml(model) except Exception as err: logger.error(f"Error creating SBML object '{sbml_obj}'") logger.error(err) raise err # FIXME: what happens for objects without id? sbml_objects[sbml_obj.getId()] = sbml_obj return sbml_objects def ast_node_from_formula(model: libsbml.Model, formula: str) -> libsbml.ASTNode: """Parse the ASTNode from given formula string with model. :param model: SBMLModel instance :param formula: formula str :return: astnode """ # sanitize formula (allow double and int assignments) if not isinstance(formula, str): formula = str(formula) ast_node = libsbml.parseL3FormulaWithModel(formula, model) if not ast_node: logger.error(f"Formula could not be parsed: '{formula}'") logger.error(libsbml.getLastParseL3Error()) return ast_node
[docs]UnitType = Optional["UnitDefinition"]
AnnotationsType = List[Union[Annotation, Tuple[Union[BQB, BQM], str]]] OptionalAnnotationsType = Optional[List[Union[Annotation, Tuple[Union[BQB, BQM], str]]]] def set_notes( sbase: libsbml.SBase, notes: str, format: NotesFormat = NotesFormat.MARKDOWN ) -> None: """Set notes information on SBase. :param sbase: SBase :param notes: notes information (xml string) :return: """ _notes = Notes(notes, format=format) check(sbase.setNotes(_notes.xml), message=f"Setting notes on '{sbase}'")
[docs]class ModelUnits: """Class for storing model units information. The ModelUnits define globally the units for `time`, `extent`, `substance`, `length`, `area` and `volume`. The following SBML Level 3 base units can be used. ampere farad joule lux radian volt avogadro gram katal metre second watt becquerel gray kelvin mole siemens weber candela henry kilogram newton sievert coulomb hertz litre ohm steradian dimensionless item lumen pascal tesla """ def __init__( self, time: UnitType = None, extent: UnitType = None, substance: UnitType = None, length: UnitType = None, area: UnitType = None, volume: UnitType = None, ): """Construct ModelUnits.""" self.time = time self.extent = extent self.substance = substance self.length = length self.area = area self.volume = volume @staticmethod
[docs] def set_model_units(model: libsbml.Model, model_units: "ModelUnits") -> None: """Set the main units in model from dictionary. Setting the model units is important for understanding the model dynamics. Allowed keys are: time extent substance length area volume :param model: SBMLModel :param model_units: dict of units :return: """ if isinstance(model_units, dict): logger.error( "Providing model units as dict is deprecated, use 'ModelUnits' instead." ) model_units = ModelUnits(**model_units) if not model_units: logger.warning( "Model units should be set for a model. These can be stored " "using the 'model_units' on a model definition." ) else: for key in ("time", "extent", "substance", "length", "area", "volume"): if getattr(model_units, key) is None: msg = f"'{key}' should be set in 'model_units'." if key in ["time", "extent", "substance", "volume"]: # strongly recommended fields logger.warning(msg) else: # optional fields logger.info(msg) continue unit: Union[str, UnitDefinition] = getattr(model_units, key) uid = UnitDefinition.get_uid_for_unit(unit=unit) # set the values if key == "time": model.setTimeUnits(uid) elif key == "extent": model.setExtentUnits(uid) elif key == "substance": model.setSubstanceUnits(uid) elif key == "length": model.setLengthUnits(uid) elif key == "area": model.setAreaUnits(uid) elif key == "volume": model.setVolumeUnits(uid)
def set_model_history( sbase: libsbml.SBase, creators: List[Creator], set_timestamps: bool = False ) -> None: """Set the model history from given creators. :param sbase: SBML model :param creators: list of creators :param set_timestamps: boolean flag to set timestamps on history. :return: """ if not sbase.isSetMetaId(): sbase.setMetaId(create_metaid(sbase=sbase)) # create and set model history h = _create_history(creators=creators, set_timestamps=set_timestamps) check(sbase.setModelHistory(h), "set model history") def _create_history( creators: Iterable[Creator], set_timestamps: bool = False ) -> libsbml.ModelHistory: """Create the model history. Sets the create and modified date to the current time. The `set_timestamps` flag allows to set no timestamps. """ h: libsbml.ModelHistory = libsbml.ModelHistory() for creator in creators: c: libsbml.ModelCreator = libsbml.ModelCreator() if creator.familyName: c.setFamilyName(creator.familyName) if creator.givenName: c.setGivenName(creator.givenName) if creator.email: c.setEmail(creator.email) if creator.organization: c.setOrganization(creator.organization) check(h.addCreator(c), "add creator") # create time is now if set_timestamps: datetime = date_now() check(h.setCreatedDate(datetime), "set creation date") check(h.setModifiedDate(datetime), "set modified date") else: datetime = libsbml.Date("1900-01-01T00:00:00") check(h.setCreatedDate(datetime), "set creation date") check(h.setModifiedDate(datetime), "set modified date") return h def date_now() -> libsbml.Date: """Get current time stamp for history. :return: current libsbml Date """ time = datetime.datetime.now() timestr = time.strftime("%Y-%m-%dT%H:%M:%S") return libsbml.Date(timestr) class Sbase: """Base class of all SBML objects.""" def __init__( self, sid: Optional[str] = None, name: Optional[str] = None, sboTerm: Optional[str] = None, metaId: Optional[str] = None, annotations: OptionalAnnotationsType = None, notes: Optional[str] = None, keyValuePairs: Optional[List[KeyValuePair]] = None, port: Any = None, uncertainties: Optional[List[Uncertainty]] = None, replacedBy: Optional[Any] = None, ): self.sid = sid self.name = name self.sboTerm = sboTerm self.metaId = metaId self.notes = notes self.keyValuePairs = keyValuePairs self.port = port self.uncertainties = uncertainties self.replacedBy = replacedBy self.annotations: AnnotationsType = annotations if annotations else [] fields = [ "sid", "name", "sboTerm", "metaId", "notes", "keyValuePairs", "port", "uncertainties", "replacedBy", "annotations", ] def __str__(self) -> str: """Get string.""" field_str = ", ".join( [str(getattr(self, f)) for f in self.fields if getattr(self, f)] ) return f"{self.__class__.__name__}({field_str})" @staticmethod def _process_annotations(annotation_objects: AnnotationsType) -> List[Annotation]: """Process annotation information. Various annotation formats are supported which have to be unified at some point. This function is performing the annotation normalization. """ annotations: List[Annotation] = [] if annotation_objects is not None: for annotation_obj in annotation_objects: annotation: Annotation if isinstance(annotation_obj, Annotation): annotation = annotation_obj elif isinstance(annotation_obj, (tuple, list, set)): annotation = Annotation.from_tuple(annotation_obj) # type: ignore annotations.append(annotation) return annotations def get_notes_xml(self) -> Optional[str]: """Get notes xml string.""" if self.notes: notes_str: Optional[str] = str(Notes(self.notes).xml) return notes_str return None def _set_fields(self, sbase: libsbml.SBase, model: Optional[libsbml.Model]) -> None: if self.sid is not None: if not libsbml.SyntaxChecker.isValidSBMLSId(self.sid): logger.error( f"The id `{self.sid}` is not a valid SBML SId on `{sbase}`. " f"The SId syntax is defined as:" f"\tletter ::= 'a'..'z','A'..'Z'" f"\tdigit ::= '0'..'9'" f"\tidChar ::= letter | digit | '_'" f"\tSId ::= ( letter | '_' ) idChar*" ) sbase.setId(self.sid) if self.name is not None: sbase.setName(self.name) else: if not isinstance( self, (Document, Port, ReplacedBy, ReplacedElement, AssignmentRule) ): logger.warning(f"'name' should be set on '{self}'") if self.sboTerm is not None: if isinstance(self.sboTerm, SBO): sbo = self.sboTerm.value.replace("_", ":") elif isinstance(self.sboTerm, str): sbo = self.sboTerm.replace("_", ":") else: sbo = self.sboTerm sbase.setSBOTerm(sbo) else: if not isinstance( self, ( Document, Port, UnitDefinition, Model, ReplacedBy, ReplacedElement, AssignmentRule, RateRule, ExternalModelDefinition, Submodel, ), ): logger.warning(f"'sboTerm' should be set on '{self}'") if self.metaId is not None: sbase.setMetaId(self.metaId) if self.notes is not None and self.notes.strip(): set_notes(sbase, self.notes) # annotation handling processed_annotations: List[Annotation] = [] if self.annotations: # annotations can have been added after initial processing processed_annotations = Sbase._process_annotations(self.annotations) if self.sboTerm is not None: sbo_annotation = Annotation( qualifier=BQB.IS, resource=f"sbo/{self.sboTerm.replace('_', ':')}" ) # check if SBO annotation exists sbo_exists = False for annotation in processed_annotations: if ( annotation.qualifier == sbo_annotation.qualifier and annotation.term == sbo_annotation.term ): sbo_exists = True continue if not sbo_exists: processed_annotations = [sbo_annotation] + processed_annotations for annotation in processed_annotations: annotator.ModelAnnotator.annotate_sbase(sbase=sbase, annotation=annotation) if model: self.create_uncertainties(sbase, model) self.create_replaced_by(sbase, model) if self.keyValuePairs is not None: self.create_key_value_pairs(sbase) def create_port(self, model: libsbml.Model) -> Optional[libsbml.Port]: """Create port if existing.""" if self.port is None: return None p: libsbml.Port = None if isinstance(self.port, bool): if self.port is True: # manually create port for the id cmodel = model.getPlugin("comp") p = cmodel.createPort() if isinstance(self, UnitDefinition): port_sid = f"{self.sid}{PORT_UNIT_SUFFIX}" else: port_sid = f"{self.sid}{PORT_SUFFIX}" p.setId(port_sid) p.setName(f"Port of {self.sid}") p.setMetaId(port_sid) sbo = SBO.PORT.value.replace("_", ":") p.setSBOTerm(sbo) if isinstance(self, UnitDefinition): p.setUnitRef(self.sid) else: p.setIdRef(self.sid) else: # use the port object if ( (not self.port.portRef) and (not self.port.idRef) and (not self.port.unitRef) and (not self.port.metaIdRef) ): # if no reference set id reference to current object self.port.idRef = self.sid p = self.port.create_sbml(model) return p def create_uncertainties( self, obj: libsbml.SBase, model: libsbml.Model ) -> Optional[List[libsbml.Uncertainty]]: """Create distrib:Uncertainty objects.""" if not self.uncertainties: return None objects = [] # FIXME: check that distrib package is activated for uncertainty in self.uncertainties: # type: Uncertainty objects.append(uncertainty.create_sbml(obj, model)) return objects def create_replaced_by( self, sbase: libsbml.SBase, model: libsbml.Model ) -> Optional[libsbml.ReplacedBy]: """Create comp:ReplacedBy.""" if not self.replacedBy: return None return self.replacedBy.create_sbml(sbase, model) def create_key_value_pairs( self, sbase: libsbml.SBase ) -> Optional[List[libsbml.KeyValuePair]]: """Create fbc:keyValuePair.""" if not self.keyValuePairs: return None kvps: List[libsbml.KeyValuePair] = [] for kvp in self.keyValuePairs: kvps.append(kvp.create_sbml(sbase)) return kvps
[docs]class KeyValuePair(Sbase): """KeyValuePair.""" def __init__( self, key: str, value: Optional[str], uri: Optional[str], sid: Optional[str] = None, name: Optional[str] = None, sboTerm: Optional[str] = None, metaId: Optional[str] = None, notes: Optional[str] = None, annotations: OptionalAnnotationsType = None, keyValuePairs: Optional[List[KeyValuePair]] = None, port: Any = None, uncertainties: Optional[List[Uncertainty]] = None, replacedBy: Optional[Any] = None, ): """Create a KeyValuePair.""" super(KeyValuePair, self).__init__( sid=sid, name=name, sboTerm=sboTerm, metaId=metaId, annotations=annotations, notes=notes, keyValuePairs=keyValuePairs, port=port, uncertainties=uncertainties, replacedBy=replacedBy, ) self.key = key self.value = value self.uri = uri
[docs] def create_sbml(self, sbase: libsbml.SBase) -> libsbml.KeyValuePair: """Create KeyValuePair on object.""" sbase_fbc: libsbml.FbcSBasePlugin = sbase.getPlugin("fbc") kvp_list: libsbml.ListOfKeyValuePairs = sbase_fbc.getListOfKeyValuePairs() kvp_list.setXmlns("http://sbml.org/fbc/keyvaluepair") kvp: libsbml.KeyValuePair = kvp_list.createKeyValuePair() check(kvp.setKey(self.key), "Set Key on KeyValuePair") if self.value is not None: check(kvp.setValue(self.value), f"Set `value={self.value}` on KeyValuePair") if self.uri is not None: check(kvp.setValue(self.value), f"Set `uri={self.uri}` on KeyValuePair") return kvp
class Value(Sbase): """Helper class. The value field is a helper storage field which is used differently by different subclasses. """ def __init__( self, sid: Optional[str], value: Union[str, float], name: Optional[str] = None, sboTerm: Optional[str] = None, metaId: Optional[str] = None, annotations: OptionalAnnotationsType = None, notes: Optional[str] = None, keyValuePairs: Optional[List[KeyValuePair]] = None, port: Any = None, uncertainties: Optional[List[Uncertainty]] = None, replacedBy: Optional[Any] = None, ): super(Value, self).__init__( sid, name=name, sboTerm=sboTerm, metaId=metaId, annotations=annotations, notes=notes, keyValuePairs=keyValuePairs, port=port, uncertainties=uncertainties, replacedBy=replacedBy, ) self.value = value def _set_fields(self, sbase: libsbml.SBase, model: libsbml.Model) -> None: super(Value, self)._set_fields(sbase, model)
[docs]class UnitDefinition(Sbase): """Unit. Corresponds to the information in the libsbml.UnitDefinition. """ # definition: str = (None,)
[docs] _pint2sbml = { "dimensionless": libsbml.UNIT_KIND_DIMENSIONLESS, "ampere": libsbml.UNIT_KIND_AMPERE, # None: libsbml.UNIT_KIND_BECQUEREL, # "becquerel": libsbml.UNIT_KIND_BECQUEREL, "candela": libsbml.UNIT_KIND_CANDELA, "degree_Celsius": libsbml.UNIT_KIND_CELSIUS, "coulomb": libsbml.UNIT_KIND_COULOMB, "farad": libsbml.UNIT_KIND_FARAD, "gram": libsbml.UNIT_KIND_GRAM, "gray": libsbml.UNIT_KIND_GRAY, "hertz": libsbml.UNIT_KIND_HERTZ, "item": libsbml.UNIT_KIND_ITEM, "kelvin": libsbml.UNIT_KIND_KELVIN, "kilogram": libsbml.UNIT_KIND_KILOGRAM, "liter": libsbml.UNIT_KIND_LITRE, "meter": libsbml.UNIT_KIND_METRE, "mole": libsbml.UNIT_KIND_MOLE, "newton": libsbml.UNIT_KIND_NEWTON, "ohm": libsbml.UNIT_KIND_OHM, "second": libsbml.UNIT_KIND_SECOND, "volt": libsbml.UNIT_KIND_VOLT, }
# see https://github.com/hgrecco/pint/blob/master/pint/default_en.txt
[docs] _prefixes = { "yocto": 1e-24, "zepto": 1e-21, "atto": 1e-18, "femto": 1e-15, "pico": 1e-12, "nano": 1e-9, "micro": 1e-6, "milli": 1e-3, "centi": 1e-2, "deci": 1e-1, "deca": 1e1, "hecto": 1e2, "kilo": 1e3, "mega": 1e6, "giga": 1e9, "tera": 1e12, "peta": 1e15, "exa": 1e18, "zetta": 1e21, "yotta": 1e24, }
def __init__( self, sid: str, definition: Optional[str] = None, name: Optional[str] = None, sboTerm: Optional[str] = None, metaId: Optional[str] = None, annotations: OptionalAnnotationsType = None, notes: Optional[str] = None, keyValuePairs: Optional[List[KeyValuePair]] = None, port: Any = None, replacedBy: Optional[Any] = None, ): """Construct UnitDefinition.""" super(UnitDefinition, self).__init__( sid=sid, name=name, sboTerm=sboTerm, metaId=metaId, annotations=annotations, notes=notes, keyValuePairs=keyValuePairs, port=port, replacedBy=replacedBy, ) self.definition = definition if definition is not None else sid if not self.name: self.name = self.definition
[docs] class Config: """Pydantic configuration."""
[docs] arbitrary_types_allowed = True
[docs] def create_sbml(self, model: libsbml.Model) -> Optional[libsbml.UnitDefinition]: """Create libsbml.UnitDefinition.""" if isinstance(self.definition, int): # libsbml unit type return None obj: libsbml.UnitDefinition = model.createUnitDefinition() # parse the string into pint quantity = Q_(self.definition) magnitude, units = quantity.to_tuple() if units: for k, item in enumerate(units): prefix, unit_name, suffix = ureg.parse_unit_name(item[0])[0] exponent = item[1] # first unit gets the multiplier multiplier = 1.0 if k == 0: multiplier = magnitude if prefix: multiplier = multiplier * self.__class__._prefixes[prefix] multiplier = np.power(multiplier, 1 / abs(exponent)) scale = 0 # resolve the kind (this is already a unit known by libsbml) kind = self.__class__._pint2sbml.get(unit_name, None) if kind is None: # we have to bring the unit to base units uq = Q_(unit_name).to_base_units() # console.log("uq:", uq) multiplier = multiplier * uq.magnitude kind = self.__class__._pint2sbml.get(str(uq.units), None) if kind is None: msg = ( f"Unit '{uq.units}' in definition " f"'{self.definition}' could not be converted to SBML." ) logger.error(msg) raise ValueError(msg) self._create_unit(obj, kind, exponent, scale, multiplier) else: # only magnitude (units canceled) kind = self.__class__._pint2sbml["dimensionless"] self._create_unit(obj, kind, 1.0, 0, magnitude) self._set_fields(obj, model) self.create_port(model) return obj
[docs] def _set_fields(self, sbase: libsbml.UnitDefinition, model: libsbml.Model) -> None: """Set fields on libsbml.UnitDefinition.""" super(UnitDefinition, self)._set_fields(sbase, model)
@staticmethod
[docs] def _create_unit( udef: libsbml.UnitDefinition, kind: str, exponent: float, scale: int = 0, multiplier: float = 1.0, ) -> libsbml.Unit: """Create libsbml.Unit.""" unit: libsbml.Unit = udef.createUnit() unit.setKind(kind) unit.setExponent(exponent) unit.setScale(scale) unit.setMultiplier(multiplier) return unit
@staticmethod
[docs] def get_uid_for_unit(unit: Union[UnitDefinition, str]) -> Optional[str]: """Get unit id for given definition string.""" uid: Optional[str] if unit is None: uid = None elif isinstance(unit, UnitDefinition): uid = unit.sid else: raise ValueError( f"unit must be a 'UnitDefinition', but '{unit}' is " f"'{type(unit)}. Best practise is to use a `class U(Units)` for " f"units definitions." ) return uid
[docs]class Units: """Base class for unit definitions.""" # libsbml units
[docs] dimensionless = UnitDefinition( "dimensionless", libsbml.UNIT_KIND_DIMENSIONLESS, name="dimensionless" )
[docs] ampere = UnitDefinition("ampere", libsbml.UNIT_KIND_AMPERE, name="ampere")
[docs] becquerel = UnitDefinition( "becquerel", libsbml.UNIT_KIND_BECQUEREL, name="becquerel" )
[docs] candela = UnitDefinition("candela", libsbml.UNIT_KIND_CANDELA, name="candela")
[docs] degree_Celsius = UnitDefinition( "degree_Celsius", libsbml.UNIT_KIND_CELSIUS, name="degree_Celsius" )
[docs] coulomb = UnitDefinition("coulomb", libsbml.UNIT_KIND_COULOMB, name="coulomb")
[docs] farad = UnitDefinition("farad", libsbml.UNIT_KIND_FARAD, name="farad")
[docs] gram = UnitDefinition("gram", libsbml.UNIT_KIND_GRAM, name="gram")
[docs] gray = UnitDefinition("gray", libsbml.UNIT_KIND_GRAY, name="gray")
[docs] hertz = UnitDefinition("hertz", libsbml.UNIT_KIND_HERTZ, name="hertz")
[docs] item = UnitDefinition("item", libsbml.UNIT_KIND_ITEM, name="item")
[docs] kelvin = UnitDefinition("kelvin", libsbml.UNIT_KIND_KELVIN, name="kelvin")
[docs] kilogram = UnitDefinition("kilogram", libsbml.UNIT_KIND_KILOGRAM, name="kilogram")
[docs] liter = UnitDefinition("litre", libsbml.UNIT_KIND_LITRE, name="liter")
[docs] litre = UnitDefinition("litre", libsbml.UNIT_KIND_LITRE, name="liter")
[docs] meter = UnitDefinition("metre", libsbml.UNIT_KIND_METRE, name="meter")
[docs] metre = UnitDefinition("metre", libsbml.UNIT_KIND_METRE, name="metre")
[docs] mole = UnitDefinition("mole", libsbml.UNIT_KIND_MOLE, name="mole")
[docs] newton = UnitDefinition("newton", libsbml.UNIT_KIND_NEWTON, name="newton")
[docs] ohm = UnitDefinition("ohm", libsbml.UNIT_KIND_OHM, name="ohm")
[docs] second = UnitDefinition("second", libsbml.UNIT_KIND_SECOND, name="second")
[docs] volt = UnitDefinition("volt", libsbml.UNIT_KIND_VOLT, name="volt")
@classmethod
[docs] def attributes(cls) -> List[Tuple[str, Union[str, "UnitDefinition"]]]: """Get the attributes list.""" attributes = inspect.getmembers(cls, lambda a: not (inspect.isroutine(a))) return [ a for a in attributes if not (a[0].startswith("__") and a[0].endswith("__")) ]
@classmethod
[docs] def create_unit_definitions(cls, model: libsbml.Model) -> None: """Create the libsbml.UnitDefinitions in the model.""" unit_definition: UnitDefinition uid: str for uid, definition in cls.attributes(): if isinstance(definition, str): unit_definition = UnitDefinition(sid=uid, definition=definition) elif isinstance(definition, UnitDefinition): unit_definition = definition else: raise ValueError( f"Units attributes must be a unit string or UnitDefinition, " f"but '{type(definition)} for '{definition}'." ) # create and register libsbml.UnitDefinition in libsbml.Model try: _: libsbml.UnitDefinition = unit_definition.create_sbml(model=model) except UndefinedUnitError as err: console.print_exception(show_locals=False) logger.error( f"Unit definition '{unit_definition.definition}' is not valid " f"pint syntax, {err}." ) raise err
class ValueWithUnit(Value): """Helper class. The value field is a helper storage field which is used differently by different subclasses. """ def __repr__(self) -> str: """Get string representation.""" return f"{self.sid} = {self.value} [{self.unit}]" def __init__( self, sid: str, value: Union[str, float], unit: UnitType = Units.dimensionless, name: Optional[str] = None, sboTerm: Optional[str] = None, metaId: Optional[str] = None, annotations: OptionalAnnotationsType = None, notes: Optional[str] = None, keyValuePairs: Optional[List[KeyValuePair]] = None, port: Any = None, uncertainties: Optional[List[Uncertainty]] = None, replacedBy: Optional[Any] = None, ): super(ValueWithUnit, self).__init__( sid, value, name=name, sboTerm=sboTerm, metaId=metaId, port=port, annotations=annotations, notes=notes, keyValuePairs=keyValuePairs, uncertainties=uncertainties, replacedBy=replacedBy, ) self.unit = unit if self.unit and not isinstance(self.unit, UnitDefinition): logger.warning( f"'unit' must be of type UnitDefinition, but '{self.unit}' " f"in '{self}' is '{type(self.unit)}'." ) def _set_fields(self, sbase: libsbml.SBase, model: libsbml.Model) -> None: super(ValueWithUnit, self)._set_fields(sbase, model) if self.unit is not None: if sbase.getTypeCode() in [ libsbml.SBML_ASSIGNMENT_RULE, libsbml.SBML_RATE_RULE, libsbml.SBML_ALGEBRAIC_RULE, ]: # AssignmentRules, RateRules and AlgebraicRules have no units pass else: uid = UnitDefinition.get_uid_for_unit(unit=self.unit) check(sbase.setUnits(uid), f"Set unit '{uid}' on {sbase}")
[docs]class Function(Sbase): """SBML FunctionDefinitions. FunctionDefinitions consist of a lambda expression in the value field, e.g., lambda(x,y, piecewise(x,gt(x,y),y) ) # definition of minimum function lambda(x, sin(x) ) """ def __init__( self, sid: str, value: str, name: Optional[str] = None, sboTerm: Optional[str] = None, metaId: Optional[str] = None, annotations: OptionalAnnotationsType = None, notes: Optional[str] = None, keyValuePairs: Optional[List[KeyValuePair]] = None, port: Any = None, uncertainties: Optional[List[Uncertainty]] = None, replacedBy: Optional[Any] = None, ): """Construct Function.""" super(Function, self).__init__( sid=sid, name=name, sboTerm=sboTerm, metaId=metaId, annotations=annotations, notes=notes, keyValuePairs=keyValuePairs, port=port, uncertainties=uncertainties, replacedBy=replacedBy, ) self.formula = value
[docs] def create_sbml(self, model: libsbml.Model) -> libsbml.FunctionDefinition: """Create FunctionDefinition SBML in model.""" fd: libsbml.FunctionDefinition = model.createFunctionDefinition() self._set_fields(fd, model) self.create_port(model) return fd
[docs] def _set_fields( self, sbase: libsbml.FunctionDefinition, model: libsbml.Model ) -> None: super(Function, self)._set_fields(sbase, model) ast_node = ast_node_from_formula(model, self.formula) sbase.setMath(ast_node)
[docs]class Parameter(ValueWithUnit): """Parameter.""" def __init__( self, sid: str, value: Optional[Union[str, float]] = None, unit: UnitType = None, constant: bool = True, name: Optional[str] = None, sboTerm: Optional[str] = None, metaId: Optional[str] = None, annotations: OptionalAnnotationsType = None, notes: Optional[str] = None, keyValuePairs: Optional[List[KeyValuePair]] = None, port: Any = None, uncertainties: Optional[List[Uncertainty]] = None, replacedBy: Optional[Any] = None, ): """Construct Parameter.""" super(Parameter, self).__init__( sid=sid, value=value, # type: ignore unit=unit, name=name, sboTerm=sboTerm, metaId=metaId, annotations=annotations, notes=notes, keyValuePairs=keyValuePairs, port=port, uncertainties=uncertainties, replacedBy=replacedBy, ) self.constant = constant
[docs] def create_sbml(self, model: libsbml.Model) -> libsbml.Parameter: """Create Parameter SBML in model.""" obj: libsbml.Parameter = model.createParameter() self._set_fields(obj, model) if self.value is None: obj.setValue(np.NaN) elif type(self.value) is str: try: # check if number value = float(self.value) logger.warning( f"When setting a numeric value use float not str: '{self}'." ) obj.setValue(value) except ValueError: if self.constant: InitialAssignment(self.sid, self.value).create_sbml(model) # type: ignore else: AssignmentRule(self.sid, self.value).create_sbml(model) # type: ignore else: # numerical value obj.setValue(float(self.value)) self.create_port(model) return obj
[docs] def _set_fields(self, sbase: libsbml.Parameter, model: libsbml.Model) -> None: """Set fields.""" super(Parameter, self)._set_fields(sbase, model) sbase.setConstant(self.constant)
[docs]class Compartment(ValueWithUnit): """Compartment.""" def __init__( self, sid: str, value: Union[str, float], unit: UnitType = None, constant: bool = True, spatialDimensions: float = 3, name: Optional[str] = None, sboTerm: Optional[str] = None, metaId: Optional[str] = None, annotations: OptionalAnnotationsType = None, notes: Optional[str] = None, keyValuePairs: Optional[List[KeyValuePair]] = None, port: Any = None, uncertainties: Optional[List[Uncertainty]] = None, replacedBy: Optional[Any] = None, ): """Construct Compartment.""" super(Compartment, self).__init__( sid=sid, value=value, unit=unit, name=name, sboTerm=sboTerm, metaId=metaId, annotations=annotations, notes=notes, keyValuePairs=keyValuePairs, port=port, uncertainties=uncertainties, replacedBy=replacedBy, ) self.constant = constant self.spatialDimensions = spatialDimensions
[docs] def create_sbml(self, model: libsbml.Model) -> libsbml.Compartment: """Create Compartment SBML in model.""" obj: libsbml.Compartment = model.createCompartment() self._set_fields(obj, model) if self.value is None: obj.setSize(np.NaN) elif type(self.value) is str: try: # check if number value = float(self.value) logger.warning( f"When setting a numeric value use float not str: '{self}'." ) obj.setSize(value) except ValueError: if self.constant: InitialAssignment(self.sid, self.value).create_sbml(model) # type: ignore else: AssignmentRule(self.sid, self.value).create_sbml(model) # type: ignore else: obj.setSize(float(self.value)) self.create_port(model) return obj
[docs] def _set_fields(self, sbase: libsbml.Compartment, model: libsbml.Model) -> None: """Set fields on Compartment.""" super(Compartment, self)._set_fields(sbase, model) sbase.setConstant(self.constant) sbase.setSpatialDimensions(self.spatialDimensions)
[docs]class Species(Sbase): """Species.""" def __init__( self, sid: str, compartment: str, initialAmount: Optional[float] = None, initialConcentration: Optional[float] = None, substanceUnit: UnitType = None, hasOnlySubstanceUnits: bool = False, # default: concentrations constant: bool = False, boundaryCondition: bool = False, charge: Optional[float] = None, chemicalFormula: Optional[str] = None, conversionFactor: Optional[str] = None, name: Optional[str] = None, sboTerm: Optional[str] = None, metaId: Optional[str] = None, annotations: OptionalAnnotationsType = None, notes: Optional[str] = None, keyValuePairs: Optional[List[KeyValuePair]] = None, port: Any = None, uncertainties: Optional[List[Uncertainty]] = None, replacedBy: Optional[Any] = None, ): """Construct Species.""" super(Species, self).__init__( sid=sid, name=name, sboTerm=sboTerm, metaId=metaId, annotations=annotations, notes=notes, keyValuePairs=keyValuePairs, port=port, uncertainties=uncertainties, replacedBy=replacedBy, ) if (initialAmount is None) and (initialConcentration is None): logger.warning( f"Either initialAmount or initialConcentration should be set " f"for species: `{sid}`." ) if initialAmount and initialConcentration: raise ValueError( f"Either initialAmount or initialConcentration can be set on " f"species, but not both: `{sid}`." ) self.substanceUnits = substanceUnit self.initialAmount = initialAmount self.initialConcentration = initialConcentration self.compartment = compartment self.constant = constant self.boundaryCondition = boundaryCondition self.hasOnlySubstanceUnits = hasOnlySubstanceUnits self.charge = charge self.chemicalFormula = chemicalFormula self.conversionFactor = conversionFactor
[docs] def create_sbml(self, model: libsbml.Model) -> libsbml.Species: """Create Species SBML in model.""" s: libsbml.Species = model.createSpecies() self._set_fields(s, model) self.create_port(model) return s
[docs] def _set_fields(self, sbase: libsbml.Species, model: libsbml.Model) -> None: """Set fields on libsbml.Species.""" super(Species, self)._set_fields(sbase, model) sbase.setConstant(self.constant) if self.compartment is None: raise ValueError(f"Compartment cannot be None on Species: '{self}'") sbase.setCompartment(self.compartment) sbase.setBoundaryCondition(self.boundaryCondition) sbase.setHasOnlySubstanceUnits(self.hasOnlySubstanceUnits) sbase.setSubstanceUnits(model.getSubstanceUnits()) if self.substanceUnits is not None: sbase.setSubstanceUnits( UnitDefinition.get_uid_for_unit(unit=self.substanceUnits) ) else: # Fallback to model units sbase.setSubstanceUnits(model.getSubstanceUnits()) if self.initialAmount is not None: sbase.setInitialAmount(self.initialAmount) if self.initialConcentration is not None: sbase.setInitialConcentration(self.initialConcentration) if self.conversionFactor is not None: sbase.setConversionFactor(self.conversionFactor) # fbc if (self.charge is not None) or (self.chemicalFormula is not None): obj_fbc: libsbml.FbcSpeciesPlugin = sbase.getPlugin("fbc") if obj_fbc is None: logger.error( "FbcSpeciesPlugin does not exist, add `packages = ['fbc']` " "to model definition." ) else: if self.charge is not None: obj_fbc.setCharge(self.charge) if self.chemicalFormula is not None: obj_fbc.setChemicalFormula(self.chemicalFormula)
[docs]class InitialAssignment(Value): """InitialAssignments. The unit attribute is only for the case where a parameter must be created (which has the unit). In case of an initialAssignment of a value the units have to be defined in the math. """ def __init__( self, symbol: str, value: Union[str, float], unit: UnitType = Units.dimensionless, sid: Optional[str] = None, name: Optional[str] = None, sboTerm: Optional[str] = None, metaId: Optional[str] = None, annotations: OptionalAnnotationsType = None, notes: Optional[str] = None, keyValuePairs: Optional[List[KeyValuePair]] = None, port: Any = None, uncertainties: Optional[List[Uncertainty]] = None, replacedBy: Optional[Any] = None, ): """Construct InitialAssignment.""" super(InitialAssignment, self).__init__( sid, value, name=name, sboTerm=sboTerm, metaId=metaId, annotations=annotations, notes=notes, keyValuePairs=keyValuePairs, port=port, uncertainties=uncertainties, replacedBy=replacedBy, ) self.symbol = symbol self.unit = unit
[docs] def create_sbml(self, model: libsbml.Model) -> libsbml.InitialAssignment: """Create InitialAssignment. Creates a required parameter if the symbol for the initial assignment does not exist in the model. """ # Create parameter if not existing if ( (not model.getParameter(self.symbol)) and (not model.getSpecies(self.symbol)) and (not model.getCompartment(self.symbol)) and (not model.getSpeciesReference(self.symbol)) ): Parameter( sid=self.symbol, value=None, unit=self.unit, constant=True, name=self.name, ).create_sbml(model) # Check if rule exists if model.getInitialAssignmentBySymbol(self.symbol): logger.error( f"InitialAssignment for symbol '{self.symbol}' already exists in model: . " f"InitialAssignment will be overwritten '{self.value}'" ) obj: libsbml.InitialAssignment = model.createInitialAssignment() self._set_fields(obj, model) obj.setSymbol(self.symbol) ast_node = ast_node_from_formula(model, str(self.value)) obj.setMath(ast_node) self.create_port(model) return obj
class RuleWithVariable: """Rule.""" variable: str value: Union[str, float] unit: UnitType sid: Optional[str] name: Optional[str] def check_model_for_rule(self, model: libsbml.Model) -> None: """Check model for rule requirements. Creates a required parameter if the symbol for the initial assignment does not exist in the model. """ # Create parameter if not existing if ( (not model.getParameter(self.variable)) and (not model.getSpecies(self.variable)) and (not model.getCompartment(self.variable)) and (not model.getSpeciesReference(self.variable)) ): Parameter( sid=self.variable, value=None, unit=self.unit, constant=False, name=self.name, ).create_sbml(model) # Make sure the parameter is const=False p: libsbml.Parameter = model.getParameter(self.variable) if p is not None: if p.getConstant() is True: logger.warning( f"Parameter affected by AssignmentRule " f"must be 'constant=False', but '{p.getId()}' " f"is 'constant={p.getConstant()}'." ) p.setConstant(False) # Check if rule exists if model.getRuleByVariable(self.variable): logger.error( f"Rule with target variable `{self.variable}` already exists in model: . " f"Existing rule will be overwritten with `{self.value}`." )
[docs]class AssignmentRule(ValueWithUnit, RuleWithVariable): """AssignmentRule. The unit attribute is only for the case where a parameter must be created (which has the unit). In case of an initialAssignment of a value the units have to be defined in the math. """
[docs] def __repr__(self) -> str: """Get string representation.""" return f"{self.variable} = {self.value} [{self.unit}]"
def __init__( self, variable: str, value: Union[str, float], unit: UnitType = Units.dimensionless, sid: Optional[str] = None, name: Optional[str] = None, sboTerm: Optional[str] = None, metaId: Optional[str] = None, annotations: OptionalAnnotationsType = None, notes: Optional[str] = None, keyValuePairs: Optional[List[KeyValuePair]] = None, port: Any = None, uncertainties: Optional[List[Uncertainty]] = None, replacedBy: Optional[Any] = None, ): """Construct AssignmentRule.""" super(AssignmentRule, self).__init__( sid=sid if sid else f"AssignmentRule_{variable}", value=value, unit=unit, name=name, sboTerm=sboTerm, metaId=metaId, annotations=annotations, notes=notes, keyValuePairs=keyValuePairs, port=port, uncertainties=uncertainties, replacedBy=replacedBy, ) self.variable: str = variable
[docs] def create_sbml(self, model: libsbml.Model) -> libsbml.AssignmentRule: """Create AssignmentRule.""" self.check_model_for_rule(model) obj: libsbml.AssignmentRule = model.createAssignmentRule() self._set_fields(obj, model) obj.setVariable(self.variable) ast_node: libsbml.ASTNode = ast_node_from_formula(model, str(self.value)) obj.setMath(ast_node) self.create_port(model) return obj
[docs]class RateRule(ValueWithUnit, RuleWithVariable): """RateRule."""
[docs] def __repr__(self) -> str: """Get string representation.""" return f"d{self.variable}/dt = {self.value} [{self.unit}]"
def __init__( self, variable: str, value: Union[str, float], unit: UnitType = Units.dimensionless, sid: Optional[str] = None, name: Optional[str] = None, sboTerm: Optional[str] = None, metaId: Optional[str] = None, annotations: OptionalAnnotationsType = None, notes: Optional[str] = None, keyValuePairs: Optional[List[KeyValuePair]] = None, port: Any = None, uncertainties: Optional[List[Uncertainty]] = None, replacedBy: Optional[Any] = None, ): """Construct RateRule.""" super(RateRule, self).__init__( sid=sid if sid else f"RateRule_{variable}", value=value, unit=unit, name=name, sboTerm=sboTerm, metaId=metaId, annotations=annotations, notes=notes, keyValuePairs=keyValuePairs, port=port, uncertainties=uncertainties, replacedBy=replacedBy, ) self.variable: str = variable
[docs] def create_sbml(self, model: libsbml.Model) -> libsbml.RateRule: """Create RateRule.""" self.check_model_for_rule(model) obj: libsbml.RateRule = model.createRateRule() self._set_fields(obj, model) obj.setVariable(self.variable) ast_node: libsbml.ASTNode = ast_node_from_formula(model, str(self.value)) obj.setMath(ast_node) self.create_port(model) return obj
[docs]class AlgebraicRule(ValueWithUnit, RuleWithVariable): """AlgebraicRule."""
[docs] def __repr__(self) -> str: """Get string representation.""" return f"0 = {self.value} [{self.unit}]"
def __init__( self, sid: str, value: Union[str, float], unit: UnitType = Units.dimensionless, name: Optional[str] = None, sboTerm: Optional[str] = None, metaId: Optional[str] = None, annotations: OptionalAnnotationsType = None, notes: Optional[str] = None, keyValuePairs: Optional[List[KeyValuePair]] = None, port: Any = None, uncertainties: Optional[List[Uncertainty]] = None, replacedBy: Optional[Any] = None, ): """Construct AlgebraicRule.""" super(AlgebraicRule, self).__init__( sid=sid, value=value, unit=unit, name=name, sboTerm=sboTerm, metaId=metaId, annotations=annotations, notes=notes, keyValuePairs=keyValuePairs, port=port, uncertainties=uncertainties, replacedBy=replacedBy, )
[docs] def create_sbml(self, model: libsbml.Model) -> libsbml.AlgebraicRule: """Create AlgebraicRule.""" rule: libsbml.AlgebraicRule = model.createAlgebraicRule() self._set_fields(rule, model) ast_node: libsbml.ASTNode = ast_node_from_formula(model, str(self.value)) rule.setMath(ast_node) self.create_port(model) return rule
[docs]Formula = namedtuple("Formula", "value unit")
[docs]class Reaction(Sbase): """Reaction. Class for creating libsbml.Reaction. Equations are of the form '1.0 S1 + 2 S2 => 2.0 P1 + 2 P2 [M1, M2]' The equation consists of - substrates concatenated via '+' on the left side (with optional stoichiometric coefficients) - separation characters separating the left and right equation sides: '<=>' or '<->' for reversible reactions, '=>' or '->' for irreversible reactions (irreversible reactions are written from left to right) - products concatenated via '+' on the right side (with optional stoichiometric coefficients) - optional list of modifiers within brackets [] separated by ',' Examples of valid equations are: '1.0 S1 + 2 S2 => 2.0 P1 + 2 P2 [M1, M2]', 'c__gal1p => c__gal + c__phos', 'e__h2oM <-> c__h2oM', '3 atp + 2.0 phos + ki <-> 16.98 tet', 'c__gal1p => c__gal + c__phos [c__udp, c__utp]', 'A_ext => A []', '=> cit', 'acoa =>', """ def __init__( self, sid: str, equation: Union[ReactionEquation, str], formula: Optional[Union[Formula, Tuple[str, UnitType], str]] = None, pars: Optional[List[Parameter]] = None, rules: Optional[List[AssignmentRule]] = None, compartment: Optional[str] = None, fast: bool = False, reversible: Optional[bool] = None, lowerFluxBound: Optional[str] = None, upperFluxBound: Optional[str] = None, geneProductAssociation: Optional[str] = None, name: Optional[str] = None, sboTerm: Optional[str] = None, metaId: Optional[str] = None, annotations: OptionalAnnotationsType = None, notes: Optional[str] = None, keyValuePairs: Optional[List[KeyValuePair]] = None, port: Any = None, uncertainties: Optional[List[Uncertainty]] = None, replacedBy: Optional[Any] = None, ): """Construct Reaction.""" super(Reaction, self).__init__( sid=sid, name=name, sboTerm=sboTerm, metaId=metaId, annotations=annotations, notes=notes, keyValuePairs=keyValuePairs, port=port, uncertainties=uncertainties, replacedBy=replacedBy, ) self.equation = Reaction._process_equation(equation=equation) self.compartment = compartment self.reversible = reversible self.pars = pars if pars else [] self.rules = rules if rules else [] self.formula = Reaction._process_formula(formula=formula) self.fast = fast self.lowerFluxBound = lowerFluxBound self.upperFluxBound = upperFluxBound self.geneProductAssociation = geneProductAssociation @staticmethod
[docs] def _process_equation(equation: Union[ReactionEquation, str]) -> ReactionEquation: """Process reaction equation.""" if isinstance(equation, ReactionEquation): return equation else: return ReactionEquation.from_str(str(equation))
@staticmethod
[docs] def _process_formula( formula: Optional[Union[Formula, Tuple[str, UnitType], str]] ) -> Optional[Formula]: """Process reaction formula (kinetic law).""" if formula is None: return None elif isinstance(formula, Formula): return formula elif isinstance(formula, (tuple, list)): return Formula(*formula) elif isinstance(formula, str): return Formula(value=formula, unit=None) else: raise ValueError(f"Unsupported formula: '{formula}'")
[docs] def create_sbml(self, model: libsbml.Model) -> libsbml.Reaction: """Create Reaction SBML in model.""" # parameters and rules create_objects(model, self.pars, key="parameters") create_objects(model, self.rules, key="rules") # reaction r: libsbml.Reaction = model.createReaction() self._set_fields(r, model) r_fbc: libsbml.FbcReactionPlugin = r.getPlugin("fbc") def set_speciesref_fields( sref: libsbml.SpeciesReference, part: EquationPart ) -> None: """Set the fields on the SpeciesReference.""" if part.species is not None: sref.setSpecies(part.species) if part.sid is not None: sref.setId(part.sid) if part.constant is not None: sref.setConstant(part.constant) if part.stoichiometry is not None: sref.setStoichiometry(part.stoichiometry) if part.metaId is not None: sref.setMetaId(part.metaId) if part.sboTerm is not None: sref.setSBOTerm(part.sboTerm) # equation for reactant in self.equation.reactants: rref: libsbml.SpeciesReference = r.createReactant() set_speciesref_fields(sref=rref, part=reactant) for product in self.equation.products: pref: libsbml.SpeciesReference = r.createProduct() set_speciesref_fields(sref=pref, part=product) for modifier in self.equation.modifiers: mref: libsbml.ModifierSpeciesReference = r.createModifier() mref.setSpecies(modifier) # kinetics if self.formula: Reaction.set_kinetic_law(model, r, self.formula.value) # add fbc bounds if self.upperFluxBound or self.lowerFluxBound: if self.upperFluxBound: r_fbc.setUpperFluxBound(self.upperFluxBound) if self.lowerFluxBound: r_fbc.setLowerFluxBound(self.lowerFluxBound) # add gpa if self.geneProductAssociation: # parse the string and create the respective GPA gpa: libsbml.GeneProductAssociation = r_fbc.createGeneProductAssociation() # check all genes are in model gpr_clean = ( self.geneProductAssociation.replace("(", " ") .replace(")", " ") .replace("and", " ") .replace("AND", "") .replace("or", "") .replace("OR", "") ) gps: List[str] = [g for g in gpr_clean.split(" ") if g] model_fbc: libsbml.FbcModelPlugin = r.getModel().getPlugin("fbc") for gp in gps: if not model_fbc.getGeneProduct(gp): logger.error(f"GeneProduct missing in model: `{gp}`") check( gpa.setAssociation( self.geneProductAssociation, True, # bool usingId=False, False, # bool addMissingGP=True ), f"set gpa: `{self.geneProductAssociation}`", ) self.create_port(model) return r
[docs] def _set_fields(self, sbase: libsbml.Reaction, model: libsbml.Model) -> None: """Set fields in libsbml.Reaction.""" super(Reaction, self)._set_fields(sbase, model) if self.compartment: sbase.setCompartment(self.compartment) # else: # logger.info(f"'compartment' should be set on '{self}'}") sbase.setReversible(self.equation.reversible) sbase.setFast(self.fast)
@staticmethod
[docs] def set_kinetic_law( model: libsbml.Model, reaction: libsbml.Reaction, formula: str ) -> libsbml.KineticLaw: """Set the kinetic law in reaction based on given formula.""" law: libsbml.KineticLaw = reaction.createKineticLaw() ast_node = libsbml.parseL3FormulaWithModel(formula, model) if ast_node is None: logger.error(libsbml.getLastParseL3Error()) check(law.setMath(ast_node), "set math in kinetic law") return law
[docs]class Event(Sbase): """Event. Trigger have the format of a logical expression: time%200 == 0 Assignments have the format sid = value """ def __init__( self, sid: str, trigger: str, assignments: Optional[Dict[str, Union[str, float]]] = None, trigger_persistent: bool = True, trigger_initialValue: bool = False, useValuesFromTriggerTime: bool = True, priority: Optional[str] = None, delay: Optional[str] = None, name: Optional[str] = None, sboTerm: Optional[str] = None, metaId: Optional[str] = None, annotations: OptionalAnnotationsType = None, notes: Optional[str] = None, keyValuePairs: Optional[List[KeyValuePair]] = None, port: Any = None, uncertainties: Optional[List[Uncertainty]] = None, replacedBy: Optional[Any] = None, ): """Construct Event.""" super(Event, self).__init__( sid, name=name, sboTerm=sboTerm, metaId=metaId, annotations=annotations, notes=notes, keyValuePairs=keyValuePairs, port=port, uncertainties=uncertainties, replacedBy=replacedBy, ) self.trigger = trigger self.assignments = assignments if assignments else {} if type(assignments) is not dict: logger.warning( f"Event assignment must be dict with sid: assignment, but: " f"'{type(assignments)}'" ) self.trigger_persistent = trigger_persistent self.trigger_initialValue = trigger_initialValue self.useValuesFromTriggerTime = useValuesFromTriggerTime self.priority = priority self.delay = delay
[docs] def create_sbml(self, model: libsbml.Model) -> libsbml.Event: """Create Event SBML in model.""" event: libsbml.Event = model.createEvent() self._set_fields(event, model) return event
[docs] def _set_fields(self, sbase: libsbml.Event, model: libsbml.Model) -> None: """Set fields in libsbml.Event.""" super(Event, self)._set_fields(sbase, model) sbase.setUseValuesFromTriggerTime(True) t = sbase.createTrigger() t.setInitialValue( self.trigger_initialValue ) # False ! not supported by Copasi -> lame fix via time t.setPersistent( self.trigger_persistent ) # True ! not supported by Copasi -> careful with usage ast_trigger = libsbml.parseL3FormulaWithModel(self.trigger, model) t.setMath(ast_trigger) if self.priority is not None: ast_priority = libsbml.parseL3FormulaWithModel(self.priority, model) priority: libsbml.Priority = sbase.createPriority() priority.setMath(ast_priority) if self.delay is not None: ast_delay = libsbml.parseL3FormulaWithModel(self.delay, model) sbase.setDelay(ast_delay) for key, math in self.assignments.items(): ast_assign = libsbml.parseL3FormulaWithModel(str(math), model) ea = sbase.createEventAssignment() ea.setVariable(key) ea.setMath(ast_assign)
@staticmethod
[docs] def _trigger_from_time(t: float) -> str: """Create trigger from given time point.""" return f"(time >= {t})"
@staticmethod
[docs] def _assignments_dict(species: List[str], values: List[str]) -> Dict[str, str]: return dict(zip(species, values))
[docs]class Constraint(Sbase): """Constraint. The Constraint object is a mechanism for stating the assumptions under which a model is designed to operate. The constraints are statements about permissible values of different quantities in a model. The message must be well formated XHTML, e.g., message='<body xmlns="http://www.w3.org/1999/xhtml">ATP must be non-negative</body>' """ def __init__( self, sid: str, math: str, message: Optional[str] = None, name: Optional[str] = None, sboTerm: Optional[str] = None, metaId: Optional[str] = None, annotations: OptionalAnnotationsType = None, notes: Optional[str] = None, keyValuePairs: Optional[List[KeyValuePair]] = None, port: Any = None, uncertainties: Optional[List[Uncertainty]] = None, replacedBy: Optional[Any] = None, ): """Constraint constructor.""" super(Constraint, self).__init__( sid, name=name, sboTerm=sboTerm, metaId=metaId, annotations=annotations, notes=notes, keyValuePairs=keyValuePairs, port=port, uncertainties=uncertainties, replacedBy=replacedBy, ) self.math = math self.message = message
[docs] def create_sbml(self, model: libsbml.Model) -> libsbml.Constraint: """Create Constraint SBML in model.""" constraint: libsbml.Constraint = model.createConstraint() self._set_fields(constraint, model) return constraint
[docs] def _set_fields(self, sbase: libsbml.Constraint, model: libsbml.Model) -> None: """Set fields on libsbml.Constraint.""" super(Constraint, self)._set_fields(sbase, model) if self.math is not None: ast_math = libsbml.parseL3FormulaWithModel(self.math, model) sbase.setMath(ast_math) if self.message is not None: check( sbase.setMessage(self.message), message=f"Setting message on constraint: '{self.message}'", )
""" --------------------------------------------------------------------------------------- distrib information --------------------------------------------------------------------------------------- """
[docs]class UncertParameter: """UncertParameter. FIXME: This is an SBase! """ def __init__( self, type: str, value: Optional[float] = None, var: Optional[str] = None, unit: UnitType = None, ): """Construct UncertParameter.""" if (value is None) and (var is None): raise ValueError( "Either 'value' or 'var' have to be set in UncertParameter." ) self.type: str = type self.value: Optional[float] = value self.var: Optional[str] = var self.unit: UnitType = unit
[docs]class UncertSpan: """UncertSpan. FIXME: This is an SBase! """ def __init__( self, type: str, valueLower: Optional[float] = None, varLower: Optional[str] = None, valueUpper: Optional[float] = None, varUpper: Optional[str] = None, unit: UnitType = None, ): """Construct UncertSpan.""" if (valueLower is None) and (varLower is None): raise ValueError( "Either 'valueLower' or 'varLower' have to be set in UncertSpan." ) if (valueUpper is None) and (varUpper is None): raise ValueError( "Either 'valueLower' or 'varLower' have to be set in UncertSpan." ) self.type = type self.valueLower = valueLower self.varLower = varLower self.valueUpper = valueUpper self.varUpper = varUpper self.unit = unit
[docs]class Uncertainty(Sbase): """Uncertainty. Uncertainty information for Sbase. """ def __init__( self, sid: Optional[str] = None, formula: Optional[str] = None, uncertParameters: Optional[List[UncertParameter]] = None, uncertSpans: Optional[List[UncertSpan]] = None, name: Optional[str] = None, sboTerm: Optional[str] = None, metaId: Optional[str] = None, annotations: OptionalAnnotationsType = None, notes: Optional[str] = None, keyValuePairs: Optional[List[KeyValuePair]] = None, port: Any = None, replacedBy: Optional[Any] = None, ): """Uncertainty constructor.""" super(Uncertainty, self).__init__( sid, name=name, sboTerm=sboTerm, metaId=metaId, annotations=annotations, notes=notes, keyValuePairs=keyValuePairs, port=port, replacedBy=replacedBy, ) # Object on which the uncertainty is written self.formula = formula self.uncertParameters: List[UncertParameter] = ( uncertParameters if uncertParameters else [] ) self.uncertSpans: List[UncertSpan] = uncertSpans if uncertSpans else []
[docs] def create_sbml( self, sbase: libsbml.SBase, model: libsbml.Model ) -> libsbml.Uncertainty: """Create libsbml Uncertainty. :param sbase: :param model: :return: """ sbase_distrib: libsbml.DistribSBasePlugin = sbase.getPlugin("distrib") uncertainty: libsbml.Uncertainty = sbase_distrib.createUncertainty() self._set_fields(uncertainty, model) uncertSpan: UncertSpan for uncertSpan in self.uncertSpans: if uncertSpan.type in [ libsbml.DISTRIB_UNCERTTYPE_INTERQUARTILERANGE, libsbml.DISTRIB_UNCERTTYPE_CREDIBLEINTERVAL, libsbml.DISTRIB_UNCERTTYPE_CONFIDENCEINTERVAL, libsbml.DISTRIB_UNCERTTYPE_RANGE, ]: up_span: libsbml.UncertSpan = uncertainty.createUncertSpan() up_span.setType(uncertSpan.type) if uncertSpan.valueLower is not None: up_span.setValueLower(uncertSpan.valueLower) if uncertSpan.valueUpper is not None: up_span.setValueUpper(uncertSpan.valueUpper) if uncertSpan.varLower is not None: up_span.setVarLower(uncertSpan.varLower) if uncertSpan.varUpper is not None: up_span.setValueLower(uncertSpan.varUpper) if uncertSpan.unit: up_span.setUnits( UnitDefinition.get_uid_for_unit(unit=uncertSpan.unit) ) else: logger.error( f"Unsupported type for UncertSpan: '{uncertSpan.type}' " f"in '{uncertSpan}'." ) uncertParameter: UncertParameter for uncertParameter in self.uncertParameters: if uncertParameter.type in [ libsbml.DISTRIB_UNCERTTYPE_COEFFIENTOFVARIATION, libsbml.DISTRIB_UNCERTTYPE_KURTOSIS, libsbml.DISTRIB_UNCERTTYPE_MEAN, libsbml.DISTRIB_UNCERTTYPE_MEDIAN, libsbml.DISTRIB_UNCERTTYPE_MODE, libsbml.DISTRIB_UNCERTTYPE_SAMPLESIZE, libsbml.DISTRIB_UNCERTTYPE_SKEWNESS, libsbml.DISTRIB_UNCERTTYPE_STANDARDDEVIATION, libsbml.DISTRIB_UNCERTTYPE_STANDARDERROR, libsbml.DISTRIB_UNCERTTYPE_VARIANCE, ]: up_p: libsbml.UncertParameter = ( uncertainty.createUncertParameter() ) # type: ignore up_p.setType(uncertParameter.type) if uncertParameter.value is not None: up_p.setValue(uncertParameter.value) if uncertParameter.var is not None: up_p.setValue(uncertParameter.var) if uncertParameter.unit: up_p.setUnits( UnitDefinition.get_uid_for_unit(unit=uncertParameter.unit) ) else: logger.error( f"Unsupported type for UncertParameter: " f"'{uncertParameter.type}' in '{uncertParameter}'." ) # create a distribution uncertainty if self.formula: model = sbase.getModel() up_dist: libsbml.UncertParameter = uncertainty.createUncertParameter() up_dist.setType(libsbml.DISTRIB_UNCERTTYPE_DISTRIBUTION) for key in [ "normal", "uniform", "bernoulli", "binomial", "cauchy", "chisquare", "exponential", "gamma", "laplace", "lognormal", "poisson", "raleigh", ]: if key in self.formula: up_dist.setDefinitionURL( f"http://www.sbml.org/sbml/symbols/distrib/{key}" ) ast = libsbml.parseL3FormulaWithModel(self.formula, model) if ast is None: logger.error(libsbml.getLastParseL3Error()) else: check(up_dist.setMath(ast), "set math in distrib formula") return uncertainty
[docs]class ExchangeReaction(Reaction): """Exchange reactions define substances which can be exchanged. This is important for FBC models. EXCHANGE_IMPORT (-INF, 0): is defined as negative flux through the exchange reaction, i.e. the upper bound must be 0, the lower bound some negative value, e.g. -INF EXCHANGE_EXPORT (0, INF): is defined as positive flux through the exchange reaction, i.e. the lower bound must be 0, the upper bound some positive value, e.g. INF """
[docs] PREFIX = "EX_"
def __init__( self, species_id: str, compartment: Optional[str] = None, fast: bool = False, reversible: bool = True, lowerFluxBound: Optional[str] = None, upperFluxBound: Optional[str] = None, geneProductAssociation: Optional[str] = None, name: Optional[str] = None, metaId: Optional[str] = None, annotations: OptionalAnnotationsType = None, notes: Optional[str] = None, keyValuePairs: Optional[List[KeyValuePair]] = None, port: Any = None, uncertainties: Optional[List[Uncertainty]] = None, replacedBy: Optional[Any] = None, ): """Construct ExchangeReaction.""" super(ExchangeReaction, self).__init__( sid=ExchangeReaction.PREFIX + species_id, equation=f"{species_id} ->", sboTerm=SBO.EXCHANGE_REACTION, name=name, compartment=compartment, fast=fast, reversible=reversible, metaId=metaId, annotations=annotations, notes=notes, keyValuePairs=keyValuePairs, lowerFluxBound=lowerFluxBound, upperFluxBound=upperFluxBound, geneProductAssociation=geneProductAssociation, uncertainties=uncertainties, port=port, replacedBy=replacedBy, )
[docs]class GeneProduct(Sbase): """GeneProduct. GeneProduct is a new FBC class derived from SBML SBase that inherits metaid and sboTerm, as well as the subcomponents for Annotation and Notes. The purpose of this class is to define a single gene product. It implements two required attributes id and label as well as two optional attributes name and associatedSpecies. """ def __init__( self, sid: str, label: str, associatedSpecies: Optional[str] = None, name: Optional[str] = None, sboTerm: Optional[str] = None, metaId: Optional[str] = None, annotations: OptionalAnnotationsType = None, notes: Optional[str] = None, keyValuePairs: Optional[List[KeyValuePair]] = None, port: Any = None, uncertainties: Optional[List[Uncertainty]] = None, replacedBy: Optional[Any] = None, ): """Create a GeneProduct.""" super(GeneProduct, self).__init__( sid=sid, name=name, sboTerm=sboTerm, metaId=metaId, annotations=annotations, notes=notes, keyValuePairs=keyValuePairs, port=port, uncertainties=uncertainties, replacedBy=replacedBy, ) self.associatedSpecies = associatedSpecies self.label = label
[docs] def create_sbml(self, model: libsbml.Model) -> libsbml.GeneProduct: """Create GeneProduct.""" model_fbc: libsbml.FbcModelPlugin = model.getPlugin("fbc") gene_product: libsbml.GeneProduct = model_fbc.createGeneProduct() self._set_fields(gene_product, model=model) gene_product.setLabel(self.label) if self.associatedSpecies: gene_product.setAssociatedSpecies(self.associatedSpecies) return gene_product
[docs]class UserDefinedConstraintComponent(Sbase): """UserDefinedConstraintComponent.""" def __init__( self, coefficient: float, variable: str, variableType: Optional[str] = None, sid: Optional[str] = None, name: Optional[str] = None, sboTerm: Optional[str] = None, metaId: Optional[str] = None, annotations: OptionalAnnotationsType = None, notes: Optional[str] = None, keyValuePairs: Optional[List[KeyValuePair]] = None, port: Any = None, uncertainties: Optional[List[Uncertainty]] = None, replacedBy: Optional[Any] = None, ): """Create a UserDefinedConstraintComponent.""" super(UserDefinedConstraintComponent, self).__init__( sid=sid, name=name, sboTerm=sboTerm, metaId=metaId, annotations=annotations, notes=notes, keyValuePairs=keyValuePairs, port=port, uncertainties=uncertainties, replacedBy=replacedBy, ) self.variable = variable self.coefficient = coefficient self.variableType = ( FluxObjective.normalize_variable_type(variableType) if variableType else None )
[docs] def create_sbml( self, constraint: libsbml.UserDefinedConstraint ) -> libsbml.UserDefinedConstraintComponent: """Create Objective.""" component: libsbml.UserDefinedConstraintComponent = ( constraint.createUserDefinedConstraintComponent() ) self._set_fields(component, model=constraint.getModel()) check(component.setVariable(self.variable), f"set variable `{self.variable}`") check( component.setCoefficient(self.coefficient), f"set coefficient `{self.coefficient}`", ) check( component.setVariableType(self.variableType), f"set variableType `{self.variableType}`", ) return component
[docs]class UserDefinedConstraint(Sbase): """UserDefinedConstraint. The FBC UserDefinedConstraint class is derived from SBML SBase and inherits metaid and sboTerm, as well as the subcomponents for Annotation and Notes. It’s purpose is to define non-stoichiometric constraints, that is constraints that are not necessarily defined by the stoichiometrically coupled reaction network. In order to achieve, we defined a new type of linear constraint, the UserDefinedConstraint """ def __init__( self, lowerBound: str, upperBound: str, components: Optional[ Union[List[UserDefinedConstraintComponent], Dict[str, float]] ] = None, variableType: str = libsbml.FBC_VARIABLE_TYPE_LINEAR, sid: Optional[str] = None, name: Optional[str] = None, sboTerm: Optional[str] = None, metaId: Optional[str] = None, annotations: OptionalAnnotationsType = None, notes: Optional[str] = None, keyValuePairs: Optional[List[KeyValuePair]] = None, port: Any = None, uncertainties: Optional[List[Uncertainty]] = None, replacedBy: Optional[Any] = None, ): """Create an UserDefinedConstraint.""" super(UserDefinedConstraint, self).__init__( sid=sid, name=name, sboTerm=sboTerm, metaId=metaId, annotations=annotations, notes=notes, keyValuePairs=keyValuePairs, port=port, uncertainties=uncertainties, replacedBy=replacedBy, ) self.lowerBound = lowerBound self.upperBound = upperBound # normalize components self.components: List[UserDefinedConstraintComponent] = [] if components: if isinstance(components, dict): # create FluxObjectives from dict for variable, coefficient in components.items(): self.components.append( UserDefinedConstraintComponent( variable=variable, coefficient=coefficient, variableType=variableType, ) ) else: for component in components: # infer variableType from objective if not component.variableType: component.variableType = variableType self.components.append(component)
[docs] def create_sbml(self, model: libsbml.Model) -> libsbml.UserDefinedConstraint: """Create UserDefinedConstraint.""" model_fbc: libsbml.FbcModelPlugin = model.getPlugin("fbc") udc: libsbml.UserDefinedConstraint = model_fbc.createUserDefinedConstraint() self._set_fields(udc, model) udc.setUpperBound(self.upperBound) udc.setLowerBound(self.lowerBound) for component in self.components: component.create_sbml(constraint=udc) return udc
[docs]class FluxObjective(Sbase): """FluxObjective."""
[docs] fbc_variable_types: Set[str] = { libsbml.FBC_VARIABLE_TYPE_LINEAR, libsbml.FBC_VARIABLE_TYPE_QUADRATIC, libsbml.FBC_VARIABLE_TYPE_INVALID, "linear", "quadratic", "invalid", }
def __init__( self, reaction: str, coefficient: float, variableType: str, sid: Optional[str] = None, name: Optional[str] = None, sboTerm: Optional[str] = None, metaId: Optional[str] = None, annotations: OptionalAnnotationsType = None, notes: Optional[str] = None, keyValuePairs: Optional[List[KeyValuePair]] = None, port: Any = None, uncertainties: Optional[List[Uncertainty]] = None, replacedBy: Optional[Any] = None, ): """Create a FluxObjective.""" super(FluxObjective, self).__init__( sid=sid, name=name, sboTerm=sboTerm, metaId=metaId, annotations=annotations, notes=notes, keyValuePairs=keyValuePairs, port=port, uncertainties=uncertainties, replacedBy=replacedBy, ) self.reaction = reaction self.coefficient = coefficient self.variableType = FluxObjective.normalize_variable_type(variableType) @classmethod
[docs] def normalize_variable_type(cls, variable_type: str) -> str: """Normalize variable type.""" if variable_type not in cls.fbc_variable_types: raise ValueError( f"Unsupported objective type `{variable_type}`. Supported are " f"`{FluxObjective.fbc_variable_types}`." ) if variable_type == "linear": variable_type = libsbml.FBC_VARIABLE_TYPE_LINEAR elif variable_type == "quadratic": variable_type = libsbml.FBC_VARIABLE_TYPE_QUADRATIC elif variable_type == "invalid": variable_type = libsbml.FBC_VARIABLE_TYPE_INVALID return variable_type
[docs] def create_sbml(self, objective: libsbml.Objective) -> libsbml.FluxObjective: """Create Objective.""" flux_objective: libsbml.FluxObjective = objective.createFluxObjective() self._set_fields(flux_objective, model=objective.getModel()) flux_objective.setReaction(self.reaction) flux_objective.setCoefficient(self.coefficient) flux_objective.setVariableType(self.variableType) return flux_objective
[docs]class Objective(Sbase): """Objective."""
[docs] objective_types: Set[str] = { libsbml.OBJECTIVE_TYPE_MAXIMIZE, libsbml.OBJECTIVE_TYPE_MINIMIZE, "maximize", "minimize", "max", "min", }
def __init__( self, sid: str, objectiveType: str = libsbml.OBJECTIVE_TYPE_MAXIMIZE, active: bool = True, fluxObjectives: Optional[Union[List[FluxObjective], Dict[str, float]]] = None, variableType: str = libsbml.FBC_VARIABLE_TYPE_LINEAR, name: Optional[str] = None, sboTerm: Optional[str] = None, metaId: Optional[str] = None, annotations: OptionalAnnotationsType = None, notes: Optional[str] = None, keyValuePairs: Optional[List[KeyValuePair]] = None, port: Any = None, uncertainties: Optional[List[Uncertainty]] = None, replacedBy: Optional[Any] = None, ): """Create an Objective. FluxObjectives can either be provided as a list of FluxObjectives or as a dictionary with the reaction ids as keys and the coefficients as values. """ super(Objective, self).__init__( sid=sid, name=name, sboTerm=sboTerm, metaId=metaId, annotations=annotations, notes=notes, keyValuePairs=keyValuePairs, port=port, uncertainties=uncertainties, replacedBy=replacedBy, ) self.objectiveType = self.normalize_objective_type(objectiveType) self.active = active # normalize fluxObjectives self.fluxObjectives: List[FluxObjective] = [] if fluxObjectives: if isinstance(fluxObjectives, dict): # create FluxObjectives from dict for rid, coefficient in fluxObjectives.items(): self.fluxObjectives.append( FluxObjective( reaction=rid, coefficient=coefficient, variableType=variableType, ) ) else: for flux_objective in fluxObjectives: # infer variableType from objective if not flux_objective.variableType: flux_objective.variableType = variableType self.fluxObjectives.append(flux_objective) @classmethod
[docs] def normalize_objective_type(cls, objective_type: str) -> str: """Normalize objective type.""" if objective_type not in Objective.objective_types: raise ValueError( f"Unsupported objective type `{objective_type}`. Supported are " f"`{Objective.objective_types}`." ) if objective_type in {"min", "minimize"}: objective_type = libsbml.OBJECTIVE_TYPE_MINIMIZE elif objective_type in {"max", "maximize"}: objective_type = libsbml.OBJECTIVE_TYPE_MAXIMIZE return objective_type
[docs] def create_sbml(self, model: libsbml.Model) -> libsbml.Objective: """Create Objective.""" model_fbc: libsbml.FbcModelPlugin = model.getPlugin("fbc") objective: libsbml.Objective = model_fbc.createObjective() self._set_fields(objective, model) objective.setType(self.objectiveType) if self.active: model_fbc.setActiveObjectiveId(self.sid) for flux_objective in self.fluxObjectives: flux_objective.create_sbml(objective=objective) return objective
[docs]class ModelDefinition(Sbase): """ModelDefinition.""" # FIXME: handle as model def __init__( self, sid: str, name: Optional[str] = None, sboTerm: Optional[str] = None, metaId: Optional[str] = None, annotations: OptionalAnnotationsType = None, notes: Optional[str] = None, keyValuePairs: Optional[List[KeyValuePair]] = None, units: Optional[Type[Units]] = None, compartments: Optional[List[Compartment]] = None, species: Optional[List[Species]] = None, ): """Create a ModelDefinition.""" super(ModelDefinition, self).__init__( sid=sid, name=name, sboTerm=sboTerm, metaId=metaId, annotations=annotations, notes=notes, keyValuePairs=keyValuePairs, ) self.units = units self.compartments = compartments self.species = species
[docs] def create_sbml(self, model: libsbml.Model) -> libsbml.ModelDefinition: """Create ModelDefinition.""" doc: libsbml.SBMLDocument = model.getSBMLDocument() doc_comp: libsbml.CompSBMLDocumentPlugin = doc.getPlugin("comp") model_definition: libsbml.ModelDefinition = doc_comp.createModelDefinition() self._set_fields(model_definition, model) return model_definition
[docs] def _set_fields(self, sbase: libsbml.ModelDefinition, model: libsbml.Model) -> None: """Set fields on ModelDefinition.""" super(ModelDefinition, self)._set_fields(sbase, model) for attr in [ "externalModelDefinitions", "modelDefinitions", "submodels", # "units", "functions", "parameters", "compartments", "species", "assignments", "rules", "rate_rules", "reactions", "events", "constraints", "ports", "replacedElements", "deletions", "objectives", "layouts", ]: # create units # FIXME: # if hasattr(self, "units"): # self.units.create_unit_definitions(obj) # create the respective objects if hasattr(self, attr): objects = getattr(self, attr) if objects: create_objects(sbase, obj_iter=objects, key=attr)
[docs]class ExternalModelDefinition(Sbase): """ExternalModelDefinition.""" def __init__( self, sid: str, source: str, modelRef: str, md5: Optional[str] = None, name: Optional[str] = None, sboTerm: Optional[str] = None, metaId: Optional[str] = None, annotations: OptionalAnnotationsType = None, notes: Optional[str] = None, keyValuePairs: Optional[List[KeyValuePair]] = None, ): """Create an ExternalModelDefinition.""" super(ExternalModelDefinition, self).__init__( sid=sid, name=name, sboTerm=sboTerm, metaId=metaId, annotations=annotations, notes=notes, keyValuePairs=keyValuePairs, ) self.source = source self.modelRef = modelRef self.md5 = md5
[docs] def create_sbml(self, model: libsbml.Model) -> libsbml.ExternalModelDefinition: """Create ExternalModelDefinition.""" doc = model.getSBMLDocument() cdoc = doc.getPlugin("comp") extdef = cdoc.createExternalModelDefinition() self._set_fields(extdef, model) return extdef
[docs] def _set_fields( self, sbase: libsbml.ExternalModelDefinition, model: libsbml.Model ) -> None: """Set fields on ExternalModelDefinition.""" super(ExternalModelDefinition, self)._set_fields(sbase, model) sbase.setModelRef(self.modelRef) sbase.setSource(self.source) if self.md5 is not None: sbase.setMd5(self.md5)
[docs]class Submodel(Sbase): """Submodel.""" def __init__( self, sid: str, modelRef: Optional[str] = None, timeConversionFactor: Optional[str] = None, extentConversionFactor: Optional[str] = None, name: Optional[str] = None, sboTerm: Optional[str] = None, metaId: Optional[str] = None, annotations: OptionalAnnotationsType = None, notes: Optional[str] = None, keyValuePairs: Optional[List[KeyValuePair]] = None, ): """Create a Submodel.""" super(Submodel, self).__init__( sid=sid, name=name, sboTerm=sboTerm, metaId=metaId, annotations=annotations, notes=notes, keyValuePairs=keyValuePairs, ) self.modelRef = modelRef self.timeConversionFactor = timeConversionFactor self.extentConversionFactor = extentConversionFactor
[docs] def create_sbml(self, model: libsbml.Model) -> libsbml.Submodel: """Create SBML Submodel.""" cmodel = model.getPlugin("comp") submodel = cmodel.createSubmodel() self._set_fields(submodel, model) submodel.setModelRef(self.modelRef) if self.timeConversionFactor: submodel.setTimeConversionFactor(self.timeConversionFactor) if self.extentConversionFactor: submodel.setExtentConversionFactor(self.extentConversionFactor) return submodel
[docs] def _set_fields(self, sbase: libsbml.Submodel, model: libsbml.Model) -> None: super(Submodel, self)._set_fields(sbase, model)
class SbaseRef(Sbase): """SBaseRef.""" def __init__( self, sid: str, portRef: Optional[str] = None, idRef: Optional[str] = None, unitRef: Optional[str] = None, metaIdRef: Optional[str] = None, name: Optional[str] = None, sboTerm: Optional[str] = None, metaId: Optional[str] = None, annotations: OptionalAnnotationsType = None, notes: Optional[str] = None, keyValuePairs: Optional[List[KeyValuePair]] = None, ): """Create an SBaseRef.""" super(SbaseRef, self).__init__( sid=sid, name=name, sboTerm=sboTerm, metaId=metaId, annotations=annotations, notes=notes, keyValuePairs=keyValuePairs, ) self.portRef = portRef self.idRef = idRef self.unitRef = unitRef self.metaIdRef = metaIdRef def _set_fields(self, sbase: libsbml.SBaseRef, model: libsbml.Model) -> None: super(SbaseRef, self)._set_fields(sbase, model) sbase.setId(self.sid) if self.portRef is not None: sbase.setPortRef(self.portRef) if self.idRef is not None: sbase.setIdRef(self.idRef) if self.unitRef is not None: unit_str = UnitDefinition.get_uid_for_unit(unit=self.unitRef) sbase.setUnitRef(unit_str) if self.metaIdRef is not None: sbase.setMetaIdRef(self.metaIdRef)
[docs]class ReplacedElement(SbaseRef): """ReplacedElement.""" def __init__( self, sid: str, elementRef: str, submodelRef: str, deletion: Optional[str] = None, conversionFactor: Optional[str] = None, portRef: Optional[str] = None, idRef: Optional[str] = None, unitRef: Optional[str] = None, metaIdRef: Optional[str] = None, name: Optional[str] = None, sboTerm: Optional[str] = None, metaId: Optional[str] = None, annotations: OptionalAnnotationsType = None, notes: Optional[str] = None, keyValuePairs: Optional[List[KeyValuePair]] = None, ): """Create a ReplacedElement.""" super(ReplacedElement, self).__init__( sid=sid, portRef=portRef, idRef=idRef, unitRef=unitRef, metaIdRef=metaIdRef, name=name, sboTerm=sboTerm, metaId=metaId, annotations=annotations, notes=notes, keyValuePairs=keyValuePairs, ) self.elementRef = elementRef self.submodelRef = submodelRef self.deletion = deletion self.conversionFactor = conversionFactor
[docs] def create_sbml(self, model: libsbml.Model) -> libsbml.ReplacedElement: """Create SBML ReplacedElement.""" # resolve port element e = model.getElementBySId(self.elementRef) if not e: # fallback to units (only working if no name shadowing) e = model.getUnitDefinition(self.elementRef) if not e: raise ValueError( f"Neither SBML element nor UnitDefinition found for elementRef: " f"'{self.elementRef}' in '{self}'" ) eplugin = e.getPlugin("comp") obj = eplugin.createReplacedElement() self._set_fields(obj, model) return obj
[docs] def _set_fields(self, sbase: libsbml.ReplacedElement, model: libsbml.Model) -> None: super(ReplacedElement, self)._set_fields(sbase, model) sbase.setSubmodelRef(self.submodelRef) if self.deletion: sbase.setDeletion(self.deletion) if self.conversionFactor: sbase.setConversionFactor(self.conversionFactor)
[docs]class ReplacedBy(SbaseRef): """ReplacedBy.""" def __init__( self, sid: str, elementRef: str, submodelRef: str, portRef: Optional[str] = None, idRef: Optional[str] = None, unitRef: Optional[str] = None, metaIdRef: Optional[str] = None, name: Optional[str] = None, sboTerm: Optional[str] = None, metaId: Optional[str] = None, annotations: OptionalAnnotationsType = None, notes: Optional[str] = None, keyValuePairs: Optional[List[KeyValuePair]] = None, ): """Create a ReplacedElement.""" super(ReplacedBy, self).__init__( sid=sid, portRef=portRef, idRef=idRef, unitRef=unitRef, metaIdRef=metaIdRef, name=name, sboTerm=sboTerm, metaId=metaId, annotations=annotations, notes=notes, keyValuePairs=keyValuePairs, ) self.elementRef = elementRef self.submodelRef = submodelRef
[docs] def create_sbml( self, sbase: libsbml.SBase, model: libsbml.Model ) -> libsbml.ReplacedBy: """Create SBML ReplacedBy.""" sbase_comp: libsbml.CompSBasePlugin = sbase.getPlugin("comp") rby: libsbml.ReplacedBy = sbase_comp.createReplacedBy() self._set_fields(rby, model) return rby
[docs] def _set_fields(self, rby: libsbml.ReplacedBy, model: libsbml.Model) -> None: """Set fields in ReplacedBy.""" super(ReplacedBy, self)._set_fields(rby, model) rby.setSubmodelRef(self.submodelRef)
[docs]class Deletion(SbaseRef): """Deletion.""" def __init__( self, sid: str, submodelRef: str, portRef: Optional[str] = None, idRef: Optional[str] = None, unitRef: Optional[str] = None, metaIdRef: Optional[str] = None, name: Optional[str] = None, sboTerm: Optional[str] = None, metaId: Optional[str] = None, annotations: OptionalAnnotationsType = None, notes: Optional[str] = None, keyValuePairs: Optional[List[KeyValuePair]] = None, ): """Initialize Deletion.""" super(Deletion, self).__init__( sid=sid, portRef=portRef, idRef=idRef, unitRef=unitRef, metaIdRef=metaIdRef, name=name, sboTerm=sboTerm, metaId=metaId, annotations=annotations, notes=notes, keyValuePairs=keyValuePairs, ) self.submodelRef = submodelRef
[docs] def create_sbml(self, model: libsbml.Model) -> libsbml.Deletion: """Create SBML Deletion.""" cmodel: libsbml.CompModelPlugin = model.getPlugin("comp") submodel: libsbml.Submodel = cmodel.getSubmodel(self.submodelRef) deletion: libsbml.Deletion = submodel.createDeletion() self._set_fields(deletion, model) return deletion
[docs] def _set_fields(self, sbase: libsbml.Deletion, model: libsbml.Model) -> None: """Set fields on Deletion.""" super(Deletion, self)._set_fields(sbase, model)
[docs]class PortType(str, Enum): """Supported port types."""
[docs] PORT = "port"
[docs] INPUT_PORT = "input port"
[docs] OUTPUT_PORT = "output port"
[docs]class Port(SbaseRef): """Port. Ports are stored in an optional child ListOfPorts object, which, if present, must contain one or more Port objects. All of the Ports present in the ListOfPorts collectively define the 'port interface' of the Model. """ def __init__( self, sid: str, portRef: Optional[str] = None, idRef: Optional[str] = None, unitRef: Optional[str] = None, metaIdRef: Optional[str] = None, portType: Optional[PortType] = PortType.PORT, name: Optional[str] = None, sboTerm: Optional[str] = None, metaId: Optional[str] = None, annotations: OptionalAnnotationsType = None, notes: Optional[str] = None, keyValuePairs: Optional[List[KeyValuePair]] = None, ): """Create a Port.""" super(Port, self).__init__( sid=sid, portRef=portRef, idRef=idRef, unitRef=unitRef, metaIdRef=metaIdRef, name=name, sboTerm=sboTerm, metaId=metaId, annotations=annotations, notes=notes, keyValuePairs=keyValuePairs, ) self.portType = portType
[docs] def create_sbml(self, model: libsbml.Model) -> libsbml.Port: """Create SBML for Port.""" cmodel = model.getPlugin("comp") p = cmodel.createPort() self._set_fields(p, model) if self.sboTerm is None: if self.portType == PortType.PORT: sbo = SBO.PORT elif self.portType == PortType.INPUT_PORT: sbo = SBO.INPUT_PORT elif self.portType == PortType.OUTPUT_PORT: sbo = SBO.OUTPUT_PORT p.setSBOTerm(sbo.value.replace("_", ":")) return p
[docs] def _set_fields(self, sbase: libsbml.Port, model: libsbml.Model) -> None: """Set fields on Port.""" super(Port, self)._set_fields(sbase, model)
[docs]class Package(str, Enum): """Supported/tested packages."""
[docs] COMP = "comp"
[docs] COMP_V1 = "comp-v1"
[docs] DISTRIB = "distrib"
[docs] DISTRIB_V1 = "distrib-v1"
[docs] FBC = "fbc"
[docs] FBC_V2 = "fbc-v2"
[docs] FBC_V3 = "fbc-v3"
[docs]class ModelDict(TypedDict, total=False): """ModelDict. The ModelDict allows to define the Model as dictionary and then use: md: ModelDict Model(**md) For model construction. If possible use the Model object directly. """
[docs] sid: str
[docs] name: Optional[str]
[docs] sboTerm: Optional[str]
[docs] metaId: Optional[str]
[docs] annotations: OptionalAnnotationsType
[docs] notes: Optional[str]
[docs] keyValuePairs: Optional[List[KeyValuePair]]
[docs] packages: Optional[List[Package]]
[docs] creators: Optional[List[Creator]]
[docs] model_units: Optional[ModelUnits]
[docs] objects: Optional[List[Sbase]]
[docs] units: Optional[Type[Units]]
[docs] functions: Optional[List[Function]]
[docs] compartments: Optional[List[Compartment]]
[docs] species: Optional[List[Species]]
[docs] parameters: Optional[List[Parameter]]
[docs] assignments: Optional[List[InitialAssignment]]
[docs] rules: Optional[List[AssignmentRule]]
[docs] rate_rules: Optional[List[RateRule]]
[docs] algebraic_rules: Optional[List[AlgebraicRule]]
[docs] reactions: Optional[List[Reaction]]
[docs] events: Optional[List[Event]]
[docs] constraints: Optional[List[Constraint]]
# comp
[docs] external_model_definitions: Optional[List[ExternalModelDefinition]]
[docs] model_definitions: Optional[List[ModelDefinition]]
[docs] submodels: Optional[List[Submodel]]
[docs] ports: Optional[List[Port]]
[docs] replaced_elements: Optional[List[ReplacedElement]]
[docs] deletions: Optional[List[Deletion]]
# fbc
[docs] user_defined_constraints: Optional[List[UserDefinedConstraint]]
[docs] objectives: Optional[List[Objective]]
[docs] gene_products: Optional[List[GeneProduct]]
# layout
[docs] layouts: Optional[List]
[docs]class Model(Sbase, FrozenClass, BaseModel): """Model."""
[docs] sid: str
[docs] name: Optional[str]
[docs] sboTerm: Optional[str]
[docs] metaId: Optional[str]
[docs] annotations: AnnotationsType
[docs] notes: Optional[str]
[docs] keyValuePairs: Optional[List[KeyValuePair]]
[docs] port: Optional[Any]
[docs] packages: List[Package]
[docs] creators: List[Creator]
[docs] model_units: Optional[ModelUnits]
[docs] units: Optional[Type[Units]]
[docs] functions: List[Function]
[docs] compartments: List[Compartment]
[docs] species: List[Species]
[docs] parameters: List[Parameter]
[docs] assignments: List[InitialAssignment]
[docs] rules: List[AssignmentRule]
[docs] rate_rules: List[RateRule]
[docs] algebraic_rules: List[AlgebraicRule]
[docs] reactions: List[Reaction]
[docs] events: List[Event]
[docs] constraints: List[Constraint]
# comp
[docs] external_model_definitions: List[ExternalModelDefinition]
[docs] model_definitions: List[ModelDefinition]
[docs] submodels: List[Submodel]
[docs] ports: List[Port]
[docs] replaced_elements: List[ReplacedElement]
[docs] deletions: List[Deletion]
# fbc
[docs] user_defined_constraints: List[UserDefinedConstraint]
[docs] objectives: List[Objective]
[docs] gene_products: List[GeneProduct]
# layout
[docs] layouts: Optional[List]
[docs] class Config: """Pydantic configuration."""
[docs] arbitrary_types_allowed = True
[docs] _keys = { "sid": None, "name": None, "sboTerm": None, "metaId": None, "annotations": list, "notes": None, "keyValuePairs": list, "port": None, "packages": list, "creators": None, "model_units": None, "units": None, "functions": list, "compartments": list, "species": list, "parameters": list, "assignments": list, "rules": list, "rate_rules": list, "algebraic_rules": list, "reactions": list, "events": list, "constraints": list, "external_model_definitions": list, "model_definitions": list, "submodels": list, "ports": list, "replaced_elements": list, "deletions": list, "user_defined_constraints": list, "objectives": list, "gene_products": list, "layouts": list, }
[docs] _supported_packages = { Package.COMP, Package.COMP_V1, Package.DISTRIB, Package.DISTRIB_V1, Package.FBC, Package.FBC_V2, Package.FBC_V3, }
[docs] def __str__(self) -> str: """Get string.""" field_str = ", ".join(f"{a}={v!r}" for a, v in self.__repr_args__() if a and v) return f"{self.__class__.__name__}({field_str})"
def __init__( self, sid: str, name: Optional[str] = None, sboTerm: Optional[str] = None, metaId: Optional[str] = None, annotations: OptionalAnnotationsType = None, notes: Optional[str] = None, keyValuePairs: Optional[List[KeyValuePair]] = None, packages: Optional[List[Package]] = None, creators: Optional[List[Creator]] = None, model_units: Optional[ModelUnits] = None, units: Optional[Type[Units]] = None, objects: Optional[List[Sbase]] = None, external_model_definitions: Optional[List[ExternalModelDefinition]] = None, model_definitions: Optional[List[ModelDefinition]] = None, submodels: Optional[List[Submodel]] = None, functions: Optional[List[Function]] = None, compartments: Optional[List[Compartment]] = None, species: Optional[List[Species]] = None, parameters: Optional[List[Parameter]] = None, assignments: Optional[List[InitialAssignment]] = None, rules: Optional[List[AssignmentRule]] = None, rate_rules: Optional[List[RateRule]] = None, algebraic_rules: Optional[List[AlgebraicRule]] = None, reactions: Optional[List[Reaction]] = None, events: Optional[List[Event]] = None, constraints: Optional[List[Constraint]] = None, ports: Optional[List[Port]] = None, replaced_elements: Optional[List[ReplacedElement]] = None, deletions: Optional[List[Deletion]] = None, user_defined_constraints: Optional[List[UserDefinedConstraint]] = None, objectives: Optional[List[Objective]] = None, gene_products: Optional[List[GeneProduct]] = None, layouts: Optional[List] = None, ): """Model constructor.""" super(Model, self).__init__( sid=sid, name=name, sboTerm=sboTerm, metaId=metaId, annotations=annotations, notes=notes, keyValuePairs=keyValuePairs, ) self.packages = self.check_packages(packages) self.creators = creators if creators else [] self.model_units = model_units self.units = units if units else Units self.units_dict = None self.external_model_definitions = ( external_model_definitions if external_model_definitions else [] ) self.model_definitions = model_definitions if model_definitions else [] self.submodels: List[Submodel] = submodels if submodels else [] self.functions: List[Function] = functions if functions else [] self.compartments: List[Compartment] = compartments if compartments else [] self.species: List[Species] = species if species else [] self.parameters: List[Parameter] = parameters if parameters else [] self.assignments: List[InitialAssignment] = assignments if assignments else [] self.rules: List[AssignmentRule] = rules if rules else [] self.rate_rules: List[RateRule] = rate_rules if rate_rules else [] self.algebraic_rules: List[AlgebraicRule] = ( algebraic_rules if algebraic_rules else [] ) self.reactions: List[Reaction] = reactions if reactions else [] self.events: List[Event] = events if events else [] self.constraints: List[Constraint] = constraints if constraints else [] self.ports: List[Port] = ports if ports else [] self.replaced_elements: List[ReplacedElement] = ( replaced_elements if replaced_elements else [] ) self.deletions: List[Deletion] = deletions if deletions else [] self.user_defined_constraints: List[UserDefinedConstraint] = ( user_defined_constraints if user_defined_constraints else [] ) self.objectives: List[Objective] = objectives if objectives else [] self.gene_products: List[GeneProduct] = gene_products if gene_products else [] self.layouts: Optional[List] = layouts if objects: for sbase in objects: if isinstance(sbase, Submodel): self.submodels.append(sbase) elif isinstance(sbase, Function): self.functions.append(sbase) elif isinstance(sbase, Compartment): self.compartments.append(sbase) elif isinstance(sbase, Species): self.species.append(sbase) elif isinstance(sbase, Parameter): self.parameters.append(sbase) elif isinstance(sbase, InitialAssignment): self.assignments.append(sbase) elif isinstance(sbase, AssignmentRule): self.rules.append(sbase) elif isinstance(sbase, RateRule): self.rate_rules.append(sbase) elif isinstance(sbase, AlgebraicRule): self.algebraic_rules.append(sbase) elif isinstance(sbase, Reaction): self.reactions.append(sbase) elif isinstance(sbase, Event): self.events.append(sbase) elif isinstance(sbase, Constraint): self.constraints.append(sbase) elif isinstance(sbase, Port): self.ports.append(sbase) elif isinstance(sbase, ReplacedElement): self.replaced_elements.append(sbase) elif isinstance(sbase, Deletion): self.deletions.append(sbase) elif isinstance(sbase, UserDefinedConstraint): self.user_defined_constraints.append(sbase) elif isinstance(sbase, Objective): self.objectives.append(sbase) elif isinstance(sbase, GeneProduct): self.gene_products.append(sbase) self._freeze() # no new attributes after this point
[docs] def create_sbml(self, doc: libsbml.SBMLDocument) -> libsbml.Model: """Create Model. To create the complete SBMLDocument with the model use: doc = Document(model=model).create_sbml() """ model: libsbml.Model = doc.createModel() self._set_fields(model, model) # history if self.creators: set_model_history(model, self.creators) # units if self.units: self.units.create_unit_definitions(model=model) # model units if self.model_units: ModelUnits.set_model_units(model, self.model_units) # lists ofs for attr in [ "external_model_definitions", "model_definitions", "submodels", # "units", "functions", "parameters", "compartments", "species", "gene_products", "reactions", "assignments", "rules", "rate_rules", "algebraic_rules", "events", "constraints", "ports", "replaced_elements", "deletions", "user_defined_constraints", "objectives", "layouts", ]: # create the respective objects if hasattr(self, attr): objects = getattr(self, attr) if objects: create_objects(model, obj_iter=objects, key=attr) return model
[docs] def get_sbml(self) -> str: """Create SBML model.""" return Document(model=self).get_sbml()
[docs] def check_packages(self, packages: Optional[List[Package]]) -> List[Package]: """Check that all provided packages are supported.""" if packages is None: packages = [] packages_set: Set[Package] = set(packages) for p in packages_set: if not isinstance(p, Package): msg = ( f"Packages must be provided as `Package`, but package " f"`{p}` is `{type(p)}`." ) logger.error(msg) raise ValueError(msg) # normalize package versions if Package.COMP in packages_set: packages_set.remove(Package.COMP) packages_set.add(Package.COMP_V1) if Package.FBC in packages_set: packages_set.remove(Package.FBC) packages_set.add(Package.FBC_V3) if Package.DISTRIB in packages_set: packages_set.remove(Package.DISTRIB) packages_set.add(Package.DISTRIB_V1) if len(packages_set) < len(packages): raise ValueError(f"Duplicate packages in `{packages}`.") for p in packages_set: if not isinstance(p, str): raise ValueError( f"Packages must be provided as `Package`, but type `{type(p)}` " f"for package `{p}`." ) if p not in self._supported_packages: raise ValueError( f"Supported packages are: '{self._supported_packages}', " f"but package '{p}' found." ) # add comp as default package packages_set.add(Package.COMP_V1) return list(packages_set)
@staticmethod
[docs] def merge_models(models: Iterable[Model]) -> Model: """Merge information from multiple models.""" if isinstance(models, Model): return models if not models: raise ValueError("No models are provided.") model = Model("template") units_base_classes: List[Type[Units]] = ( [model.units] if model.units else [Units] ) creators = set() for m2 in models: for key, value in m2.__dict__.items(): kind = m2._keys.get(key, None) # lists of higher modules are extended if kind in [list, tuple]: # create new list if not hasattr(model, key) or getattr(model, key) is None: setattr(model, key, []) # now add elements by copy if getattr(model, key): if value: getattr(model, key).extend(deepcopy(value)) else: if value: setattr(model, key, deepcopy(value)) # units are collected and class created dynamically at the end elif key == "units": if m2.units: units_base_classes.append(m2.units) elif key == "creators": if m2.creators: for c in m2.creators: creators.add(c) # !everything else is overwritten else: setattr(model, key, value) # Handle merging of units attr_dict = {} for base_class in units_base_classes: for a in base_class.attributes(): attr_dict[a[0]] = a[1] if units_base_classes: model.units = type("U", (Units,), attr_dict) model.creators = list(creators) return model
[docs]class Document(Sbase): """Document.""" def __init__( self, model: Model, sid: Optional[str] = None, name: Optional[str] = None, sboTerm: Optional[str] = None, metaId: Optional[str] = None, annotations: OptionalAnnotationsType = None, notes: Optional[str] = None, keyValuePairs: Optional[List[KeyValuePair]] = None, sbml_level: int = SBML_LEVEL, sbml_version: int = SBML_VERSION, ): """Document constructor.""" self.model = model self.sid = sid self.name = name self.sboTerm = sboTerm self.metaId = metaId self.annotations: AnnotationsType = annotations if annotations else [] self.notes = notes self.keyValuePairs = keyValuePairs self.sbml_level = sbml_level self.sbml_version = sbml_version self.doc: libsbml.SBMLDocument = None sbmlutils_notes = """ Created with [https://github.com/matthiaskoenig/sbmlutils](https://github.com/matthiaskoenig/sbmlutils). [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.5525390.svg)](https://doi.org/10.5281/zenodo.5525390) """ if self.notes is None: self.notes = sbmlutils_notes else: self.notes += sbmlutils_notes
[docs] def create_sbml(self) -> libsbml.SBMLDocument: """Create SBML model.""" logger.info(f"Create SBML for model '{self.model.sid}'") # create core model sbmlns = libsbml.SBMLNamespaces(self.sbml_level, self.sbml_version) # add all the package for package in self.model.packages: if package == Package.COMP_V1: sbmlns.addPackageNamespace("comp", 1) if package == Package.DISTRIB_V1: sbmlns.addPackageNamespace("distrib", 1) if package == Package.FBC_V2: sbmlns.addPackageNamespace("fbc", 2) if package == Package.FBC_V3: sbmlns.addPackageNamespace("fbc", 3) self.doc = libsbml.SBMLDocument(sbmlns) self._set_fields(self.doc, None) # create model sbml_model: libsbml.Model = self.model.create_sbml(self.doc) if Package.COMP_V1 in self.model.packages: self.doc.setPackageRequired("comp", True) if (Package.FBC_V2 in self.model.packages) or ( Package.FBC_V3 in self.model.packages ): self.doc.setPackageRequired("fbc", False) fbc_plugin = sbml_model.getPlugin("fbc") fbc_plugin.setStrict(False) if Package.DISTRIB_V1 in self.model.packages: self.doc.setPackageRequired("distrib", True) return self.doc
[docs] def get_sbml(self) -> str: """Return SBML string of the model. :return: SBML string """ if self.doc is None: self.create_sbml() return str(libsbml.writeSBMLToString(self.doc))
[docs] def get_json(self) -> str: """Get JSON representation.""" o = xmltodict.parse(self.get_sbml()) return json.dumps(o, indent=2)
@dataclass
[docs]class FactoryResult: """Data structure for model creation."""
[docs] model: Model
[docs] sbml_path: Path
[docs]def create_model( model: Union[Model, Iterable[Model]], filepath: Path, sbml_level: int = SBML_LEVEL, sbml_version: int = SBML_VERSION, validate: bool = True, validation_options: Optional[ValidationOptions] = None, show_sbml: bool = False, annotations: Optional[Path] = None, ) -> FactoryResult: """Create SBML model from models. This is the entry point for creating models. If multiple models are provided these are merged in the process of model creation. See `merge_models` for more details. Additional model annotations can be provided via a file. :param model: Model or iterable of Model instances which are merged in single model :param filepath: Path to write the SBML model to :param sbml_level: set SBML level for model generation :param sbml_version: set SBML version for model generation :param validate: boolean flag to validate the SBML file :param validation_options: options for model validation :param show_sbml: boolean flag to show SBML :param annotations: Path to annotations file :return: FactoryResult """ console.rule(title="Create SBML", style="white") if validation_options is None: validation_options = ValidationOptions() # merge models m: Model if isinstance(model, Iterable): m = Model.merge_models(model) elif isinstance(model, Model): m = model else: raise ValueError(f"Unsupported `model` type: {type(model)}") # create and write SBML doc: libsbml.SBMLDocument = Document( model=m, sbml_level=sbml_level, sbml_version=sbml_version, ).create_sbml() write_sbml( doc=doc, filepath=filepath, validate=validate, validation_options=validation_options, ) # annotation of model (overwrites file) if annotations is not None: annotator.annotate_sbml( source=filepath, annotations_path=annotations, filepath=filepath ) console.rule(style="white") # print created sbml if show_sbml: with open(filepath, "r") as f_sbml: sbml_str = f_sbml.read() console.log(sbml_str) console.rule(style="white") return FactoryResult(sbml_path=filepath, model=m)