From 4912c845d8df636d87ee239f707955fb2b65a46e Mon Sep 17 00:00:00 2001 From: "Moritz E. Beber" Date: Wed, 22 Sep 2021 00:16:32 +0200 Subject: [PATCH 1/2] feat: add parser and builder for SBML models --- src/maud/sbml_compat.py | 165 +++++ tests/data/BIOMD0000000639_urn.xml | 902 ++++++++++++++++++++++++++++ tests/test_integration/__init__.py | 0 tests/test_integration/test_sbml.py | 14 + 4 files changed, 1081 insertions(+) create mode 100644 src/maud/sbml_compat.py create mode 100644 tests/data/BIOMD0000000639_urn.xml delete mode 100644 tests/test_integration/__init__.py create mode 100644 tests/test_integration/test_sbml.py diff --git a/src/maud/sbml_compat.py b/src/maud/sbml_compat.py new file mode 100644 index 000000000..74fae4bb7 --- /dev/null +++ b/src/maud/sbml_compat.py @@ -0,0 +1,165 @@ +# Copyright (C) 2021 Moritz E. Beber. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + + +"""Provide compatibility with SBML.""" + + +from __future__ import annotations + +from pathlib import Path +from typing import List, Dict + +import libsbml + +from .data_model import ( + Compartment, + Metabolite, + MetaboliteInCompartment, + Enzyme, + Reaction, + KineticModel, +) + + +class MaudModelBuilder: + """Define a builder for the kinetic model aggregate.""" + + def __init__(self, **kwargs): + """""" + super().__init__(**kwargs) + self._compartments: List[Compartment] = [] + self._metabolites: List[Metabolite] = [] + self._reactions: List[Reaction] = [] + self._mics: List[MetaboliteInCompartment] = [] + + def build_model(self, model_id: str) -> KineticModel: + """""" + return KineticModel( + model_id=model_id, + metabolites=self._metabolites, + reactions=self._reactions, + compartments=self._compartments, + mics=self._mics, + ) + + def build_compartment(self, id: str, name: str, volume: float = 1.0) -> None: + """""" + self._compartments.append(Compartment(id=id, name=name, volume=volume)) + + def build_metabolite( + self, id: str, name: str, compartment_id: str, balanced: bool + ) -> None: + """""" + self._metabolites.append(Metabolite(id=id, name=name)) + self._mics.append( + MetaboliteInCompartment( + id=f"{id}_{compartment_id}", + metabolite_id=id, + compartment_id=compartment_id, + balanced=balanced, + ) + ) + + def build_reaction( + self, + id: str, + name: str, + mechanism: str, + stoichiometry: Dict[str, float], + enzymes: List[Enzyme], + ) -> None: + """""" + self._reactions.append( + Reaction( + id=id, + name=name, + reaction_mechanism=mechanism, + stoichiometry=stoichiometry, + enzymes=enzymes, + ) + ) + + +class SBMLModelParser: + """Define an SBML model parser which hands off information to a builder instance.""" + + def __init__(self, *, document: libsbml.SBMLDocument, **kwargs) -> None: + """""" + super().__init__(**kwargs) + self._doc = document + self._builder = None + + @classmethod + def from_file(cls, path: Path) -> SBMLModelParser: + """""" + return cls(document=libsbml.readSBMLFromFile(str(path))) + + def parse(self, builder: MaudModelBuilder) -> KineticModel: + """""" + self._builder = builder + model: libsbml.Model = self._doc.getModel() + self._parse_compartments(model) + self._parse_metabolites(model) + self._parse_reactions(model) + return self._builder.build_model(model.getIdAttribute()) + + def _parse_compartments(self, model: libsbml.Model) -> None: + """""" + for compartment in model.getListOfCompartments(): + self._builder.build_compartment( + compartment.getIdAttribute(), + compartment.getName(), + compartment.getVolume(), + ) + + def _parse_metabolites(self, model: libsbml.Model) -> None: + """""" + for species in model.getListOfSpecies(): + self._builder.build_metabolite( + species.getIdAttribute(), + species.getName(), + species.getCompartment(), + not species.getBoundaryCondition(), + ) + + def _parse_reactions(self, model: libsbml.Model) -> None: + """""" + for reaction in model.getListOfReactions(): + stoichiometry = {} + for reactant in reaction.getListOfReactants(): + stoichiometry[reactant.getSpecies()] = -float( + reactant.getStoichiometry() + ) + for reactant in reaction.getListOfProducts(): + stoichiometry[reactant.getSpecies()] = float( + reactant.getStoichiometry() + ) + # FIXME: The reaction mechanism could be parsed from an SBO term, e.g., + # https://www.ebi.ac.uk/sbo/main/SBO:0000239 (might have to be added to the + # SBO) or a custom annotation. Please ask Matthias König or Andreas Dräger + # for advice. Or post on the SBML mailing list. + enzymes = [] + # FIXME: I must admit, I'm not sure what the best SBML type is to hold the + # Maud-specific enzyme information. Maybe you can get some inspiration from + # looking at a few kinetic models defined in SBML or the tellurium format + # https://tellurium.readthedocs.io/. + self._builder.build_reaction( + reaction.getIdAttribute(), + reaction.getName(), + "reversible_modular_rate_law", + stoichiometry, + enzymes, + ) diff --git a/tests/data/BIOMD0000000639_urn.xml b/tests/data/BIOMD0000000639_urn.xml new file mode 100644 index 000000000..f7f0f2896 --- /dev/null +++ b/tests/data/BIOMD0000000639_urn.xml @@ -0,0 +1,902 @@ + + + + + +
Thiaville2016 - Wild type folate pathway +model with proposed PanB reaction
+
This is a wild type E. coli model, and +is one amongst the three models described in the paper. The other +two models are MODEL1602280002 (wild type with PanB over +expression) and MODEL1602280003 (wild type with PanB over +expression and THF regulation). 
+
+

