modelbase2 0.1.78__py3-none-any.whl → 0.2.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.
- modelbase2/__init__.py +138 -26
- modelbase2/distributions.py +306 -0
- modelbase2/experimental/__init__.py +17 -0
- modelbase2/experimental/codegen.py +239 -0
- modelbase2/experimental/diff.py +227 -0
- modelbase2/experimental/notes.md +4 -0
- modelbase2/experimental/tex.py +521 -0
- modelbase2/fit.py +284 -0
- modelbase2/fns.py +185 -0
- modelbase2/integrators/__init__.py +19 -0
- modelbase2/integrators/int_assimulo.py +146 -0
- modelbase2/integrators/int_scipy.py +147 -0
- modelbase2/label_map.py +610 -0
- modelbase2/linear_label_map.py +301 -0
- modelbase2/mc.py +548 -0
- modelbase2/mca.py +280 -0
- modelbase2/model.py +1621 -0
- modelbase2/npe.py +343 -0
- modelbase2/parallel.py +171 -0
- modelbase2/parameterise.py +28 -0
- modelbase2/paths.py +36 -0
- modelbase2/plot.py +829 -0
- modelbase2/sbml/__init__.py +14 -0
- modelbase2/sbml/_data.py +77 -0
- modelbase2/sbml/_export.py +656 -0
- modelbase2/sbml/_import.py +585 -0
- modelbase2/sbml/_mathml.py +691 -0
- modelbase2/sbml/_name_conversion.py +52 -0
- modelbase2/sbml/_unit_conversion.py +74 -0
- modelbase2/scan.py +616 -0
- modelbase2/scope.py +96 -0
- modelbase2/simulator.py +635 -0
- modelbase2/surrogates/__init__.py +32 -0
- modelbase2/surrogates/_poly.py +66 -0
- modelbase2/surrogates/_torch.py +249 -0
- modelbase2/surrogates.py +316 -0
- modelbase2/types.py +352 -11
- modelbase2-0.2.0.dist-info/METADATA +81 -0
- modelbase2-0.2.0.dist-info/RECORD +42 -0
- {modelbase2-0.1.78.dist-info → modelbase2-0.2.0.dist-info}/WHEEL +1 -1
- modelbase2/core/__init__.py +0 -29
- modelbase2/core/algebraic_module_container.py +0 -130
- modelbase2/core/constant_container.py +0 -113
- modelbase2/core/data.py +0 -109
- modelbase2/core/name_container.py +0 -29
- modelbase2/core/reaction_container.py +0 -115
- modelbase2/core/utils.py +0 -28
- modelbase2/core/variable_container.py +0 -24
- modelbase2/ode/__init__.py +0 -13
- modelbase2/ode/integrator.py +0 -80
- modelbase2/ode/mca.py +0 -270
- modelbase2/ode/model.py +0 -470
- modelbase2/ode/simulator.py +0 -153
- modelbase2/utils/__init__.py +0 -0
- modelbase2/utils/plotting.py +0 -372
- modelbase2-0.1.78.dist-info/METADATA +0 -44
- modelbase2-0.1.78.dist-info/RECORD +0 -22
- {modelbase2-0.1.78.dist-info → modelbase2-0.2.0.dist-info/licenses}/LICENSE +0 -0
@@ -0,0 +1,301 @@
|
|
1
|
+
"""Linear Label Mapping Module for Metabolic Models.
|
2
|
+
|
3
|
+
This module implements linear label mapping functionality for tracking isotope labels
|
4
|
+
through metabolic networks. It provides utilities for:
|
5
|
+
|
6
|
+
- Generating linear isotope label combinations
|
7
|
+
- Mapping labels between substrates and products
|
8
|
+
- Processing stoichiometric coefficients for label transfer
|
9
|
+
|
10
|
+
"""
|
11
|
+
|
12
|
+
from __future__ import annotations
|
13
|
+
|
14
|
+
from dataclasses import dataclass, field
|
15
|
+
from typing import TYPE_CHECKING
|
16
|
+
|
17
|
+
from modelbase2.model import Derived, Model
|
18
|
+
|
19
|
+
__all__ = ["LinearLabelMapper"]
|
20
|
+
|
21
|
+
if TYPE_CHECKING:
|
22
|
+
from collections.abc import Mapping
|
23
|
+
|
24
|
+
import pandas as pd
|
25
|
+
|
26
|
+
|
27
|
+
def _generate_isotope_labels(base_name: str, num_labels: int) -> list[str]:
|
28
|
+
"""Generate list of isotopomer names for a compound.
|
29
|
+
|
30
|
+
Args:
|
31
|
+
base_name: Base name of the compound
|
32
|
+
num_labels: Number of label positions
|
33
|
+
|
34
|
+
Returns:
|
35
|
+
List of isotopomer names in format base_name__position
|
36
|
+
|
37
|
+
Raises:
|
38
|
+
ValueError: If num_labels <= 0
|
39
|
+
|
40
|
+
Examples:
|
41
|
+
>>> _generate_isotope_labels("x", 2)
|
42
|
+
|
43
|
+
['x__0', 'x__1']
|
44
|
+
|
45
|
+
"""
|
46
|
+
if num_labels > 0:
|
47
|
+
return [f"{base_name}__{i}" for i in range(num_labels)]
|
48
|
+
msg = f"Compound {base_name} must have labels"
|
49
|
+
raise ValueError(msg)
|
50
|
+
|
51
|
+
|
52
|
+
def _unpack_stoichiometries(
|
53
|
+
stoichiometries: Mapping[str, float | Derived],
|
54
|
+
) -> tuple[dict[str, int], dict[str, int]]:
|
55
|
+
"""Split reaction stoichiometry into substrates and products.
|
56
|
+
|
57
|
+
Args:
|
58
|
+
stoichiometries: Dictionary of {species: coefficient} pairs
|
59
|
+
|
60
|
+
Returns:
|
61
|
+
Tuple of (substrates, products) dictionaries with integer coefficients
|
62
|
+
|
63
|
+
Raises:
|
64
|
+
NotImplementedError: If derived quantities are used in stoichiometry
|
65
|
+
|
66
|
+
Examples:
|
67
|
+
>>> _unpack_stoichiometries({"A": -1, "B": 2})
|
68
|
+
({"A": 1}, {"B": 2})
|
69
|
+
|
70
|
+
"""
|
71
|
+
substrates = {}
|
72
|
+
products = {}
|
73
|
+
for k, v in stoichiometries.items():
|
74
|
+
if isinstance(v, Derived):
|
75
|
+
raise NotImplementedError
|
76
|
+
|
77
|
+
if v < 0:
|
78
|
+
substrates[k] = int(-v)
|
79
|
+
else:
|
80
|
+
products[k] = int(v)
|
81
|
+
return substrates, products
|
82
|
+
|
83
|
+
|
84
|
+
def _stoichiometry_to_duplicate_list(stoichiometry: dict[str, int]) -> list[str]:
|
85
|
+
"""Convert stoichiometry dictionary to expanded list of species.
|
86
|
+
|
87
|
+
Args:
|
88
|
+
stoichiometry: Dictionary of {species: coefficient} pairs
|
89
|
+
|
90
|
+
Returns:
|
91
|
+
List with species repeated according to coefficients
|
92
|
+
|
93
|
+
Examples:
|
94
|
+
>>> _stoichiometry_to_duplicate_list({"A": 2, "B": 1})
|
95
|
+
['A', 'A', 'B']
|
96
|
+
|
97
|
+
"""
|
98
|
+
long_form: list[str] = []
|
99
|
+
for k, v in stoichiometry.items():
|
100
|
+
long_form.extend([k] * v)
|
101
|
+
return long_form
|
102
|
+
|
103
|
+
|
104
|
+
def _map_substrates_to_labelmap(
|
105
|
+
substrates: list[str], labelmap: list[int]
|
106
|
+
) -> list[str]:
|
107
|
+
"""Map substrate labels to product label positions.
|
108
|
+
|
109
|
+
Args:
|
110
|
+
substrates: List of substrate names
|
111
|
+
labelmap: List of integers mapping substrate positions to product positions
|
112
|
+
|
113
|
+
Returns:
|
114
|
+
Dictionary mapping substrate names to product label positions
|
115
|
+
|
116
|
+
Examples:
|
117
|
+
>>> _map_substrates_to_labelmap(['A', 'B'], [1, 0])
|
118
|
+
{'A': 1, 'B': 0}
|
119
|
+
|
120
|
+
"""
|
121
|
+
res = ["EXT"] * len(substrates)
|
122
|
+
for substrate, pos in zip(substrates, labelmap, strict=True):
|
123
|
+
res[pos] = substrate
|
124
|
+
return res
|
125
|
+
|
126
|
+
|
127
|
+
def _add_label_influx_or_efflux(
|
128
|
+
substrates: list[str],
|
129
|
+
products: list[str],
|
130
|
+
labelmap: list[int],
|
131
|
+
) -> tuple[list[str], list[str]]:
|
132
|
+
"""Add label influx or efflux to balance substrate and product lists.
|
133
|
+
|
134
|
+
This function ensures that the substrate and product lists have equal length by adding
|
135
|
+
external ("EXT") placeholders where needed. It also validates that the labelmap contains
|
136
|
+
enough labels for all substrates.
|
137
|
+
|
138
|
+
Args:
|
139
|
+
substrates: List of substrate identifiers
|
140
|
+
products: List of product identifiers
|
141
|
+
labelmap: List of integer labels corresponding to substrate positions
|
142
|
+
|
143
|
+
Returns:
|
144
|
+
tuple: A tuple containing:
|
145
|
+
- List of substrates (possibly extended with "EXT")
|
146
|
+
- List of products (possibly extended with "EXT")
|
147
|
+
|
148
|
+
Raises:
|
149
|
+
ValueError: If the labelmap length is less than the number of substrates
|
150
|
+
|
151
|
+
"""
|
152
|
+
# Add label outfluxes
|
153
|
+
if (diff := len(substrates) - len(products)) > 0:
|
154
|
+
products.extend(["EXT"] * diff)
|
155
|
+
|
156
|
+
# Label influxes
|
157
|
+
if (diff := len(products) - len(substrates)) > 0:
|
158
|
+
substrates.extend(["EXT"] * diff)
|
159
|
+
|
160
|
+
# Broken labelmap
|
161
|
+
if (diff := len(labelmap) - len(substrates)) < 0:
|
162
|
+
msg = f"Labelmap 'missing' {abs(diff)} label(s)"
|
163
|
+
raise ValueError(msg)
|
164
|
+
return substrates, products
|
165
|
+
|
166
|
+
|
167
|
+
def _relative_label_flux(label_percentage: float, v_ss: float) -> float:
|
168
|
+
"""Calculate relative label flux based on label percentage and steady-state flux."""
|
169
|
+
return label_percentage * v_ss
|
170
|
+
|
171
|
+
|
172
|
+
def _one_div(y: float) -> float:
|
173
|
+
"""Calculate 1/y."""
|
174
|
+
return 1 / y
|
175
|
+
|
176
|
+
|
177
|
+
def _neg_one_div(y: float) -> float:
|
178
|
+
"""Calculate -1/y."""
|
179
|
+
return -1 / y
|
180
|
+
|
181
|
+
|
182
|
+
@dataclass(slots=True)
|
183
|
+
class LinearLabelMapper:
|
184
|
+
"""A class to map linear labels for a given model and build a model with isotopomers.
|
185
|
+
|
186
|
+
Attributes:
|
187
|
+
model (Model): The model to which the labels are mapped.
|
188
|
+
label_variables (dict[str, int]): A dictionary mapping label names to their respective counts.
|
189
|
+
label_maps (dict[str, list[int]]): A dictionary mapping reaction names to their respective label maps.
|
190
|
+
|
191
|
+
Methods:
|
192
|
+
get_isotopomers(variables: list[str]) -> dict[str, list[str]]:
|
193
|
+
Generates isotopomers for the given variables based on label variables.
|
194
|
+
|
195
|
+
build_model(
|
196
|
+
initial_labels: dict[str, int | list[int]] | None = None
|
197
|
+
Builds and returns a model with the given concentrations, fluxes, external label, and initial labels.
|
198
|
+
|
199
|
+
"""
|
200
|
+
|
201
|
+
model: Model
|
202
|
+
label_variables: dict[str, int] = field(default_factory=dict)
|
203
|
+
label_maps: dict[str, list[int]] = field(default_factory=dict)
|
204
|
+
|
205
|
+
def get_isotopomers(self, variables: list[str]) -> dict[str, list[str]]:
|
206
|
+
"""Generate a dictionary of isotopomers for the given variables.
|
207
|
+
|
208
|
+
This method creates a dictionary where the keys are the variable names
|
209
|
+
provided in the `variables` list, and the values are lists of isotopomer
|
210
|
+
labels generated for each variable.
|
211
|
+
|
212
|
+
Args:
|
213
|
+
variables (list[str]): A list of variable names for which to generate
|
214
|
+
isotopomer labels.
|
215
|
+
|
216
|
+
Returns:
|
217
|
+
dict[str, list[str]]: A dictionary where the keys are the variable names
|
218
|
+
from the `variables` list, and the values are lists
|
219
|
+
of isotopomer labels for each variable.
|
220
|
+
|
221
|
+
"""
|
222
|
+
isotopomers = {
|
223
|
+
name: _generate_isotope_labels(name, num)
|
224
|
+
for name, num in self.label_variables.items()
|
225
|
+
}
|
226
|
+
return {k: isotopomers[k] for k in variables}
|
227
|
+
|
228
|
+
def build_model(
|
229
|
+
self,
|
230
|
+
concs: pd.Series,
|
231
|
+
fluxes: pd.Series,
|
232
|
+
external_label: float = 1.0,
|
233
|
+
initial_labels: dict[str, int | list[int]] | None = None,
|
234
|
+
) -> Model:
|
235
|
+
"""Build a metabolic model with labeled isotopomers and reactions.
|
236
|
+
|
237
|
+
Examples:
|
238
|
+
>>> mapper = LinearLabelMapper(
|
239
|
+
... model,
|
240
|
+
... label_variables={"A": 2, "B": 2},
|
241
|
+
... label_maps={"v1": [0, 1], "v2": [1, 2]},
|
242
|
+
... )
|
243
|
+
>>> mapper.build_model(concs, fluxes)
|
244
|
+
|
245
|
+
Args:
|
246
|
+
concs : pd.Series
|
247
|
+
A pandas Series containing concentration values for metabolites.
|
248
|
+
fluxes : pd.Series
|
249
|
+
A pandas Series containing flux values for reactions.
|
250
|
+
external_label : float, optional
|
251
|
+
The label value for external metabolites, by default 1.0.
|
252
|
+
initial_labels : dict[str, int | list[int]] | None, optional
|
253
|
+
A dictionary specifying initial labeling positions for base compounds.
|
254
|
+
Keys are compound names, and values are either a single integer or a list of integers
|
255
|
+
indicating the positions to be labeled. Default is None.
|
256
|
+
|
257
|
+
"""
|
258
|
+
isotopomers = {
|
259
|
+
name: _generate_isotope_labels(name, num)
|
260
|
+
for name, num in self.label_variables.items()
|
261
|
+
}
|
262
|
+
variables = {k: 0.0 for iso in isotopomers.values() for k in iso}
|
263
|
+
if initial_labels is not None:
|
264
|
+
for base_compound, label_positions in initial_labels.items():
|
265
|
+
if isinstance(label_positions, int):
|
266
|
+
label_positions = [label_positions] # noqa: PLW2901
|
267
|
+
for pos in label_positions:
|
268
|
+
variables[f"{base_compound}__{pos}"] = 1 / len(label_positions)
|
269
|
+
|
270
|
+
m = Model()
|
271
|
+
m.add_variables(variables)
|
272
|
+
m.add_parameters(concs.to_dict() | fluxes.to_dict() | {"EXT": external_label})
|
273
|
+
for rxn_name, label_map in self.label_maps.items():
|
274
|
+
rxn = self.model.reactions[rxn_name]
|
275
|
+
subs, prods = _unpack_stoichiometries(rxn.stoichiometry)
|
276
|
+
|
277
|
+
subs = _stoichiometry_to_duplicate_list(subs)
|
278
|
+
prods = _stoichiometry_to_duplicate_list(prods)
|
279
|
+
subs = [j for i in subs for j in isotopomers[i]]
|
280
|
+
prods = [j for i in prods for j in isotopomers[i]]
|
281
|
+
subs, prods = _add_label_influx_or_efflux(subs, prods, label_map)
|
282
|
+
subs = _map_substrates_to_labelmap(subs, label_map)
|
283
|
+
for i, (substrate, product) in enumerate(zip(subs, prods, strict=True)):
|
284
|
+
if substrate == product:
|
285
|
+
continue
|
286
|
+
|
287
|
+
stoichiometry = {}
|
288
|
+
if substrate != "EXT":
|
289
|
+
stoichiometry[substrate] = Derived(
|
290
|
+
_neg_one_div, [substrate.split("__")[0]]
|
291
|
+
)
|
292
|
+
if product != "EXT":
|
293
|
+
stoichiometry[product] = Derived(_one_div, [product.split("__")[0]])
|
294
|
+
|
295
|
+
m.add_reaction(
|
296
|
+
name=f"{rxn_name}__{i}",
|
297
|
+
fn=_relative_label_flux,
|
298
|
+
stoichiometry=stoichiometry,
|
299
|
+
args=[substrate, rxn_name],
|
300
|
+
)
|
301
|
+
return m
|