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
modelbase2/__init__.py
CHANGED
@@ -1,36 +1,148 @@
|
|
1
|
+
"""modelbase2: A Python Package for Metabolic Modeling and Analysis.
|
2
|
+
|
3
|
+
This package provides tools for creating, simulating and analyzing metabolic models
|
4
|
+
with features including:
|
5
|
+
|
6
|
+
Key Features:
|
7
|
+
- Model creation and manipulation
|
8
|
+
- Steady state and time-series simulations
|
9
|
+
- Parameter fitting and optimization
|
10
|
+
- Monte Carlo analysis
|
11
|
+
- Metabolic Control Analysis (MCA)
|
12
|
+
- SBML import/export support
|
13
|
+
- Visualization tools
|
14
|
+
|
15
|
+
Core Components:
|
16
|
+
Model: Core class for metabolic model representation
|
17
|
+
Simulator: Handles model simulation and integration
|
18
|
+
DefaultIntegrator: Standard ODE solver implementation
|
19
|
+
LabelMapper: Maps between model components and labels
|
20
|
+
Cache: Performance optimization through result caching
|
21
|
+
|
22
|
+
Simulation Features:
|
23
|
+
- Steady state calculations
|
24
|
+
- Time course simulations
|
25
|
+
- Parameter scanning
|
26
|
+
- Surrogate modeling with PyTorch
|
27
|
+
|
28
|
+
Analysis Tools:
|
29
|
+
- Parameter fitting to experimental data
|
30
|
+
- Monte Carlo methods for uncertainty analysis
|
31
|
+
- Metabolic Control Analysis
|
32
|
+
- Custom visualization functions
|
33
|
+
|
34
|
+
"""
|
35
|
+
|
1
36
|
from __future__ import annotations
|
2
37
|
|
3
38
|
__all__ = [
|
4
|
-
"AlgebraicModule",
|
5
39
|
"Assimulo",
|
6
|
-
"
|
7
|
-
"
|
8
|
-
"
|
40
|
+
"Cache",
|
41
|
+
"DefaultIntegrator",
|
42
|
+
"Derived",
|
43
|
+
"IntegratorProtocol",
|
44
|
+
"LabelMapper",
|
45
|
+
"LinearLabelMapper",
|
9
46
|
"Model",
|
10
|
-
"
|
47
|
+
"Scipy",
|
11
48
|
"Simulator",
|
12
|
-
"
|
49
|
+
"cartesian_product",
|
50
|
+
"distributions",
|
51
|
+
"experimental",
|
52
|
+
"fit",
|
53
|
+
"make_protocol",
|
54
|
+
"mc",
|
13
55
|
"mca",
|
56
|
+
"plot",
|
57
|
+
"sbml",
|
58
|
+
"steady_state",
|
59
|
+
"surrogates",
|
60
|
+
"time_course",
|
61
|
+
"time_course_over_protocol",
|
14
62
|
]
|
15
63
|
|
16
|
-
import
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
from .
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
64
|
+
import contextlib
|
65
|
+
import itertools as it
|
66
|
+
from typing import TYPE_CHECKING
|
67
|
+
|
68
|
+
import pandas as pd
|
69
|
+
|
70
|
+
if TYPE_CHECKING:
|
71
|
+
from modelbase2.types import ArrayLike
|
72
|
+
|
73
|
+
from . import distributions, experimental, fit, mc, mca, plot, sbml, surrogates
|
74
|
+
from .integrators import DefaultIntegrator, Scipy
|
75
|
+
from .label_map import LabelMapper
|
76
|
+
from .linear_label_map import LinearLabelMapper
|
77
|
+
from .mc import Cache
|
78
|
+
from .model import Model
|
79
|
+
from .scan import (
|
80
|
+
steady_state,
|
81
|
+
time_course,
|
82
|
+
time_course_over_protocol,
|
33
83
|
)
|
34
|
-
|
35
|
-
|
36
|
-
|
84
|
+
from .simulator import Simulator
|
85
|
+
from .types import Derived, IntegratorProtocol
|
86
|
+
|
87
|
+
with contextlib.suppress(ImportError):
|
88
|
+
from .integrators import Assimulo
|
89
|
+
|
90
|
+
|
91
|
+
def cartesian_product(parameters: dict[str, ArrayLike]) -> pd.DataFrame:
|
92
|
+
"""Generate a cartesian product of the parameter values.
|
93
|
+
|
94
|
+
Args:
|
95
|
+
parameters: Dictionary containing parameter names and values.
|
96
|
+
|
97
|
+
Returns:
|
98
|
+
pd.DataFrame: DataFrame containing the cartesian product of the parameter values.
|
99
|
+
|
100
|
+
"""
|
101
|
+
return pd.DataFrame(
|
102
|
+
it.product(*parameters.values()),
|
103
|
+
columns=list(parameters),
|
104
|
+
)
|
105
|
+
|
106
|
+
|
107
|
+
def make_protocol(steps: list[tuple[float, dict[str, float]]]) -> pd.DataFrame:
|
108
|
+
"""Create protocol DataFrame from a dictionary of steps.
|
109
|
+
|
110
|
+
Arguments:
|
111
|
+
steps: dictionary of steps, where each key is the duration of
|
112
|
+
the step in seconds and the value is a dictionary of all
|
113
|
+
parameter values during that step.
|
114
|
+
|
115
|
+
Examples:
|
116
|
+
>>> make_protocol([
|
117
|
+
... (1, {"k1": 1.0}),
|
118
|
+
... (2, {"k1": 2.0}),
|
119
|
+
... (3, {"k1": 1.0}),
|
120
|
+
... ])
|
121
|
+
|
122
|
+
| Timedelta | k1 |
|
123
|
+
|:----------------|-----:|
|
124
|
+
| 0 days 00:00:01 | 1.0 |
|
125
|
+
| 0 days 00:00:03 | 2.0 |
|
126
|
+
| 0 days 00:00:06 | 1.0 |
|
127
|
+
|
128
|
+
>>> make_protocol([
|
129
|
+
... (1, {"k1": 1.0, "k2": 2.0}),
|
130
|
+
... (2, {"k1": 2.0, "k2": 3.0}),
|
131
|
+
... (3, {"k1": 1.0, "k2": 2.0}),
|
132
|
+
... ])
|
133
|
+
|
134
|
+
| Timedelta | k1 | k2 |
|
135
|
+
|:----------------|-----:|-----:|
|
136
|
+
| 0 days 00:00:01 | 1 | 2 |
|
137
|
+
| 0 days 00:00:03 | 2 | 3 |
|
138
|
+
| 0 days 00:00:06 | 1 | 2 |
|
139
|
+
|
140
|
+
"""
|
141
|
+
data = {}
|
142
|
+
t0 = pd.Timedelta(0)
|
143
|
+
for step, pars in steps:
|
144
|
+
t0 += pd.Timedelta(seconds=step)
|
145
|
+
data[t0] = pars
|
146
|
+
protocol = pd.DataFrame(data).T
|
147
|
+
protocol.index.name = "Timedelta"
|
148
|
+
return protocol
|
@@ -0,0 +1,306 @@
|
|
1
|
+
"""Probability Distribution Classes for Parameter Sampling.
|
2
|
+
|
3
|
+
This module provides a collection of probability distributions used for parameter sampling
|
4
|
+
in metabolic modeling and Monte Carlo simulations.
|
5
|
+
|
6
|
+
Classes:
|
7
|
+
Distribution (Protocol): Base protocol for all distribution classes
|
8
|
+
Beta: Beta distribution for parameters bounded between 0 and 1
|
9
|
+
Uniform: Uniform distribution for parameters with simple bounds
|
10
|
+
Normal: Normal (Gaussian) distribution for unbounded parameters
|
11
|
+
LogNormal: Log-normal distribution for strictly positive parameters
|
12
|
+
Skewnorm: Skewed normal distribution for asymmetric parameter distributions
|
13
|
+
|
14
|
+
Each distribution class provides:
|
15
|
+
- Consistent interface through the sample() method
|
16
|
+
- Optional random number generator (RNG) control
|
17
|
+
- Reproducible results via seed parameter
|
18
|
+
|
19
|
+
"""
|
20
|
+
|
21
|
+
from __future__ import annotations
|
22
|
+
|
23
|
+
from dataclasses import dataclass
|
24
|
+
from typing import Protocol, cast
|
25
|
+
|
26
|
+
import numpy as np
|
27
|
+
import pandas as pd
|
28
|
+
from scipy import stats
|
29
|
+
|
30
|
+
from modelbase2.plot import Axes, FigAx, _default_fig_ax
|
31
|
+
from modelbase2.types import Array
|
32
|
+
|
33
|
+
__all__ = [
|
34
|
+
"Beta",
|
35
|
+
"Distribution",
|
36
|
+
"GaussianKde",
|
37
|
+
"LogNormal",
|
38
|
+
"Normal",
|
39
|
+
"RNG",
|
40
|
+
"Skewnorm",
|
41
|
+
"Uniform",
|
42
|
+
"sample",
|
43
|
+
]
|
44
|
+
|
45
|
+
RNG = np.random.default_rng(seed=42)
|
46
|
+
|
47
|
+
|
48
|
+
class Distribution(Protocol):
|
49
|
+
"""Protocol defining interface for distribution classes.
|
50
|
+
|
51
|
+
All distribution classes must implement the sample() method.
|
52
|
+
"""
|
53
|
+
|
54
|
+
def sample(
|
55
|
+
self,
|
56
|
+
num: int,
|
57
|
+
rng: np.random.Generator | None = None,
|
58
|
+
) -> Array:
|
59
|
+
"""Generate random samples from the distribution.
|
60
|
+
|
61
|
+
Args:
|
62
|
+
num: Number of samples to generate
|
63
|
+
rng: Random number generator
|
64
|
+
|
65
|
+
Returns:
|
66
|
+
Array of random samples
|
67
|
+
|
68
|
+
"""
|
69
|
+
...
|
70
|
+
|
71
|
+
|
72
|
+
@dataclass
|
73
|
+
class Beta:
|
74
|
+
"""Beta distribution for parameters bounded between 0 and 1.
|
75
|
+
|
76
|
+
Args:
|
77
|
+
a: Alpha shape parameter (>0)
|
78
|
+
b: Beta shape parameter (>0)
|
79
|
+
|
80
|
+
"""
|
81
|
+
|
82
|
+
a: float
|
83
|
+
b: float
|
84
|
+
|
85
|
+
def sample(self, num: int, rng: np.random.Generator | None = None) -> Array:
|
86
|
+
"""Generate random samples from the beta distribution.
|
87
|
+
|
88
|
+
Args:
|
89
|
+
num: Number of samples to generate
|
90
|
+
rng: Random number generator
|
91
|
+
|
92
|
+
"""
|
93
|
+
if rng is None:
|
94
|
+
rng = RNG
|
95
|
+
return rng.beta(self.a, self.b, num)
|
96
|
+
|
97
|
+
|
98
|
+
@dataclass
|
99
|
+
class Uniform:
|
100
|
+
"""Uniform distribution for parameters with simple bounds.
|
101
|
+
|
102
|
+
Args:
|
103
|
+
lower_bound: Minimum value
|
104
|
+
upper_bound: Maximum value
|
105
|
+
|
106
|
+
"""
|
107
|
+
|
108
|
+
lower_bound: float
|
109
|
+
upper_bound: float
|
110
|
+
|
111
|
+
def sample(self, num: int, rng: np.random.Generator | None = None) -> Array:
|
112
|
+
"""Generate random samples from the uniform distribution.
|
113
|
+
|
114
|
+
Args:
|
115
|
+
num: Number of samples to generate
|
116
|
+
rng: Random number generator
|
117
|
+
|
118
|
+
"""
|
119
|
+
if rng is None:
|
120
|
+
rng = RNG
|
121
|
+
return rng.uniform(self.lower_bound, self.upper_bound, num)
|
122
|
+
|
123
|
+
|
124
|
+
@dataclass
|
125
|
+
class Normal:
|
126
|
+
"""Normal (Gaussian) distribution for unbounded parameters.
|
127
|
+
|
128
|
+
Args:
|
129
|
+
loc: Mean of the distribution
|
130
|
+
scale: Standard deviation
|
131
|
+
|
132
|
+
"""
|
133
|
+
|
134
|
+
loc: float
|
135
|
+
scale: float
|
136
|
+
|
137
|
+
def sample(self, num: int, rng: np.random.Generator | None = None) -> Array:
|
138
|
+
"""Generate random samples from the normal distribution.
|
139
|
+
|
140
|
+
Args:
|
141
|
+
num: Number of samples to generate
|
142
|
+
rng: Random number generator
|
143
|
+
|
144
|
+
"""
|
145
|
+
if rng is None:
|
146
|
+
rng = RNG
|
147
|
+
return rng.normal(self.loc, self.scale, num)
|
148
|
+
|
149
|
+
|
150
|
+
@dataclass
|
151
|
+
class LogNormal:
|
152
|
+
"""Log-normal distribution for strictly positive parameters.
|
153
|
+
|
154
|
+
Args:
|
155
|
+
mean: Mean of the underlying normal distribution
|
156
|
+
sigma: Standard deviation of the underlying normal distribution
|
157
|
+
seed: Random seed for reproducibility
|
158
|
+
|
159
|
+
"""
|
160
|
+
|
161
|
+
mean: float
|
162
|
+
sigma: float
|
163
|
+
|
164
|
+
def sample(self, num: int, rng: np.random.Generator | None = None) -> Array:
|
165
|
+
"""Generate random samples from the log-normal distribution.
|
166
|
+
|
167
|
+
Args:
|
168
|
+
num: Number of samples to generate
|
169
|
+
rng: Random number generator
|
170
|
+
|
171
|
+
"""
|
172
|
+
if rng is None:
|
173
|
+
rng = RNG
|
174
|
+
return rng.lognormal(self.mean, self.sigma, num)
|
175
|
+
|
176
|
+
|
177
|
+
@dataclass
|
178
|
+
class Skewnorm:
|
179
|
+
"""Skewed normal distribution for asymmetric parameter distributions.
|
180
|
+
|
181
|
+
Args:
|
182
|
+
loc: Mean of the distribution
|
183
|
+
scale: Standard deviation
|
184
|
+
a: Skewness parameter
|
185
|
+
|
186
|
+
"""
|
187
|
+
|
188
|
+
loc: float
|
189
|
+
scale: float
|
190
|
+
a: float
|
191
|
+
|
192
|
+
def sample(
|
193
|
+
self,
|
194
|
+
num: int,
|
195
|
+
rng: np.random.Generator | None = None, # noqa: ARG002
|
196
|
+
) -> Array:
|
197
|
+
"""Generate random samples from the skewed normal distribution.
|
198
|
+
|
199
|
+
Args:
|
200
|
+
num: Number of samples to generate
|
201
|
+
rng: The random generator argument is unused but required for API compatibility
|
202
|
+
|
203
|
+
"""
|
204
|
+
return cast(
|
205
|
+
Array, stats.skewnorm(self.a, loc=self.loc, scale=self.scale).rvs(num)
|
206
|
+
)
|
207
|
+
|
208
|
+
|
209
|
+
@dataclass
|
210
|
+
class GaussianKde:
|
211
|
+
"""Representation of a kernel-density estimate using Gaussian kernels.
|
212
|
+
|
213
|
+
Args:
|
214
|
+
mean: Mean of the underlying normal distribution
|
215
|
+
sigma: Standard deviation of the underlying normal distribution
|
216
|
+
seed: Random seed for reproducibility
|
217
|
+
|
218
|
+
"""
|
219
|
+
|
220
|
+
kde: stats.gaussian_kde
|
221
|
+
|
222
|
+
@classmethod
|
223
|
+
def from_data(cls, data: Array | pd.Series) -> GaussianKde:
|
224
|
+
"""Create a GaussianKde object from a data array.
|
225
|
+
|
226
|
+
Args:
|
227
|
+
data: Array of data points
|
228
|
+
|
229
|
+
"""
|
230
|
+
return cls(stats.gaussian_kde(data))
|
231
|
+
|
232
|
+
def plot(
|
233
|
+
self,
|
234
|
+
xmin: float,
|
235
|
+
xmax: float,
|
236
|
+
n: int = 1000,
|
237
|
+
ax: Axes | None = None,
|
238
|
+
) -> FigAx:
|
239
|
+
"""Plot the kernel-density estimate."""
|
240
|
+
fig, ax = _default_fig_ax(ax=ax, grid=True, figsize=(5, 3))
|
241
|
+
|
242
|
+
x = np.geomspace(xmin, xmax, n)
|
243
|
+
y = self.kde(x)
|
244
|
+
ax.set_xlim(xmin, xmax)
|
245
|
+
ax.set_xscale("log")
|
246
|
+
ax.fill_between(x, y, alpha=0.2)
|
247
|
+
ax.plot(x, y)
|
248
|
+
ax.grid(visible=True)
|
249
|
+
ax.set_frame_on(False)
|
250
|
+
return fig, ax
|
251
|
+
|
252
|
+
def sample(
|
253
|
+
self,
|
254
|
+
num: int,
|
255
|
+
rng: np.random.Generator | None = None, # noqa: ARG002
|
256
|
+
) -> Array:
|
257
|
+
"""Generate random samples from the kde.
|
258
|
+
|
259
|
+
Args:
|
260
|
+
num: Number of samples to generate
|
261
|
+
rng: Random number generator. Unused but required for API compatibility
|
262
|
+
|
263
|
+
"""
|
264
|
+
return cast(Array, self.kde.resample(num)[0])
|
265
|
+
|
266
|
+
|
267
|
+
def sample(
|
268
|
+
parameters: dict[str, Distribution],
|
269
|
+
n: int,
|
270
|
+
rng: np.random.Generator | None = None,
|
271
|
+
) -> pd.DataFrame:
|
272
|
+
"""Generate samples from the specified distributions.
|
273
|
+
|
274
|
+
Examples:
|
275
|
+
>>> sample({"beta": Beta(a=1.0, b=1.0),
|
276
|
+
... "uniform": Uniform(lower_bound=0.0, upper_bound=1.0),
|
277
|
+
... "normal": Normal(loc=1.0, scale=0.1),
|
278
|
+
... "log_normal": LogNormal(mean=1.0, sigma=0.1),
|
279
|
+
... "skewnorm": Skewnorm(loc=1.0, scale=0.1, a=5.0),},
|
280
|
+
... n=2,)
|
281
|
+
beta uniform normal log_normal skewnorm
|
282
|
+
0 0.253043 0.682496 1.067891 2.798020 1.216259
|
283
|
+
1 0.573357 0.139752 1.006758 2.895416 1.129373
|
284
|
+
|
285
|
+
Args:
|
286
|
+
parameters: Dictionary mapping parameter names to distribution objects.
|
287
|
+
n: Number of samples to generate.
|
288
|
+
rng: Random number generator.
|
289
|
+
|
290
|
+
Returns: DataFrame containing the generated samples.
|
291
|
+
|
292
|
+
"""
|
293
|
+
return pd.DataFrame({k: v.sample(n, rng) for k, v in parameters.items()})
|
294
|
+
|
295
|
+
|
296
|
+
if __name__ == "__main__":
|
297
|
+
_ = sample(
|
298
|
+
{
|
299
|
+
"beta": Beta(a=1.0, b=1.0),
|
300
|
+
"uniform": Uniform(lower_bound=0.0, upper_bound=1.0),
|
301
|
+
"normal": Normal(loc=1.0, scale=0.1),
|
302
|
+
"log_normal": LogNormal(mean=1.0, sigma=0.1),
|
303
|
+
"skewnorm": Skewnorm(loc=1.0, scale=0.1, a=5.0),
|
304
|
+
},
|
305
|
+
n=1,
|
306
|
+
)
|
@@ -0,0 +1,17 @@
|
|
1
|
+
"""Experimental features for modelbase2.
|
2
|
+
|
3
|
+
APIs should be considered unstable and may change without notice.
|
4
|
+
"""
|
5
|
+
|
6
|
+
from __future__ import annotations
|
7
|
+
|
8
|
+
from .codegen import generate_model_code_py, generate_modelbase_code
|
9
|
+
from .diff import model_diff
|
10
|
+
from .tex import to_tex
|
11
|
+
|
12
|
+
__all__ = [
|
13
|
+
"generate_model_code_py",
|
14
|
+
"generate_modelbase_code",
|
15
|
+
"model_diff",
|
16
|
+
"to_tex",
|
17
|
+
]
|