This model is described in the article:

+ +
Thiaville JJ, Frelin O, + García-Salinas C, Harrison K, Hasnain G, Horenstein NA, + Díaz de la Garza RI, Henry CS, Hanson AD, de + Crécy-Lagard V.
+
Front Microbiol 2016; 7: 431
+

Abstract:

+
+

Tetrahydrofolate (THF) and its one-carbon derivatives, + collectively termed folates, are essential cofactors, but are + inherently unstable. While it is clear that chemical oxidation + can cleave folates or damage their pterin precursors, very + little is known about enzymatic damage to these molecules or + about whether the folate biosynthesis pathway responds + adaptively to damage to its end-products. The presence of a + duplication of the gene encoding the folate biosynthesis enzyme + 6-hydroxymethyl-7,8-dihydropterin pyrophosphokinase (FolK) in + many sequenced bacterial genomes combined with a strong + chromosomal clustering of the folK gene with panB, encoding the + 5,10-methylene-THF-dependent enzyme ketopantoate + hydroxymethyltransferase, led us to infer that PanB has a side + activity that cleaves 5,10-methylene-THF, yielding a pterin + product that is recycled by FolK. Genetic and metabolic + analyses of Escherichia coli strains showed that overexpression + of PanB leads to accumulation of the likely folate cleavage + product 6-hydroxymethylpterin and other pterins in cells and + medium, and-unexpectedly-to a 46% increase in total folate + content. In silico modeling of the folate biosynthesis pathway + showed that these observations are consistent with the in vivo + cleavage of 5,10-methylene-THF by a side-activity of PanB, with + FolK-mediated recycling of the pterin cleavage product, and + with regulation of folate biosynthesis by folates or their + damage products.

+
+
+
+

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

+

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

+
+
+

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

