mntools 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mntools/__init__.py +14 -0
- mntools/compartment.py +38 -0
- mntools/constants.py +19 -0
- mntools/load/__init__.py +3 -0
- mntools/load/loader_sbml.py +208 -0
- mntools/metabolicnetwork.py +308 -0
- mntools/metabolite.py +74 -0
- mntools/reaction.py +182 -0
- mntools/species.py +76 -0
- mntools/types.py +9 -0
- mntools/utils/stoichiometry.py +9 -0
- mntools/warning.py +16 -0
- mntools-0.1.0.dist-info/METADATA +27 -0
- mntools-0.1.0.dist-info/RECORD +15 -0
- mntools-0.1.0.dist-info/WHEEL +4 -0
mntools/__init__.py
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""Metabolic Network Tools."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from enum import Enum
|
|
6
|
+
|
|
7
|
+
from mntools.compartment import Compartment
|
|
8
|
+
from mntools.metabolicnetwork import (
|
|
9
|
+
MetabolicNetwork,
|
|
10
|
+
)
|
|
11
|
+
from mntools.metabolite import Metabolite
|
|
12
|
+
from mntools.reaction import Reaction
|
|
13
|
+
from mntools.species import Species
|
|
14
|
+
from mntools.types import Side, StoichCoeff
|
mntools/compartment.py
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING, Optional
|
|
4
|
+
|
|
5
|
+
if TYPE_CHECKING:
|
|
6
|
+
from mntools.species import Species
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Compartment:
|
|
10
|
+
"""todo."""
|
|
11
|
+
|
|
12
|
+
id: str #: Primary identifier
|
|
13
|
+
name: str #: Name, if present
|
|
14
|
+
|
|
15
|
+
# Network
|
|
16
|
+
_species: list[Species]
|
|
17
|
+
|
|
18
|
+
# Getters
|
|
19
|
+
def species(self) -> list[Species]:
|
|
20
|
+
return self._species
|
|
21
|
+
|
|
22
|
+
# Setters
|
|
23
|
+
def __init__(self, id: str, name: Optional[str]):
|
|
24
|
+
self.id = id
|
|
25
|
+
self.name = name if name is not None else id
|
|
26
|
+
|
|
27
|
+
self._species = list()
|
|
28
|
+
|
|
29
|
+
def add_species(self, species: Species):
|
|
30
|
+
if species in self._species:
|
|
31
|
+
raise ValueError(f"Species {species} already in compartment {self.id}.")
|
|
32
|
+
self._species.append(species)
|
|
33
|
+
|
|
34
|
+
def __str__(self):
|
|
35
|
+
return self.name
|
|
36
|
+
|
|
37
|
+
def __repr__(self):
|
|
38
|
+
return self.id
|
mntools/constants.py
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""Various tables."""
|
|
2
|
+
|
|
3
|
+
# Names and ids to recognize the extracellular compartment.
|
|
4
|
+
extracellular_names: set[str] = {
|
|
5
|
+
"external",
|
|
6
|
+
"extracellular",
|
|
7
|
+
"external_species",
|
|
8
|
+
"c_e",
|
|
9
|
+
"e",
|
|
10
|
+
"External_Species",
|
|
11
|
+
"C_e",
|
|
12
|
+
"extracellular space",
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
biomass_names: set[str] = {
|
|
16
|
+
"biomass",
|
|
17
|
+
"growth",
|
|
18
|
+
"biomass reaction",
|
|
19
|
+
}
|
mntools/load/__init__.py
ADDED
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
from math import isnan
|
|
2
|
+
from typing import Optional
|
|
3
|
+
|
|
4
|
+
import chemrecon
|
|
5
|
+
import libsbml
|
|
6
|
+
|
|
7
|
+
from mntools import Compartment, MetabolicNetwork, Reaction, Species, StoichCoeff
|
|
8
|
+
from mntools.constants import biomass_names
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def load_from_sbml(
|
|
12
|
+
filename: str,
|
|
13
|
+
metabolite_annotation_type: Optional[str] = "autodetect",
|
|
14
|
+
reaction_annotation_type: Optional[str] = "autodetect",
|
|
15
|
+
) -> MetabolicNetwork:
|
|
16
|
+
|
|
17
|
+
model = MetabolicNetwork(name="test", filename=filename)
|
|
18
|
+
|
|
19
|
+
# Read the SBML
|
|
20
|
+
reader = libsbml.SBMLReader()
|
|
21
|
+
sbml_file = reader.readSBML(filename)
|
|
22
|
+
sbml_model: libsbml.Model = sbml_file.getModel()
|
|
23
|
+
|
|
24
|
+
# Annotation parser
|
|
25
|
+
annotation_parser = libsbml.RDFAnnotationParser()
|
|
26
|
+
|
|
27
|
+
# Determine the name of the model
|
|
28
|
+
# TODO
|
|
29
|
+
# Fall back to filename if name not found
|
|
30
|
+
|
|
31
|
+
# Determine type of id annotations
|
|
32
|
+
metabolite_annotation: Optional[chemrecon.IdentifierTypeCompound]
|
|
33
|
+
reaction_annotation: Optional[chemrecon.IdentifierTypeReaction]
|
|
34
|
+
match metabolite_annotation_type:
|
|
35
|
+
case "autodetect":
|
|
36
|
+
# Autodetect from the annotations
|
|
37
|
+
metabolite_annotation = parse_metabolite_id_type(sbml_model)
|
|
38
|
+
case None:
|
|
39
|
+
# No annotation type set
|
|
40
|
+
metabolite_annotation = None
|
|
41
|
+
case _:
|
|
42
|
+
# Try to parse the string
|
|
43
|
+
metabolite_annotation = chemrecon.recognize_id_type_compound(
|
|
44
|
+
metabolite_annotation_type
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
match reaction_annotation_type:
|
|
48
|
+
case "autodetect":
|
|
49
|
+
# Autodetect from the annotations
|
|
50
|
+
reaction_annotation = parse_reaction_id_type(sbml_model)
|
|
51
|
+
case None:
|
|
52
|
+
# No annotation type set
|
|
53
|
+
reaction_annotation = None
|
|
54
|
+
case _:
|
|
55
|
+
# Try to parse the string
|
|
56
|
+
reaction_annotation = chemrecon.recognize_id_type_reaction(
|
|
57
|
+
reaction_annotation_type
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
model.set_metabolite_annotation_type(metabolite_annotation)
|
|
61
|
+
model.set_reaction_annotation_type(reaction_annotation)
|
|
62
|
+
|
|
63
|
+
# Load compartments
|
|
64
|
+
sbml_compartment_list = sbml_model.getListOfCompartments()
|
|
65
|
+
for sbml_compartment in sbml_compartment_list:
|
|
66
|
+
id = sbml_compartment.getId()
|
|
67
|
+
name = sbml_compartment.getName()
|
|
68
|
+
|
|
69
|
+
compartment = Compartment(id=id, name=name)
|
|
70
|
+
model.add_compartment(compartment)
|
|
71
|
+
|
|
72
|
+
# Load species
|
|
73
|
+
sbml_species_list = sbml_model.getListOfSpecies()
|
|
74
|
+
for sbml_species in sbml_species_list:
|
|
75
|
+
id = sbml_species.getId()
|
|
76
|
+
name = sbml_species.getName()
|
|
77
|
+
compartment_id = sbml_species.getCompartment()
|
|
78
|
+
compartment = model.get_or_create_compartment(compartment_id)
|
|
79
|
+
|
|
80
|
+
# Annotations
|
|
81
|
+
species_annotation_strs: set[str] = set()
|
|
82
|
+
for cvterm in sbml_species.getCVTerms():
|
|
83
|
+
for i in range(cvterm.getNumResources()):
|
|
84
|
+
res = cvterm.getResourceURI(i)
|
|
85
|
+
if res.startswith("http://identifiers.org"):
|
|
86
|
+
species_annotation_strs.add(res)
|
|
87
|
+
elif res.startswith("https://identifiers.org"):
|
|
88
|
+
species_annotation_strs.add(res)
|
|
89
|
+
|
|
90
|
+
species = Species(id=id, name=name, compartment=compartment)
|
|
91
|
+
species.annotation_strs = species_annotation_strs
|
|
92
|
+
model.add_species(species)
|
|
93
|
+
|
|
94
|
+
# Load reactions
|
|
95
|
+
sbml_reaction_list = sbml_model.getListOfReactions()
|
|
96
|
+
for sbml_reaction in sbml_reaction_list:
|
|
97
|
+
id = sbml_reaction.getId()
|
|
98
|
+
name = sbml_reaction.getName()
|
|
99
|
+
|
|
100
|
+
# Get species which take part
|
|
101
|
+
lhs_float: dict[Species, float] = dict()
|
|
102
|
+
rhs_float: dict[Species, float] = dict()
|
|
103
|
+
lhs: dict[Species, StoichCoeff] = dict()
|
|
104
|
+
rhs: dict[Species, StoichCoeff] = dict()
|
|
105
|
+
|
|
106
|
+
for stoich_mult, listOfSpeciesRefs in [
|
|
107
|
+
(-1, sbml_reaction.getListOfReactants()),
|
|
108
|
+
(1, sbml_reaction.getListOfProducts()),
|
|
109
|
+
]:
|
|
110
|
+
float_stoich_dict: dict[Species, float] = dict()
|
|
111
|
+
int_stoich_dict: dict[Species, int] = dict()
|
|
112
|
+
|
|
113
|
+
for sbml_species_ref in listOfSpeciesRefs:
|
|
114
|
+
stoich_float = sbml_species_ref.getStoichiometry()
|
|
115
|
+
species_id = sbml_species_ref.getSpecies()
|
|
116
|
+
species = model.get_species(species_id)
|
|
117
|
+
if species is None:
|
|
118
|
+
raise ValueError(
|
|
119
|
+
f"Reaction {id} refers to species {species_id} not found in model."
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
if species in float_stoich_dict:
|
|
123
|
+
# In this case, assume multiple entries correspond to stoichiometry
|
|
124
|
+
float_stoich_dict[species] += 1
|
|
125
|
+
int_stoich_dict[species] += 1
|
|
126
|
+
|
|
127
|
+
if isnan(stoich_float):
|
|
128
|
+
# If no stoichiometry is provided, assume 1
|
|
129
|
+
stoich_float = 1
|
|
130
|
+
|
|
131
|
+
float_stoich_dict[species] = stoich_float * stoich_mult
|
|
132
|
+
int_stoich_dict[species] = int(stoich_float * stoich_mult)
|
|
133
|
+
|
|
134
|
+
# Set the dicts
|
|
135
|
+
match stoich_mult:
|
|
136
|
+
case -1:
|
|
137
|
+
lhs_float = float_stoich_dict
|
|
138
|
+
lhs = int_stoich_dict
|
|
139
|
+
case 1:
|
|
140
|
+
rhs_float = float_stoich_dict
|
|
141
|
+
rhs = int_stoich_dict
|
|
142
|
+
|
|
143
|
+
# Annotations
|
|
144
|
+
reaction_annotation_strs: set[str] = set()
|
|
145
|
+
for cvterm in sbml_reaction.getCVTerms():
|
|
146
|
+
for i in range(cvterm.getNumResources()):
|
|
147
|
+
res = cvterm.getResourceURI(i)
|
|
148
|
+
if res.startswith("http://identifiers.org"):
|
|
149
|
+
reaction_annotation_strs.add(res)
|
|
150
|
+
elif res.startswith("https://identifiers.org"):
|
|
151
|
+
reaction_annotation_strs.add(res)
|
|
152
|
+
|
|
153
|
+
# Create reaction and add
|
|
154
|
+
reaction = Reaction(id=id, name=name, lhs=lhs, rhs=rhs)
|
|
155
|
+
reaction.annotation_strs = reaction_annotation_strs
|
|
156
|
+
model.add_reaction(reaction)
|
|
157
|
+
|
|
158
|
+
# Finalize and validate
|
|
159
|
+
model.process()
|
|
160
|
+
return model
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def is_id_special(id: str) -> bool:
|
|
164
|
+
"""Whether a reaction/metabolite id is 'nonstandard', or in some sense apart from the majority of compounds.
|
|
165
|
+
For example, the biomass species is 'special'.
|
|
166
|
+
"""
|
|
167
|
+
if id.lower() in biomass_names:
|
|
168
|
+
return True
|
|
169
|
+
|
|
170
|
+
return False
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def parse_metabolite_id_type(
|
|
174
|
+
sbml_model: libsbml.Model,
|
|
175
|
+
) -> Optional[chemrecon.IdentifierTypeCompound]:
|
|
176
|
+
id_strings: list[str] = list()
|
|
177
|
+
sbml_species_list = sbml_model.getListOfSpecies()
|
|
178
|
+
for sbml_species in sbml_species_list:
|
|
179
|
+
species_id = sbml_species.getId()
|
|
180
|
+
id_strings.append(species_id)
|
|
181
|
+
|
|
182
|
+
# Check if ChEbI
|
|
183
|
+
if all(s.startswith("M_chebi") for s in id_strings if not is_id_special(s)):
|
|
184
|
+
return chemrecon.C_CHEBI
|
|
185
|
+
|
|
186
|
+
# Check if BiGG
|
|
187
|
+
if all(s.startswith("M_") for s in id_strings if not is_id_special(s)):
|
|
188
|
+
return chemrecon.C_BIGG
|
|
189
|
+
|
|
190
|
+
# If none found, return none
|
|
191
|
+
return None
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def parse_reaction_id_type(
|
|
195
|
+
sbml_model: libsbml.Model,
|
|
196
|
+
) -> Optional[chemrecon.IdentifierTypeReaction]:
|
|
197
|
+
id_strings: list[str] = list()
|
|
198
|
+
sbml_reaction_list = sbml_model.getListOfReactions()
|
|
199
|
+
for sbml_reaction in sbml_reaction_list:
|
|
200
|
+
reaction_id = sbml_reaction.getId()
|
|
201
|
+
id_strings.append(reaction_id)
|
|
202
|
+
|
|
203
|
+
# Check if BiGG
|
|
204
|
+
if all(s.startswith("R_") for s in id_strings if not is_id_special(s)):
|
|
205
|
+
return chemrecon.R_BIGG
|
|
206
|
+
|
|
207
|
+
# If none found, return none
|
|
208
|
+
return None
|
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
"""Main class for defining a metabolic network."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections import defaultdict
|
|
6
|
+
from typing import TYPE_CHECKING, Any, Optional
|
|
7
|
+
|
|
8
|
+
import chemrecon
|
|
9
|
+
from chemrecon.utils.get_id_type import get_id_from_identifiers_org
|
|
10
|
+
|
|
11
|
+
from mntools.compartment import Compartment
|
|
12
|
+
from mntools.metabolite import Metabolite
|
|
13
|
+
from mntools.reaction import Reaction
|
|
14
|
+
from mntools.species import Species
|
|
15
|
+
from mntools.types import Side, StoichCoeff
|
|
16
|
+
from mntools.warning import ModelWarning
|
|
17
|
+
|
|
18
|
+
# Notes:
|
|
19
|
+
# SBML: id and metaid (see: https://sbml.org/documents/elaborations/metaid_syntax/)
|
|
20
|
+
# - id: Must conform to XML id standard
|
|
21
|
+
# - metaid: Legacy - does not need to conform to XML id standard
|
|
22
|
+
# Only use ID - metaid is always just id, but with meta_ prefix or similar.
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class MetabolicNetwork:
|
|
26
|
+
"""A chemical reaection network."""
|
|
27
|
+
|
|
28
|
+
# Basics
|
|
29
|
+
name: str
|
|
30
|
+
filename: str #: Filename from which this was created.
|
|
31
|
+
model_text: Optional[str] #: The text file defining the model, if available.
|
|
32
|
+
native_repr: Any #:
|
|
33
|
+
|
|
34
|
+
# Annotations
|
|
35
|
+
_metabolite_annotation_type: Optional[chemrecon.IdentifierTypeCompound]
|
|
36
|
+
_reaction_annotation_type: Optional[chemrecon.IdentifierTypeReaction]
|
|
37
|
+
|
|
38
|
+
# Network
|
|
39
|
+
_species: list[Species]
|
|
40
|
+
_metabolites: list[Metabolite]
|
|
41
|
+
_reactions: list[Reaction]
|
|
42
|
+
_compartments: list[Compartment]
|
|
43
|
+
|
|
44
|
+
# Network graph
|
|
45
|
+
# TODO
|
|
46
|
+
|
|
47
|
+
# Lookup tables
|
|
48
|
+
_compartment_lookup: dict[str, Compartment] # Lookup for compartments by suffix
|
|
49
|
+
_species_lookup: dict[str, Species]
|
|
50
|
+
_metabolite_lookup: dict[str, Metabolite]
|
|
51
|
+
_reaction_lookup: dict[str, Reaction]
|
|
52
|
+
|
|
53
|
+
# Meta
|
|
54
|
+
loader: Optional[
|
|
55
|
+
Any # TODO
|
|
56
|
+
] #: The loader, which created this network, if any. Can be used to access load warnings (parser warnings).
|
|
57
|
+
warnings: list[
|
|
58
|
+
ModelWarning
|
|
59
|
+
] #: Any semantic uncertainties encountered when loading the network.
|
|
60
|
+
|
|
61
|
+
# Getters
|
|
62
|
+
# ------------------------------------------------------------------------------------------------------------------
|
|
63
|
+
def all_species(self) -> list[Species]:
|
|
64
|
+
return self._species
|
|
65
|
+
|
|
66
|
+
def all_metabolites(self) -> list[Metabolite]:
|
|
67
|
+
return self._metabolites
|
|
68
|
+
|
|
69
|
+
def all_reactions(self) -> list[Reaction]:
|
|
70
|
+
return self._reactions
|
|
71
|
+
|
|
72
|
+
def all_compartments(self) -> list[Compartment]:
|
|
73
|
+
return self._compartments
|
|
74
|
+
|
|
75
|
+
def get_species(self, identifier: str) -> Optional[Species]:
|
|
76
|
+
return self._species_lookup.get(identifier, None)
|
|
77
|
+
|
|
78
|
+
def get_metabolite(self, identifier: str) -> Optional[Metabolite]:
|
|
79
|
+
return self._metabolite_lookup.get(identifier, None)
|
|
80
|
+
|
|
81
|
+
def get_reaction(self, identifier: str) -> Optional[Reaction]:
|
|
82
|
+
return self._reaction_lookup.get(identifier, None)
|
|
83
|
+
|
|
84
|
+
def get_compartment(self, identifier: str) -> Optional[Compartment]:
|
|
85
|
+
return self._compartment_lookup.get(identifier, None)
|
|
86
|
+
|
|
87
|
+
def get_or_create_compartment(self, identifier: str) -> Compartment:
|
|
88
|
+
if identifier in self._compartment_lookup:
|
|
89
|
+
return self._compartment_lookup[identifier]
|
|
90
|
+
else:
|
|
91
|
+
compartment = Compartment(id=identifier, name=None)
|
|
92
|
+
self.add_compartment(compartment)
|
|
93
|
+
return compartment
|
|
94
|
+
|
|
95
|
+
# Analysis
|
|
96
|
+
# ----------------------------------------------------------------------------------------------------------
|
|
97
|
+
# TODO get graph
|
|
98
|
+
|
|
99
|
+
# TODO get stoich matrix
|
|
100
|
+
|
|
101
|
+
# Warnings
|
|
102
|
+
# ----------------------------------------------------------------------------------------------------------
|
|
103
|
+
def get_warnings_and_errors(self) -> list[ModelWarning]:
|
|
104
|
+
"""List all the errors and warnings encountered when loading this model."""
|
|
105
|
+
# TODO
|
|
106
|
+
raise NotImplementedError()
|
|
107
|
+
|
|
108
|
+
# Metabolic network creation
|
|
109
|
+
# ----------------------------------------------------------------------------------------------------------
|
|
110
|
+
def __init__(
|
|
111
|
+
self,
|
|
112
|
+
name: str,
|
|
113
|
+
filename: str,
|
|
114
|
+
model_text: Optional[str] = None,
|
|
115
|
+
):
|
|
116
|
+
""" """
|
|
117
|
+
# Model information
|
|
118
|
+
self.name = name
|
|
119
|
+
self.filename = filename.split("/")[-1]
|
|
120
|
+
self.model_text = model_text
|
|
121
|
+
|
|
122
|
+
# Annotations
|
|
123
|
+
self._metabolite_annotation_type = None
|
|
124
|
+
self._reaction_annotation_type = None
|
|
125
|
+
|
|
126
|
+
# Reaction network
|
|
127
|
+
self._species = list()
|
|
128
|
+
self._metabolites = list()
|
|
129
|
+
self._reactions = list()
|
|
130
|
+
self._compartments = list()
|
|
131
|
+
|
|
132
|
+
self._species_lookup = dict()
|
|
133
|
+
self._metabolite_lookup = dict()
|
|
134
|
+
self._reaction_lookup = dict()
|
|
135
|
+
self._compartment_lookup = dict()
|
|
136
|
+
|
|
137
|
+
# Meta
|
|
138
|
+
self.warnings = list()
|
|
139
|
+
|
|
140
|
+
# Set annotations
|
|
141
|
+
def set_metabolite_annotation_type(
|
|
142
|
+
self, annotation_type: Optional[chemrecon.IdentifierTypeCompound]
|
|
143
|
+
):
|
|
144
|
+
"""If the 'id' field of the model correspond to specific database annotations, MNTools can extract database
|
|
145
|
+
entries from the ids. Enter a string like 'bigg' or 'chebi' to associate a database entry with the compound ids.
|
|
146
|
+
"""
|
|
147
|
+
self._metabolite_annotation_type = annotation_type
|
|
148
|
+
|
|
149
|
+
def set_reaction_annotation_type(
|
|
150
|
+
self, annotation_type: Optional[chemrecon.IdentifierTypeReaction]
|
|
151
|
+
):
|
|
152
|
+
"""If the 'id' field of the model correspond to specific database annotations, MNTools can extract database
|
|
153
|
+
entries from the ids. Enter a string like 'bigg' or 'chebi' to associate a database entry with the reaction ids.
|
|
154
|
+
"""
|
|
155
|
+
self._reaction_annotation_type = annotation_type
|
|
156
|
+
|
|
157
|
+
# Adding network elements
|
|
158
|
+
def add_species(self, species: Species):
|
|
159
|
+
self._species.append(species)
|
|
160
|
+
self._species_lookup[species.id] = species
|
|
161
|
+
|
|
162
|
+
# Add to compartment
|
|
163
|
+
species.compartment.add_species(species)
|
|
164
|
+
|
|
165
|
+
def add_metabolite(self, metabolite: Metabolite):
|
|
166
|
+
self._metabolites.append(metabolite)
|
|
167
|
+
self._metabolite_lookup[metabolite.id] = metabolite
|
|
168
|
+
for species in metabolite.species():
|
|
169
|
+
if species.associated_metabolite is not None:
|
|
170
|
+
raise ValueError(
|
|
171
|
+
f"Attempting to assign metabolite to {species} already associated with metabolite {metabolite}."
|
|
172
|
+
)
|
|
173
|
+
species.associated_metabolite = metabolite
|
|
174
|
+
|
|
175
|
+
def add_reaction(self, reaction: Reaction):
|
|
176
|
+
self._reactions.append(reaction)
|
|
177
|
+
self._reaction_lookup[reaction.id] = reaction
|
|
178
|
+
for species, coeff in reaction.get_species_with_sum_stoichiometry().items():
|
|
179
|
+
species.add_reaction(reaction, coeff)
|
|
180
|
+
|
|
181
|
+
def add_compartment(self, compartment: Compartment):
|
|
182
|
+
if compartment.id in self._compartment_lookup:
|
|
183
|
+
raise ValueError(f"Compartment with id {compartment.id} already exists.")
|
|
184
|
+
self._compartments.append(compartment)
|
|
185
|
+
self._compartment_lookup[compartment.id] = compartment
|
|
186
|
+
|
|
187
|
+
def process(self):
|
|
188
|
+
"""To be called after adding all elements to the model. Will perform sanity checks and
|
|
189
|
+
post-processing."""
|
|
190
|
+
|
|
191
|
+
# Identify and create metabolites (identify species across compartments).
|
|
192
|
+
# - by name
|
|
193
|
+
name_lookup_dict: dict[str, set[Species]] = defaultdict(set)
|
|
194
|
+
for s in self._species:
|
|
195
|
+
if s.name is not None:
|
|
196
|
+
name_lookup_dict[s.name].add(s)
|
|
197
|
+
for common_name, species_set in name_lookup_dict.items():
|
|
198
|
+
metabolite = Metabolite(
|
|
199
|
+
species_set, unified_id=common_name, unified_by="name"
|
|
200
|
+
)
|
|
201
|
+
self.add_metabolite(metabolite)
|
|
202
|
+
|
|
203
|
+
# TODO also try to identify compounds based on annotation
|
|
204
|
+
# - If BiGG/ChEBI: Can parse based on common prefix
|
|
205
|
+
|
|
206
|
+
# When metabolites are created, we can process the metabolites/compartments information for all
|
|
207
|
+
# reactions
|
|
208
|
+
for r in self._reactions:
|
|
209
|
+
r.process()
|
|
210
|
+
|
|
211
|
+
# Remove empty compartments
|
|
212
|
+
compartments_to_remove: list[Compartment] = list()
|
|
213
|
+
for comp in self._compartments:
|
|
214
|
+
if len(comp.species()) == 0:
|
|
215
|
+
compartments_to_remove.append(comp)
|
|
216
|
+
|
|
217
|
+
compartment_keys_to_remove: list[str] = list()
|
|
218
|
+
for comp in compartments_to_remove:
|
|
219
|
+
self._compartments.remove(comp)
|
|
220
|
+
compartment_keys_to_remove.append(comp.id)
|
|
221
|
+
|
|
222
|
+
for key in compartment_keys_to_remove:
|
|
223
|
+
del self._compartment_lookup[key]
|
|
224
|
+
|
|
225
|
+
# Validate
|
|
226
|
+
# - All species are in a compartment
|
|
227
|
+
for s in self._species:
|
|
228
|
+
if s.compartment is None or s.compartment not in self._compartments:
|
|
229
|
+
raise ValueError(f"Species {s} is not in a compartment.")
|
|
230
|
+
|
|
231
|
+
# - Verify that all species have associated metabolite
|
|
232
|
+
for s in self._species:
|
|
233
|
+
if s.associated_metabolite is None:
|
|
234
|
+
raise ValueError(f"Species {s} does not have metabolite assigned.")
|
|
235
|
+
|
|
236
|
+
# - Verify reactions
|
|
237
|
+
# TODO
|
|
238
|
+
|
|
239
|
+
# Finalize
|
|
240
|
+
pass
|
|
241
|
+
|
|
242
|
+
def create_annotations(self):
|
|
243
|
+
"""Adds the annotations as ChemRecon proto-entries (with no looked-up recon id).
|
|
244
|
+
So the entry annotations may or may not exist in ChemRecon.
|
|
245
|
+
TODO add a method which also looks up the entries.
|
|
246
|
+
"""
|
|
247
|
+
# Load all identifiers
|
|
248
|
+
compound_annotation_str_map: dict[str, Optional[chemrecon.Compound]] = dict()
|
|
249
|
+
reaction_annotation_str_map: dict[str, Optional[chemrecon.Reaction]] = dict()
|
|
250
|
+
for s in self.all_species():
|
|
251
|
+
for annotation_str in s.annotation_strs:
|
|
252
|
+
compound_annotation_str_map[annotation_str] = None
|
|
253
|
+
for r in self.all_reactions():
|
|
254
|
+
for annotation_str in r.annotation_strs:
|
|
255
|
+
reaction_annotation_str_map[annotation_str] = None
|
|
256
|
+
|
|
257
|
+
# Create 'prototype' entries from the identifiers.org strings
|
|
258
|
+
prototype_entries_compound: dict[str, chemrecon.Compound] = dict()
|
|
259
|
+
for compound_str in compound_annotation_str_map.keys():
|
|
260
|
+
id_org_result = get_id_from_identifiers_org(
|
|
261
|
+
compound_str, chemrecon.IdentifierTypeCompound
|
|
262
|
+
)
|
|
263
|
+
if id_org_result is not None:
|
|
264
|
+
prototype_entries_compound[compound_str] = chemrecon.Compound(
|
|
265
|
+
id_type=id_org_result[0].enum_type,
|
|
266
|
+
source_id=id_org_result[0].std_identifier(id_org_result[1]),
|
|
267
|
+
)
|
|
268
|
+
prototype_entries_reaction: dict[str, chemrecon.Reaction] = dict()
|
|
269
|
+
for reaction_str in reaction_annotation_str_map.keys():
|
|
270
|
+
id_org_result = get_id_from_identifiers_org(
|
|
271
|
+
reaction_str, chemrecon.IdentifierTypeCompound
|
|
272
|
+
)
|
|
273
|
+
if id_org_result is not None:
|
|
274
|
+
prototype_entries_reaction[reaction_str] = chemrecon.Reaction(
|
|
275
|
+
id_type=id_org_result[0].enum_type,
|
|
276
|
+
source_id=id_org_result[0].std_identifier(id_org_result[1]),
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
# Assign the prototype entries
|
|
280
|
+
for s in self.all_species():
|
|
281
|
+
s.entries = set()
|
|
282
|
+
for annotation_str in s.annotation_strs:
|
|
283
|
+
try:
|
|
284
|
+
s.entries.add(prototype_entries_compound[annotation_str])
|
|
285
|
+
except KeyError:
|
|
286
|
+
# Not found. TODO log warning?
|
|
287
|
+
pass
|
|
288
|
+
|
|
289
|
+
for r in self.all_reactions():
|
|
290
|
+
r.entries = set()
|
|
291
|
+
for annotation_str in r.annotation_strs:
|
|
292
|
+
try:
|
|
293
|
+
r.entries.add(prototype_entries_reaction[annotation_str])
|
|
294
|
+
except KeyError:
|
|
295
|
+
# Not found. TODO log warning?
|
|
296
|
+
pass
|
|
297
|
+
|
|
298
|
+
# Add entries to metabolites (union of entries of associated species)
|
|
299
|
+
for m in self.all_metabolites():
|
|
300
|
+
m.entries = set.union(*[s.entries for s in m.species()])
|
|
301
|
+
pass
|
|
302
|
+
|
|
303
|
+
# Done.
|
|
304
|
+
pass
|
|
305
|
+
|
|
306
|
+
def lookup_annotations(self):
|
|
307
|
+
# TODO method for getting additional data from the looked up annotations in the ChemRecon db.
|
|
308
|
+
pass
|
mntools/metabolite.py
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING, Any, Iterable, Optional
|
|
4
|
+
|
|
5
|
+
import chemrecon
|
|
6
|
+
|
|
7
|
+
from mntools.warning import ModelWarning
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from mntools.compartment import Compartment
|
|
11
|
+
from mntools.species import Species
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Metabolite:
|
|
15
|
+
"""
|
|
16
|
+
In this model, a `Metabolite` is a set of species that are in different compartments.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
id: str #: Primary identifier
|
|
20
|
+
name: str #:
|
|
21
|
+
|
|
22
|
+
# Annotations
|
|
23
|
+
primary_entry: Optional[chemrecon.Compound]
|
|
24
|
+
entries: set[chemrecon.Compound]
|
|
25
|
+
unified_by: str
|
|
26
|
+
|
|
27
|
+
# Network
|
|
28
|
+
_species: set[Species]
|
|
29
|
+
_species_by_compartment: dict[Compartment, Species] = dict()
|
|
30
|
+
|
|
31
|
+
# Meta
|
|
32
|
+
warnings: list[ModelWarning]
|
|
33
|
+
|
|
34
|
+
def species(self) -> set[Species]:
|
|
35
|
+
return self._species
|
|
36
|
+
|
|
37
|
+
def get_species_in_compartment(self, compartment: Compartment) -> Optional[Species]:
|
|
38
|
+
return self._species_by_compartment.get(compartment, None)
|
|
39
|
+
|
|
40
|
+
def __init__(self, species: Iterable[Species], unified_id: str, unified_by: str):
|
|
41
|
+
"""The 'unified_by' field determines the common field unifying the species that make up the compound."""
|
|
42
|
+
self._species = set()
|
|
43
|
+
self.id = unified_id
|
|
44
|
+
self.unified_by = unified_by
|
|
45
|
+
self._species_by_compartment = dict()
|
|
46
|
+
for s in species:
|
|
47
|
+
if s in self._species:
|
|
48
|
+
raise ValueError(f"Species {s} already in metabolite {self.id}.")
|
|
49
|
+
self._species.add(s)
|
|
50
|
+
self._species_by_compartment[s.compartment] = s
|
|
51
|
+
|
|
52
|
+
# Derive name/id from the species
|
|
53
|
+
if self.unified_by == "name":
|
|
54
|
+
self.name = unified_id
|
|
55
|
+
|
|
56
|
+
# Derive entries
|
|
57
|
+
self.entries = next(iter(species)).entries
|
|
58
|
+
for s in species:
|
|
59
|
+
if s.entries != self.entries:
|
|
60
|
+
# TODO raise warning
|
|
61
|
+
self.entries.update(s.entries)
|
|
62
|
+
|
|
63
|
+
# The primary entry of the metabolite is the primary entry of the species only if all agree.
|
|
64
|
+
primary_entry = next(iter(species)).primary_entry
|
|
65
|
+
if all(s.primary_entry == primary_entry for s in species):
|
|
66
|
+
self.primary_entry = primary_entry
|
|
67
|
+
else:
|
|
68
|
+
self.primary_entry = None
|
|
69
|
+
|
|
70
|
+
def __str__(self):
|
|
71
|
+
return self.name
|
|
72
|
+
|
|
73
|
+
def __repr__(self):
|
|
74
|
+
return self.id
|
mntools/reaction.py
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING, Any, Optional
|
|
4
|
+
|
|
5
|
+
import chemrecon
|
|
6
|
+
|
|
7
|
+
from mntools import compartment
|
|
8
|
+
from mntools.types import Side
|
|
9
|
+
from mntools.warning import ModelWarning
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from mntools.compartment import Compartment
|
|
13
|
+
from mntools.metabolite import Metabolite
|
|
14
|
+
from mntools.species import Species
|
|
15
|
+
from mntools.types import StoichCoeff
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class Reaction:
|
|
19
|
+
"""A reaction."""
|
|
20
|
+
|
|
21
|
+
id: str #: Primary identifier
|
|
22
|
+
name: str #:
|
|
23
|
+
|
|
24
|
+
# Annotations
|
|
25
|
+
primary_entry: Optional[chemrecon.Reaction]
|
|
26
|
+
entries: set[chemrecon.Reaction]
|
|
27
|
+
annotation_strs: set[str]
|
|
28
|
+
|
|
29
|
+
# Network
|
|
30
|
+
_species: dict[Side, dict[Species, StoichCoeff]]
|
|
31
|
+
_metabolites: dict[Side, set[Metabolite]] = dict()
|
|
32
|
+
_compartments: dict[Side, set[Compartment]] = dict()
|
|
33
|
+
|
|
34
|
+
# Meta
|
|
35
|
+
warnings: list[ModelWarning]
|
|
36
|
+
|
|
37
|
+
# Properties
|
|
38
|
+
is_biomass_reaction: bool
|
|
39
|
+
# - Reversibility
|
|
40
|
+
# - TODO reversible?
|
|
41
|
+
# - Transport reactions
|
|
42
|
+
# - TODO types (simple, antiporter, synporter ...)?
|
|
43
|
+
is_transport_reaction: bool #: We consider a reaction a transport reaction if it spans more than 1 compartment
|
|
44
|
+
is_transport_reaction_1to1: (
|
|
45
|
+
bool #: A transport reaction involving only the transported metabolites.
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
# Getters
|
|
49
|
+
def get_lhs(self) -> dict[Species, StoichCoeff]:
|
|
50
|
+
return self._species[Side.L]
|
|
51
|
+
|
|
52
|
+
def get_rhs(self) -> dict[Species, StoichCoeff]:
|
|
53
|
+
return self._species[Side.R]
|
|
54
|
+
|
|
55
|
+
def get_lhs_metabolites(self) -> dict[Metabolite, StoichCoeff]:
|
|
56
|
+
return {
|
|
57
|
+
s.associated_metabolite: coeff
|
|
58
|
+
for s, coeff in self._species[Side.L].items()
|
|
59
|
+
if s.associated_metabolite is not None
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
def get_rhs_metabolites(self) -> dict[Metabolite, StoichCoeff]:
|
|
63
|
+
return {
|
|
64
|
+
s.associated_metabolite: coeff
|
|
65
|
+
for s, coeff in self._species[Side.R].items()
|
|
66
|
+
if s.associated_metabolite is not None
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
def get_species(self) -> set[Species]:
|
|
70
|
+
"""Get the species which appear on both the LHS and RHS, if any."""
|
|
71
|
+
return set(self._species[Side.L].keys()) | set(self._species[Side.R].keys())
|
|
72
|
+
|
|
73
|
+
def get_metabolites(self) -> set[Metabolite]:
|
|
74
|
+
"""Get the metabolites involved in the reaction (species independent of compartment)."""
|
|
75
|
+
return set(self._metabolites[Side.L]) | set(self._metabolites[Side.R])
|
|
76
|
+
|
|
77
|
+
def get_species_with_sum_stoichiometry(self) -> dict[Species, StoichCoeff]:
|
|
78
|
+
"""Get all species involved and their stoichiometric coefficient.
|
|
79
|
+
If a species appears on both sides, the sum stoichiometric coefficient is listed.
|
|
80
|
+
"""
|
|
81
|
+
return {
|
|
82
|
+
s: self._species[Side.L].get(s, 0) + self._species[Side.R].get(s, 0)
|
|
83
|
+
for s in self._species[Side.L].keys() | self._species[Side.R].keys()
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
def get_metabolites_with_sum_stoichiometry(self) -> dict[Metabolite, StoichCoeff]:
|
|
87
|
+
"""Get all metabolites involved and their stoichiometric coefficient.
|
|
88
|
+
If a metabolite appears on both sides, the sum stoichiometric coefficient is listed.
|
|
89
|
+
This lists _metabolites_, not species.
|
|
90
|
+
So if this is a transport reaction, the sum stoichiometry should be 0.
|
|
91
|
+
"""
|
|
92
|
+
# TODO
|
|
93
|
+
raise NotImplementedError()
|
|
94
|
+
|
|
95
|
+
def get_compartment(self) -> Optional[Compartment]:
|
|
96
|
+
"""If not a transport reaction, return the compartment in which the reaction takes place."""
|
|
97
|
+
if self.is_transport_reaction:
|
|
98
|
+
return None
|
|
99
|
+
else:
|
|
100
|
+
assert (
|
|
101
|
+
len(self._compartments[Side.L]) == 1
|
|
102
|
+
and len(self._compartments[Side.R]) == 1
|
|
103
|
+
), "Reaction should only involve one compartment"
|
|
104
|
+
return next(iter(self._compartments[Side.L]))
|
|
105
|
+
|
|
106
|
+
def get_compartments(self) -> set[Compartment]:
|
|
107
|
+
"""Get the compartments of hte species involved in the reaction."""
|
|
108
|
+
return set(self._compartments[Side.L]) | set(self._compartments[Side.R])
|
|
109
|
+
|
|
110
|
+
def __init__(
|
|
111
|
+
self,
|
|
112
|
+
id: str,
|
|
113
|
+
name: Optional[str],
|
|
114
|
+
lhs: dict[Species, StoichCoeff],
|
|
115
|
+
rhs: dict[Species, StoichCoeff],
|
|
116
|
+
):
|
|
117
|
+
# Annotation
|
|
118
|
+
self.id = id
|
|
119
|
+
self.name = name if name is not None else id
|
|
120
|
+
self.primary_entry = None
|
|
121
|
+
self.entries = set()
|
|
122
|
+
self.annotation_strs = set()
|
|
123
|
+
|
|
124
|
+
# Network
|
|
125
|
+
self._species = {Side.L: lhs, Side.R: rhs}
|
|
126
|
+
self._metabolites = dict()
|
|
127
|
+
self._compartments = dict()
|
|
128
|
+
|
|
129
|
+
# Properties
|
|
130
|
+
# TODO determine whether this represents biomass
|
|
131
|
+
self.is_biomass_reaction = False
|
|
132
|
+
|
|
133
|
+
def process(self):
|
|
134
|
+
# Assign metabolites and compartments
|
|
135
|
+
self._metabolites[Side.L] = set()
|
|
136
|
+
self._metabolites[Side.R] = set()
|
|
137
|
+
self._compartments[Side.L] = set()
|
|
138
|
+
self._compartments[Side.R] = set()
|
|
139
|
+
for side in [Side.L, Side.R]:
|
|
140
|
+
for s in self._species[side]:
|
|
141
|
+
self._compartments[side].add(s.compartment)
|
|
142
|
+
if s.associated_metabolite is not None:
|
|
143
|
+
self._metabolites[side].add(s.associated_metabolite)
|
|
144
|
+
else:
|
|
145
|
+
# TODO raise a warning?
|
|
146
|
+
pass
|
|
147
|
+
|
|
148
|
+
# Identify whether this is a transport reaction
|
|
149
|
+
match len(self.get_compartments()):
|
|
150
|
+
# TODO other types of transport reactions (synporter, antiporter, etc.)?
|
|
151
|
+
case 1:
|
|
152
|
+
self.is_transport_reaction = False
|
|
153
|
+
self.is_transport_reaction_1to1 = False
|
|
154
|
+
case 2:
|
|
155
|
+
# Direct transport
|
|
156
|
+
self.is_transport_reaction = True
|
|
157
|
+
|
|
158
|
+
# Check whether 1-to-1 (same metabolites in both compartments)
|
|
159
|
+
if self._metabolites[Side.L] == self._metabolites[Side.R]:
|
|
160
|
+
self.is_transport_reaction_1to1 = True
|
|
161
|
+
else:
|
|
162
|
+
self.is_transport_reaction_1to1 = False
|
|
163
|
+
|
|
164
|
+
case _:
|
|
165
|
+
# Multiple compartments involved.
|
|
166
|
+
# TODO improve support for this case
|
|
167
|
+
self.is_transport_reaction = True
|
|
168
|
+
self.is_transport_reaction_1to1 = False
|
|
169
|
+
|
|
170
|
+
# Reversibility
|
|
171
|
+
# TODO:
|
|
172
|
+
# - Check if set in SBML?
|
|
173
|
+
# - Check if has an inverse reaction somewhere?
|
|
174
|
+
|
|
175
|
+
# Done processing this reaction
|
|
176
|
+
pass
|
|
177
|
+
|
|
178
|
+
def __str__(self):
|
|
179
|
+
return self.name
|
|
180
|
+
|
|
181
|
+
def __repr__(self):
|
|
182
|
+
return self.id
|
mntools/species.py
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections import defaultdict
|
|
4
|
+
from typing import TYPE_CHECKING, Any, Optional
|
|
5
|
+
|
|
6
|
+
import chemrecon
|
|
7
|
+
|
|
8
|
+
from mntools.warning import ModelWarning
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from mntools.compartment import Compartment
|
|
12
|
+
from mntools.metabolite import Metabolite
|
|
13
|
+
from mntools.reaction import Reaction
|
|
14
|
+
from mntools.types import StoichCoeff
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class Species:
|
|
18
|
+
"""
|
|
19
|
+
A species is a type of entity that can participate in reactions.
|
|
20
|
+
In this model, a species is a `Metabolite` in a particular `Compartment`.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
id: str #: Primary identifier
|
|
24
|
+
name: str #:
|
|
25
|
+
|
|
26
|
+
# Annotations
|
|
27
|
+
primary_entry: Optional[chemrecon.Compound]
|
|
28
|
+
entries: set[chemrecon.Compound]
|
|
29
|
+
annotation_strs: set[str]
|
|
30
|
+
is_biomass: bool
|
|
31
|
+
|
|
32
|
+
# Network
|
|
33
|
+
compartment: Compartment
|
|
34
|
+
associated_metabolite: Optional[Metabolite]
|
|
35
|
+
_in_reactions: dict[Reaction, StoichCoeff]
|
|
36
|
+
|
|
37
|
+
# Meta
|
|
38
|
+
warnings: list[ModelWarning]
|
|
39
|
+
|
|
40
|
+
# Getters
|
|
41
|
+
def get_reactions(self) -> dict[Reaction, StoichCoeff]:
|
|
42
|
+
"""Get a list of reactions that this species participates in.
|
|
43
|
+
If the species appears on both sides of the reaction, the sum stoichiometric coefficient is listed.
|
|
44
|
+
"""
|
|
45
|
+
return {r: stoich for r, stoich in self._in_reactions.items() if stoich != 0}
|
|
46
|
+
|
|
47
|
+
# Creation
|
|
48
|
+
def add_reaction(self, reaction: Reaction, stoich: StoichCoeff):
|
|
49
|
+
if reaction in self._in_reactions:
|
|
50
|
+
raise ValueError(f"Species {self.id} already in reaction {reaction.id}.")
|
|
51
|
+
self._in_reactions[reaction] = stoich
|
|
52
|
+
|
|
53
|
+
def __str__(self):
|
|
54
|
+
return self.name
|
|
55
|
+
|
|
56
|
+
def __repr__(self):
|
|
57
|
+
return self.id
|
|
58
|
+
|
|
59
|
+
def __init__(
|
|
60
|
+
self,
|
|
61
|
+
id: str,
|
|
62
|
+
name: Optional[str],
|
|
63
|
+
compartment: Compartment,
|
|
64
|
+
):
|
|
65
|
+
self.id = id
|
|
66
|
+
self.name = name if name is not None else id
|
|
67
|
+
self.primary_entry = None
|
|
68
|
+
self.entries = set()
|
|
69
|
+
self.annotation_strs = set()
|
|
70
|
+
self.compartment = compartment
|
|
71
|
+
self._in_reactions = defaultdict(int)
|
|
72
|
+
self.associated_metabolite = None
|
|
73
|
+
self.warnings = list()
|
|
74
|
+
|
|
75
|
+
# TODO determine whether this represents biomass
|
|
76
|
+
self.is_biomass = False
|
mntools/types.py
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import math
|
|
2
|
+
from fractions import Fraction
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def scale_to_integer(values: list[float]) -> list[int]:
|
|
6
|
+
fracs = [Fraction(v) for v in values]
|
|
7
|
+
denominator_lcm = math.lcm(*[f.denominator for f in fracs])
|
|
8
|
+
ints = [int(f * denominator_lcm) for f in fracs]
|
|
9
|
+
return ints
|
mntools/warning.py
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
class ModelWarning(Exception):
|
|
2
|
+
"""A warning emitted by the model loading procedure."""
|
|
3
|
+
|
|
4
|
+
message: str
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
# Types of warnings
|
|
8
|
+
# --------------------------------------------------------------------------------------------------------------
|
|
9
|
+
class SyntaxWarning(ModelWarning):
|
|
10
|
+
"""Something wrong with the syntax of the SBML file."""
|
|
11
|
+
|
|
12
|
+
# TODO
|
|
13
|
+
pass
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
# TODO warn when different annotations between species with the same name(compound)
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: mntools
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Metabolic Network Tools.
|
|
5
|
+
Keywords: bioinformatics,metabolic networks,metabolic network analysis,cheminformatics
|
|
6
|
+
Author: Casper Asbjørn Eriksen
|
|
7
|
+
Author-email: Casper Asbjørn Eriksen <casbjorn@imada.sdu.dk>
|
|
8
|
+
License-Expression: GPL-3.0-only
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
10
|
+
Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
|
|
11
|
+
Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
|
|
12
|
+
Classifier: Topic :: Scientific/Engineering :: Chemistry
|
|
13
|
+
Requires-Dist: chemrecon==0.1.5
|
|
14
|
+
Requires-Dist: python-libsbml==5.21.1
|
|
15
|
+
Requires-Dist: ruff>=0.15.10
|
|
16
|
+
Requires-Dist: ty>=0.0.31
|
|
17
|
+
Maintainer: Casper Asbjørn Eriksen
|
|
18
|
+
Maintainer-email: Casper Asbjørn Eriksen <casbjorn@imada.sdu.dk>
|
|
19
|
+
Requires-Python: >=3.12
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
|
|
22
|
+
# Metabolic Network Tools
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
MNTools is a library for loading and representing metabolic networks.
|
|
27
|
+
It uses [ChemRecon](gitlab.com/casbjorn/chemrecon) as its backend for handling database information.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
mntools/__init__.py,sha256=u3BdNqaOCQqJtwK7XQkV6ikiCjuNHiGbOBbJJZ5Cg8I,358
|
|
2
|
+
mntools/compartment.py,sha256=LftVSRFnQuu_2hmWn7yEnNqMBMgWvkeX8M0V0F51x3U,851
|
|
3
|
+
mntools/constants.py,sha256=U9Qoj_CNqHQb0AvRFMNh1gKdYae7CkSj9sIeQNuJccg,346
|
|
4
|
+
mntools/load/__init__.py,sha256=D2_ER0kWJV3_8CkZYLOkoq-R44KSWRt4aEUlvO_Xavk,105
|
|
5
|
+
mntools/load/loader_sbml.py,sha256=1ejW5NH5oJCNC2-rzamWLce2VZbIs4u5Myl0x1hOrJY,7372
|
|
6
|
+
mntools/metabolicnetwork.py,sha256=QnuOEt1FTm8sZvF_wU2HAU_Gq7WMHjQSw4d7DGMhZjM,11935
|
|
7
|
+
mntools/metabolite.py,sha256=doEl-kkCL8G3qEHA93z4WYLKbjbnwF95IZkEYXC9utM,2289
|
|
8
|
+
mntools/reaction.py,sha256=pIwjMGxCBkjhJaCfKabAzFKiQjueCQZVk38cdhznCDk,6442
|
|
9
|
+
mntools/species.py,sha256=s3buQnmNyB4eic6QrAbfeYCN9CrVgIVN47g2XiUb8SY,2200
|
|
10
|
+
mntools/types.py,sha256=vFtbYW_NloaLXZdIDdFy6vLugpfxdhEWEJyeGTt7X0Y,88
|
|
11
|
+
mntools/utils/stoichiometry.py,sha256=JEzJyHEsHOI4UZE97ahc_ZnuN-IsC2WhrVcjWCSRS2o,276
|
|
12
|
+
mntools/warning.py,sha256=8pIrzzqNZ_iZBPtUmpHF4OjNy4DWylu8iSWToRSuiTM,446
|
|
13
|
+
mntools-0.1.0.dist-info/WHEEL,sha256=WvwXFgRajeoYkfRVmDhkP4Qlqo31Mk687zIO2QQoFmw,80
|
|
14
|
+
mntools-0.1.0.dist-info/METADATA,sha256=xFOTOj4WGaYAfDoiIghwIp8h4JjUnNqc2L5aYH5t_IU,1042
|
|
15
|
+
mntools-0.1.0.dist-info/RECORD,,
|