modelbase2 0.1.53__tar.gz → 0.1.55__tar.gz
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-0.1.53 → modelbase2-0.1.55}/PKG-INFO +1 -1
- {modelbase2-0.1.53 → modelbase2-0.1.55}/pyproject.toml +4 -1
- {modelbase2-0.1.53 → modelbase2-0.1.55}/setup.py +1 -1
- {modelbase2-0.1.53 → modelbase2-0.1.55}/src/modelbase2/core/algebraic_module_container.py +12 -5
- {modelbase2-0.1.53 → modelbase2-0.1.55}/src/modelbase2/core/constant_container.py +6 -2
- {modelbase2-0.1.53 → modelbase2-0.1.55}/src/modelbase2/core/name_container.py +3 -2
- {modelbase2-0.1.53 → modelbase2-0.1.55}/src/modelbase2/core/reaction_container.py +7 -3
- {modelbase2-0.1.53 → modelbase2-0.1.55}/src/modelbase2/ode/integrator.py +1 -2
- {modelbase2-0.1.53 → modelbase2-0.1.55}/src/modelbase2/ode/mca.py +12 -4
- {modelbase2-0.1.53 → modelbase2-0.1.55}/src/modelbase2/ode/model.py +18 -6
- {modelbase2-0.1.53 → modelbase2-0.1.55}/src/modelbase2/ode/simulator.py +13 -5
- {modelbase2-0.1.53 → modelbase2-0.1.55}/src/modelbase2/utils/plotting.py +11 -5
- {modelbase2-0.1.53 → modelbase2-0.1.55}/LICENSE +0 -0
- {modelbase2-0.1.53 → modelbase2-0.1.55}/src/modelbase2/__init__.py +0 -0
- {modelbase2-0.1.53 → modelbase2-0.1.55}/src/modelbase2/core/__init__.py +0 -0
- {modelbase2-0.1.53 → modelbase2-0.1.55}/src/modelbase2/core/data.py +0 -0
- {modelbase2-0.1.53 → modelbase2-0.1.55}/src/modelbase2/core/utils.py +0 -0
- {modelbase2-0.1.53 → modelbase2-0.1.55}/src/modelbase2/core/variable_container.py +0 -0
- {modelbase2-0.1.53 → modelbase2-0.1.55}/src/modelbase2/ode/__init__.py +1 -1
- {modelbase2-0.1.53 → modelbase2-0.1.55}/src/modelbase2/py.typed +0 -0
- {modelbase2-0.1.53 → modelbase2-0.1.55}/src/modelbase2/types.py +0 -0
- {modelbase2-0.1.53 → modelbase2-0.1.55}/src/modelbase2/utils/__init__.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
[tool.poetry]
|
2
2
|
name = "modelbase2"
|
3
|
-
version = "0.1.
|
3
|
+
version = "0.1.55"
|
4
4
|
description = "A package to build metabolic models"
|
5
5
|
license = "GPL-3.0-or-later"
|
6
6
|
authors = ["Marvin van Aalst <marvin.vanaalst@gmail.com>", "Oliver Ebenhöh <oliver.ebenhoeh@hhu.de>"]
|
@@ -60,6 +60,9 @@ build-backend = "poetry.core.masonry.api"
|
|
60
60
|
line-length = 90
|
61
61
|
target-version = ['py310']
|
62
62
|
|
63
|
+
[tool.bandit]
|
64
|
+
skips = ["B101", "B301", "B403", "B404", "B603", "B607"]
|
65
|
+
|
63
66
|
[tool.isort]
|
64
67
|
profile = "black"
|
65
68
|
src_paths = ["src", "tests"]
|
@@ -1,12 +1,11 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
3
|
import logging
|
4
|
-
|
4
|
+
from ..types import Array
|
5
5
|
from .data import AlgebraicModule, ModuleFunction
|
6
6
|
from dataclasses import dataclass, field
|
7
7
|
from queue import Empty, SimpleQueue
|
8
8
|
from typing import Iterable, Iterator, Optional, cast
|
9
|
-
from ..types import Array
|
10
9
|
|
11
10
|
logger = logging.getLogger(__name__)
|
12
11
|
|
@@ -19,7 +18,9 @@ class AlgebraicModuleContainer:
|
|
19
18
|
def __iter__(self) -> Iterator[AlgebraicModule]:
|
20
19
|
return iter(self.modules.values())
|
21
20
|
|
22
|
-
def _sort_algebraic_modules(
|
21
|
+
def _sort_algebraic_modules(
|
22
|
+
self, available_args: set[str], max_iterations: int = 10_000
|
23
|
+
) -> None:
|
23
24
|
module_order = []
|
24
25
|
modules_to_sort: SimpleQueue[tuple[str, AlgebraicModule]] = SimpleQueue()
|
25
26
|
for k, v in self.modules.items():
|
@@ -49,7 +50,11 @@ class AlgebraicModuleContainer:
|
|
49
50
|
self.module_order = module_order
|
50
51
|
|
51
52
|
def get_derived_variables(self) -> list[str]:
|
52
|
-
return [
|
53
|
+
return [
|
54
|
+
variable
|
55
|
+
for module in self.modules.values()
|
56
|
+
for variable in module.derived_variables
|
57
|
+
]
|
53
58
|
|
54
59
|
def add(
|
55
60
|
self,
|
@@ -116,7 +121,9 @@ class AlgebraicModuleContainer:
|
|
116
121
|
module = self.modules[name]
|
117
122
|
# values = np.array(module.function(*(args[arg] for arg in module.args)), dtype=float)
|
118
123
|
# values = values.reshape((len(module.derived_variables), -1))
|
119
|
-
_values = cast(
|
124
|
+
_values = cast(
|
125
|
+
Iterable[Array], module.function(*(args[arg] for arg in module.args))
|
126
|
+
)
|
120
127
|
values = dict(zip(module.derived_variables, _values))
|
121
128
|
derived_variables |= values
|
122
129
|
args |= values
|
@@ -34,7 +34,9 @@ class ConstantContainer:
|
|
34
34
|
|
35
35
|
def update_basic(self, name: str, value: float, update_derived: bool = True) -> None:
|
36
36
|
if name not in self.constants:
|
37
|
-
raise KeyError(
|
37
|
+
raise KeyError(
|
38
|
+
f"Constant {name} is not in the model. You have to add it first"
|
39
|
+
)
|
38
40
|
self.constants[name].value = value
|
39
41
|
self.values[name] = value
|
40
42
|
if name in self._derived_from_constants and update_derived:
|
@@ -80,7 +82,9 @@ class ConstantContainer:
|
|
80
82
|
def _update_derived_constant_values(self) -> None:
|
81
83
|
for name in self._derived_constant_order:
|
82
84
|
derived_constant = self.derived_constants[name]
|
83
|
-
value = derived_constant.function(
|
85
|
+
value = derived_constant.function(
|
86
|
+
*(self.values[i] for i in derived_constant.args)
|
87
|
+
)
|
84
88
|
self.values[name] = value
|
85
89
|
|
86
90
|
def add_derived(self, constant: DerivedConstant, update_derived: bool = True) -> None:
|
@@ -13,13 +13,14 @@ class NameContainer:
|
|
13
13
|
|
14
14
|
def add(self, name: str, element_type: str) -> None:
|
15
15
|
if (old_type := self.names.get(name)) is not None:
|
16
|
-
raise KeyError(
|
16
|
+
raise KeyError(
|
17
|
+
f"Cannot add {element_type} {name}, as there already exists a {old_type} with that name."
|
18
|
+
)
|
17
19
|
|
18
20
|
logger.info(f"Adding name {name}")
|
19
21
|
self.names[name] = element_type
|
20
22
|
|
21
23
|
def remove(self, name: str) -> None:
|
22
|
-
|
23
24
|
logger.info(f"Removing name {name}")
|
24
25
|
del self.names[name]
|
25
26
|
|
@@ -3,10 +3,10 @@ from __future__ import annotations
|
|
3
3
|
import logging
|
4
4
|
import numpy as np
|
5
5
|
import pandas as pd
|
6
|
+
from ..types import Array
|
6
7
|
from .data import DerivedStoichiometry, RateFunction, Reaction, StoichiometryByVariable
|
7
8
|
from dataclasses import dataclass, field
|
8
9
|
from typing import Optional
|
9
|
-
from ..types import Array
|
10
10
|
|
11
11
|
logger = logging.getLogger()
|
12
12
|
|
@@ -14,7 +14,9 @@ logger = logging.getLogger()
|
|
14
14
|
@dataclass
|
15
15
|
class ReactionContainer:
|
16
16
|
reactions: dict[str, Reaction] = field(default_factory=dict)
|
17
|
-
stoichiometries_by_variables: dict[str, StoichiometryByVariable] = field(
|
17
|
+
stoichiometries_by_variables: dict[str, StoichiometryByVariable] = field(
|
18
|
+
default_factory=dict
|
19
|
+
)
|
18
20
|
|
19
21
|
def set_stoichiometry(self, variable: str, reaction: str, factor: float) -> None:
|
20
22
|
self.stoichiometries_by_variables.setdefault(variable, {})[reaction] = factor
|
@@ -23,7 +25,9 @@ class ReactionContainer:
|
|
23
25
|
for variable, factor in reaction.stoichiometry.items():
|
24
26
|
self.set_stoichiometry(variable, name, factor)
|
25
27
|
|
26
|
-
def update_derived_stoichiometry(
|
28
|
+
def update_derived_stoichiometry(
|
29
|
+
self, name: str, reaction: Reaction, constants: dict[str, float]
|
30
|
+
) -> None:
|
27
31
|
for variable, derived_stoich in reaction.derived_stoichiometry.items():
|
28
32
|
factor = derived_stoich.function(*(constants[i] for i in derived_stoich.args))
|
29
33
|
self.set_stoichiometry(variable, name, factor)
|
@@ -1,13 +1,12 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
3
|
import numpy as np
|
4
|
-
|
4
|
+
from ..types import Array
|
5
5
|
from assimulo.problem import Explicit_Problem # type: ignore
|
6
6
|
from assimulo.solvers import CVode # type: ignore
|
7
7
|
from assimulo.solvers.sundials import CVodeError # type: ignore
|
8
8
|
from dataclasses import dataclass
|
9
9
|
from typing import Callable, Optional, cast
|
10
|
-
from ..types import Array
|
11
10
|
|
12
11
|
|
13
12
|
@dataclass
|
@@ -59,7 +59,9 @@ def get_variable_elasticities(
|
|
59
59
|
Also called epsilon-elasticities. Not in steady state!
|
60
60
|
"""
|
61
61
|
stoichiometries = m.get_stoichiometries().columns
|
62
|
-
elasticities = np.full(
|
62
|
+
elasticities = np.full(
|
63
|
+
shape=(len(variables), len(stoichiometries)), fill_value=np.nan
|
64
|
+
)
|
63
65
|
for i, variable in enumerate(variables):
|
64
66
|
elasticities[i] = get_variable_elasticity(
|
65
67
|
m=m,
|
@@ -105,7 +107,9 @@ def get_constant_elasticities(
|
|
105
107
|
Also called pi-elasticities. Not in steady state!
|
106
108
|
"""
|
107
109
|
stoichiometries = m.get_stoichiometries().columns
|
108
|
-
elasticities = np.full(
|
110
|
+
elasticities = np.full(
|
111
|
+
shape=(len(constants), len(stoichiometries)), fill_value=np.nan
|
112
|
+
)
|
109
113
|
for i, constant in enumerate(constants):
|
110
114
|
elasticities[i] = get_constant_elasticity(
|
111
115
|
m=m,
|
@@ -154,8 +158,12 @@ def _get_response_coefficients_single_constant(
|
|
154
158
|
flux_resp_coef = (fluxes[0] - fluxes[1]) / (2 * displacement * old_value)
|
155
159
|
|
156
160
|
return (
|
157
|
-
pd.Series(conc_resp_coef * y_ss_norm, index=list(y_full.keys())).replace(
|
158
|
-
|
161
|
+
pd.Series(conc_resp_coef * y_ss_norm, index=list(y_full.keys())).replace(
|
162
|
+
[np.inf, -np.inf], np.nan
|
163
|
+
),
|
164
|
+
pd.Series(flux_resp_coef * fluxes_norm, index=list(m.reactions)).replace(
|
165
|
+
[np.inf, -np.inf], np.nan
|
166
|
+
),
|
159
167
|
)
|
160
168
|
|
161
169
|
|
@@ -62,13 +62,17 @@ def _sort_derived_compounds(m: Model, max_iterations: int = 10_000) -> list[str]
|
|
62
62
|
except Empty:
|
63
63
|
break
|
64
64
|
|
65
|
-
raise ValueError(
|
65
|
+
raise ValueError(
|
66
|
+
"Exceeded max iterations on sorting. Check if there are circular references."
|
67
|
+
)
|
66
68
|
return order
|
67
69
|
|
68
70
|
|
69
71
|
@dataclass
|
70
72
|
class Model:
|
71
|
-
_algebraic_modules: AlgebraicModuleContainer = field(
|
73
|
+
_algebraic_modules: AlgebraicModuleContainer = field(
|
74
|
+
default_factory=AlgebraicModuleContainer
|
75
|
+
)
|
72
76
|
_variables: VariableContainer = field(default_factory=VariableContainer)
|
73
77
|
_constants: ConstantContainer = field(default_factory=ConstantContainer)
|
74
78
|
_reactions: ReactionContainer = field(default_factory=ReactionContainer)
|
@@ -310,7 +314,9 @@ class Model:
|
|
310
314
|
derived_variables = old_module.derived_variables
|
311
315
|
if args is None:
|
312
316
|
args = old_module.args
|
313
|
-
self.add_algebraic_module(
|
317
|
+
self.add_algebraic_module(
|
318
|
+
AlgebraicModule(name, function, derived_variables, args)
|
319
|
+
)
|
314
320
|
|
315
321
|
self._algebraic_modules.update(
|
316
322
|
name,
|
@@ -378,7 +384,9 @@ class Model:
|
|
378
384
|
args |= self._algebraic_modules.get_values_float(args)
|
379
385
|
return args
|
380
386
|
|
381
|
-
def get_derived_variables(
|
387
|
+
def get_derived_variables(
|
388
|
+
self, variables: dict[str, float], time: float = 0.0
|
389
|
+
) -> dict[str, float]:
|
382
390
|
args = variables | self.constant_values | {"time": time}
|
383
391
|
return self._algebraic_modules.get_values_float(args)
|
384
392
|
|
@@ -435,7 +443,9 @@ class Model:
|
|
435
443
|
else:
|
436
444
|
raise NotImplementedError(f"Unknown return type {return_type}")
|
437
445
|
|
438
|
-
def get_right_hand_side(
|
446
|
+
def get_right_hand_side(
|
447
|
+
self, variables: dict[str, float], time: float = 0.0
|
448
|
+
) -> dict[str, float]:
|
439
449
|
fluxes = self.get_fluxes(variables, time=time)
|
440
450
|
return self._reactions.get_right_hand_side_float(fluxes)
|
441
451
|
|
@@ -451,7 +461,9 @@ class Model:
|
|
451
461
|
old.add_derived_parameter(k, dc.function, dc.args)
|
452
462
|
|
453
463
|
for k, am in self.algebraic_modules.items():
|
454
|
-
old.add_algebraic_module_from_args(
|
464
|
+
old.add_algebraic_module_from_args(
|
465
|
+
k, am.function, am.derived_variables, am.args
|
466
|
+
)
|
455
467
|
|
456
468
|
for r in self.reactions.values():
|
457
469
|
old.add_reaction_from_args(r.name, r.function, r.stoichiometry, r.args)
|
@@ -3,11 +3,11 @@ from __future__ import annotations
|
|
3
3
|
import logging
|
4
4
|
import numpy as np
|
5
5
|
import pandas as pd
|
6
|
+
from ..types import Array
|
6
7
|
from .integrator import Assimulo
|
7
8
|
from .model import Model
|
8
9
|
from dataclasses import dataclass, field
|
9
10
|
from typing import Any, Optional, Type
|
10
|
-
from ..types import Array
|
11
11
|
|
12
12
|
logger = logging.getLogger()
|
13
13
|
|
@@ -90,8 +90,12 @@ class Simulator:
|
|
90
90
|
self.results.append(res)
|
91
91
|
return res
|
92
92
|
|
93
|
-
def simulate_to_steady_state(
|
94
|
-
|
93
|
+
def simulate_to_steady_state(
|
94
|
+
self, tolerance: float = 1e-6
|
95
|
+
) -> Optional[dict[str, float]]:
|
96
|
+
if (
|
97
|
+
integration := self.integrator.integrate_to_steady_state(tolerance)
|
98
|
+
) is not None:
|
95
99
|
return dict(zip(self.model.stoichiometries, integration))
|
96
100
|
else:
|
97
101
|
logger.warning("Simulation failed")
|
@@ -109,7 +113,9 @@ class Simulator:
|
|
109
113
|
) -> dict[str, Array]:
|
110
114
|
return self.model._algebraic_modules.get_values_array(args)
|
111
115
|
|
112
|
-
def _get_result_all_variables(
|
116
|
+
def _get_result_all_variables(
|
117
|
+
self, result: SimulationResult, constants: dict[str, Array]
|
118
|
+
) -> dict[str, Array]:
|
113
119
|
args = result.values | constants | {"time": result.time}
|
114
120
|
return result.values | self._get_result_derived_variables(args)
|
115
121
|
|
@@ -124,7 +130,9 @@ class Simulator:
|
|
124
130
|
for result in self.results:
|
125
131
|
full_results.append(
|
126
132
|
SimulationResult(
|
127
|
-
values=self._get_result_all_variables(
|
133
|
+
values=self._get_result_all_variables(
|
134
|
+
result, self._get_result_constants(result)
|
135
|
+
),
|
128
136
|
time=result.time,
|
129
137
|
constants=result.constants,
|
130
138
|
)
|
@@ -5,11 +5,11 @@ import math
|
|
5
5
|
import matplotlib.pyplot as plt
|
6
6
|
import numpy as np
|
7
7
|
import pandas as pd
|
8
|
+
from ..types import Axes, Figure
|
8
9
|
from matplotlib.collections import QuadMesh
|
9
10
|
from matplotlib.colors import LogNorm, Normalize, SymLogNorm
|
10
11
|
from matplotlib.colors import colorConverter as _colorConverter
|
11
12
|
from typing import Any, Iterable, Optional, Sized, Union
|
12
|
-
from ..types import Figure, Axes
|
13
13
|
|
14
14
|
|
15
15
|
def _get_plot_kwargs(
|
@@ -191,7 +191,10 @@ def plot_grid(
|
|
191
191
|
"""Plot simulation results as a grid."""
|
192
192
|
if ncols is None:
|
193
193
|
_, ncols = min(
|
194
|
-
(
|
194
|
+
(
|
195
|
+
(math.ceil(len(plot_groups) / i) * i - len(plot_groups), i)
|
196
|
+
for i in range(2, 6)
|
197
|
+
),
|
195
198
|
key=lambda x: (x[0], -x[1]),
|
196
199
|
)
|
197
200
|
nrows = math.ceil(len(plot_groups) / ncols)
|
@@ -278,7 +281,6 @@ def relative_luminance(color: list[float]) -> float:
|
|
278
281
|
|
279
282
|
|
280
283
|
def get_norm(vmin: float, vmax: float) -> plt.Normalize:
|
281
|
-
|
282
284
|
if vmax < 1000 and vmin > -1000:
|
283
285
|
norm = Normalize(vmin=vmin, vmax=vmax)
|
284
286
|
elif vmin <= 0:
|
@@ -348,8 +350,12 @@ def heatmap_from_dataframe(
|
|
348
350
|
text_kwargs = {"ha": "center", "va": "center"}
|
349
351
|
hm.update_scalarmappable() # So that get_facecolor is an array
|
350
352
|
xpos, ypos = np.meshgrid(np.arange(len(columns)), np.arange(len(rows)))
|
351
|
-
for x, y, val, color in zip(
|
352
|
-
|
353
|
+
for x, y, val, color in zip(
|
354
|
+
xpos.flat, ypos.flat, hm.get_array(), hm.get_facecolor()
|
355
|
+
):
|
356
|
+
text_kwargs["color"] = (
|
357
|
+
"black" if relative_luminance(color) > 0.45 else "white"
|
358
|
+
)
|
353
359
|
if sci_annotation_bounds[0] < abs(val) <= sci_annotation_bounds[1]:
|
354
360
|
val_text = f"{val:.{annotation_style}}"
|
355
361
|
else:
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|