+
+ +
+ + + + + + + 2015-03-03T00:36:59Z + + + + + chenry@mcs.anl.gov + + + Henry + Christopher + + + + + Argonne National Laboratory + + + + + + + + + + + + + + Henry + Christopher + + chenry@mcs.anl.gov + + University of Chicago + + + + + Kothamachu + Varun + + kothamav@babraham.ac.uk + + Babraham Institute + + + + + + 2015-03-03T00:36:59Z + + + 2017-06-01T16:43:07Z + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 2015-10-29T14:58:10Z + + + + + + + + + + substrate + + + Km + + + V + + + + + + V + substrate + + + + Km + substrate + + + + + + + + + + + + + 2015-10-29T14:54:57Z + + + + + + + + + + v + + v + + + + + + + + + + + + + + + + + + + + + + + + + 2015-10-28T21:10:22Z + + + + + + + + + + + + + + + + + 2015-03-04T01:46:28Z + + + + + + + + + + + + + + + 2015-03-04T01:47:11Z + + + + + + + + + + + + + + + 2015-03-04T01:47:46Z + + + + + + + + + + + + + + + 2015-03-04T01:48:14Z + + + + + + + + + + + + + + + 2015-03-04T01:49:53Z + + + + + + + + + + + + + + + 2015-03-04T01:50:13Z + + + + + + + + + + + + + + + 2015-03-04T01:50:43Z + + + + + + + + + + + + + + + 2015-03-04T11:32:48Z + + + + + + + + + + + + + + + 2015-03-04T14:39:59Z + + + + + + + + + + + + + + + 2015-03-04T14:40:24Z + + + + + + + + + + + + + + + 2015-03-04T14:40:47Z + + + + + + + + + + + + + + + 2015-03-06T11:38:21Z + + + + + + + + + + + + + + + 2015-03-04T16:41:05Z + + + + + + + + + + + + + + + 2015-03-06T11:38:48Z + + + + + + + + + + + + + + + 2015-03-06T11:39:02Z + + + + + + + + + + + + + + + 2015-03-06T11:26:16Z + + + + + + + + + + + + + + + 2015-03-06T11:24:33Z + + + + + + + + + + + + + + + + + 2015-03-04T02:00:39Z + + + + + + + + + + + + + + + + + + + compartment + k1 + ATP + H2_HMPt + + + + + + + + + + + + + + + 2015-03-04T02:04:35Z + + + + + + + + + + + + + + + + + + + compartment + k1 + p_ABA + H2_HMPterinPP + + + + + + + + + + + + + + + 2015-03-04T02:08:18Z + + + + + + + + + + + + + + + + + + + + + compartment + k1 + L_Glutamate + ATP + H2_Pteroate + + + + + + + + + + + + + + + 2015-03-04T16:38:30Z + + + + + + + + + + + + + + + + + + + compartment + + + + + k1 + THF + L_serine + + + + k2 + CH2_THF + Glycine + + + + + + + + + + + + + + + + + + 2015-03-05T00:08:35Z + + + + + + + + + + + + + + + + + + + compartment + k1 + DHF + NADPH + + + + + + + + + + + + + + + 2015-03-05T01:23:23Z + + + + + + + + + + + + + + + + + + compartment + k1 + CH2_THF + + + + + + + + + + + + + + + 2015-03-06T11:42:21Z + + + + + + + + + + + + + + compartment + + Constant_flux__irreversible + v + + + + + + + + + + + + + + + + 2015-03-06T11:43:17Z + + + + + + + + + + + + + + compartment + + Constant_flux__irreversible + v + + + + + + + + + + + + + + + + 2015-03-06T11:54:33Z + + + + + + + + + + + + + + compartment + + Henri_Michaelis_Menten__irreversible + THF + Km + V + + + + + + + + + + + + + + + + + 2015-03-06T11:55:20Z + + + + + + + + + + + + + + compartment + + Henri_Michaelis_Menten__irreversible + CH2_THF + Km + V + + + + + + + + + + +
+
\ No newline at end of file diff --git a/tests/test_integration/__init__.py b/tests/test_integration/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/tests/test_integration/test_sbml.py b/tests/test_integration/test_sbml.py new file mode 100644 index 000000000..28935d926 --- /dev/null +++ b/tests/test_integration/test_sbml.py @@ -0,0 +1,14 @@ +from pathlib import Path + +from maud.sbml_compat import SBMLModelParser, MaudModelBuilder + + +data_dir = Path(__file__).parent.parent / "data" + + +def test_sbml_parser(): + parser = SBMLModelParser.from_file(data_dir / "BIOMD0000000639_urn.xml") + model = parser.parse(MaudModelBuilder()) + assert model.model_id == "MODEL1602280001" + assert len(model.metabolites) == 17 + assert len(model.reactions) == 10 From bd756d82e9aea7a6752b1d62540f50dbf2921fa6 Mon Sep 17 00:00:00 2001 From: "Moritz E. Beber" Date: Wed, 22 Sep 2021 00:25:16 +0200 Subject: [PATCH 2/2] style: sort imports --- src/maud/sbml_compat.py | 6 +++--- tests/test_integration/test_sbml.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/maud/sbml_compat.py b/src/maud/sbml_compat.py index 74fae4bb7..4da91d412 100644 --- a/src/maud/sbml_compat.py +++ b/src/maud/sbml_compat.py @@ -20,17 +20,17 @@ from __future__ import annotations from pathlib import Path -from typing import List, Dict +from typing import Dict, List import libsbml from .data_model import ( Compartment, + Enzyme, + KineticModel, Metabolite, MetaboliteInCompartment, - Enzyme, Reaction, - KineticModel, ) diff --git a/tests/test_integration/test_sbml.py b/tests/test_integration/test_sbml.py index 28935d926..e04a1e002 100644 --- a/tests/test_integration/test_sbml.py +++ b/tests/test_integration/test_sbml.py @@ -1,6 +1,6 @@ from pathlib import Path -from maud.sbml_compat import SBMLModelParser, MaudModelBuilder +from maud.sbml_compat import MaudModelBuilder, SBMLModelParser data_dir = Path(__file__).parent.parent / "data"