mxlpy 0.8.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.
Files changed (48) hide show
  1. mxlpy/__init__.py +165 -0
  2. mxlpy/distributions.py +339 -0
  3. mxlpy/experimental/__init__.py +12 -0
  4. mxlpy/experimental/diff.py +226 -0
  5. mxlpy/fit.py +291 -0
  6. mxlpy/fns.py +191 -0
  7. mxlpy/integrators/__init__.py +19 -0
  8. mxlpy/integrators/int_assimulo.py +146 -0
  9. mxlpy/integrators/int_scipy.py +146 -0
  10. mxlpy/label_map.py +610 -0
  11. mxlpy/linear_label_map.py +303 -0
  12. mxlpy/mc.py +548 -0
  13. mxlpy/mca.py +280 -0
  14. mxlpy/meta/__init__.py +11 -0
  15. mxlpy/meta/codegen_latex.py +516 -0
  16. mxlpy/meta/codegen_modebase.py +110 -0
  17. mxlpy/meta/codegen_py.py +107 -0
  18. mxlpy/meta/source_tools.py +320 -0
  19. mxlpy/model.py +1737 -0
  20. mxlpy/nn/__init__.py +10 -0
  21. mxlpy/nn/_tensorflow.py +0 -0
  22. mxlpy/nn/_torch.py +129 -0
  23. mxlpy/npe.py +277 -0
  24. mxlpy/parallel.py +171 -0
  25. mxlpy/parameterise.py +27 -0
  26. mxlpy/paths.py +36 -0
  27. mxlpy/plot.py +875 -0
  28. mxlpy/py.typed +0 -0
  29. mxlpy/sbml/__init__.py +14 -0
  30. mxlpy/sbml/_data.py +77 -0
  31. mxlpy/sbml/_export.py +644 -0
  32. mxlpy/sbml/_import.py +599 -0
  33. mxlpy/sbml/_mathml.py +691 -0
  34. mxlpy/sbml/_name_conversion.py +52 -0
  35. mxlpy/sbml/_unit_conversion.py +74 -0
  36. mxlpy/scan.py +629 -0
  37. mxlpy/simulator.py +655 -0
  38. mxlpy/surrogates/__init__.py +31 -0
  39. mxlpy/surrogates/_poly.py +97 -0
  40. mxlpy/surrogates/_torch.py +196 -0
  41. mxlpy/symbolic/__init__.py +10 -0
  42. mxlpy/symbolic/strikepy.py +582 -0
  43. mxlpy/symbolic/symbolic_model.py +75 -0
  44. mxlpy/types.py +474 -0
  45. mxlpy-0.8.0.dist-info/METADATA +106 -0
  46. mxlpy-0.8.0.dist-info/RECORD +48 -0
  47. mxlpy-0.8.0.dist-info/WHEEL +4 -0
  48. mxlpy-0.8.0.dist-info/licenses/LICENSE +674 -0
mxlpy/__init__.py ADDED
@@ -0,0 +1,165 @@
1
+ """mxlpy: 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
+
36
+ from __future__ import annotations
37
+
38
+ import contextlib
39
+ import itertools as it
40
+ from typing import TYPE_CHECKING
41
+
42
+ import pandas as pd
43
+
44
+ from . import (
45
+ distributions,
46
+ experimental,
47
+ fit,
48
+ fns,
49
+ mc,
50
+ mca,
51
+ plot,
52
+ sbml,
53
+ surrogates,
54
+ )
55
+ from .integrators import DefaultIntegrator, Scipy
56
+ from .label_map import LabelMapper
57
+ from .linear_label_map import LinearLabelMapper
58
+ from .mc import Cache
59
+ from .model import Model
60
+ from .nn import _torch
61
+ from .scan import (
62
+ steady_state,
63
+ time_course,
64
+ time_course_over_protocol,
65
+ )
66
+ from .simulator import Simulator
67
+ from .symbolic import SymbolicModel, to_symbolic_model
68
+ from .types import Derived, IntegratorProtocol
69
+
70
+ with contextlib.suppress(ImportError):
71
+ from .integrators import Assimulo
72
+
73
+ if TYPE_CHECKING:
74
+ from mxlpy.types import ArrayLike
75
+
76
+ __all__ = [
77
+ "Assimulo",
78
+ "Cache",
79
+ "DefaultIntegrator",
80
+ "Derived",
81
+ "IntegratorProtocol",
82
+ "LabelMapper",
83
+ "LinearLabelMapper",
84
+ "Model",
85
+ "Scipy",
86
+ "Simulator",
87
+ "SymbolicModel",
88
+ "_torch",
89
+ "cartesian_product",
90
+ "distributions",
91
+ "experimental",
92
+ "fit",
93
+ "fns",
94
+ "make_protocol",
95
+ "mc",
96
+ "mca",
97
+ "plot",
98
+ "sbml",
99
+ "steady_state",
100
+ "surrogates",
101
+ "symbolic",
102
+ "time_course",
103
+ "time_course_over_protocol",
104
+ "to_symbolic_model",
105
+ ]
106
+
107
+
108
+ def cartesian_product(parameters: dict[str, ArrayLike]) -> pd.DataFrame:
109
+ """Generate a cartesian product of the parameter values.
110
+
111
+ Args:
112
+ parameters: Dictionary containing parameter names and values.
113
+
114
+ Returns:
115
+ pd.DataFrame: DataFrame containing the cartesian product of the parameter values.
116
+
117
+ """
118
+ return pd.DataFrame(
119
+ it.product(*parameters.values()),
120
+ columns=list(parameters),
121
+ )
122
+
123
+
124
+ def make_protocol(steps: list[tuple[float, dict[str, float]]]) -> pd.DataFrame:
125
+ """Create protocol DataFrame from a dictionary of steps.
126
+
127
+ Arguments:
128
+ steps: dictionary of steps, where each key is the duration of
129
+ the step in seconds and the value is a dictionary of all
130
+ parameter values during that step.
131
+
132
+ Examples:
133
+ >>> make_protocol([
134
+ ... (1, {"k1": 1.0}),
135
+ ... (2, {"k1": 2.0}),
136
+ ... (3, {"k1": 1.0}),
137
+ ... ])
138
+
139
+ | Timedelta | k1 |
140
+ |:----------------|-----:|
141
+ | 0 days 00:00:01 | 1.0 |
142
+ | 0 days 00:00:03 | 2.0 |
143
+ | 0 days 00:00:06 | 1.0 |
144
+
145
+ >>> make_protocol([
146
+ ... (1, {"k1": 1.0, "k2": 2.0}),
147
+ ... (2, {"k1": 2.0, "k2": 3.0}),
148
+ ... (3, {"k1": 1.0, "k2": 2.0}),
149
+ ... ])
150
+
151
+ | Timedelta | k1 | k2 |
152
+ |:----------------|-----:|-----:|
153
+ | 0 days 00:00:01 | 1 | 2 |
154
+ | 0 days 00:00:03 | 2 | 3 |
155
+ | 0 days 00:00:06 | 1 | 2 |
156
+
157
+ """
158
+ data = {}
159
+ t0 = pd.Timedelta(0)
160
+ for step, pars in steps:
161
+ t0 += pd.Timedelta(seconds=step)
162
+ data[t0] = pars
163
+ protocol = pd.DataFrame(data).T
164
+ protocol.index.name = "Timedelta"
165
+ return protocol
mxlpy/distributions.py ADDED
@@ -0,0 +1,339 @@
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
+ LogUniform: LogUniform distribution for parameters with simple bounds
11
+ Normal: Normal (Gaussian) distribution for unbounded parameters
12
+ LogNormal: Log-normal distribution for strictly positive parameters
13
+ Skewnorm: Skewed normal distribution for asymmetric parameter distributions
14
+
15
+ Each distribution class provides:
16
+ - Consistent interface through the sample() method
17
+ - Optional random number generator (RNG) control
18
+ - Reproducible results via seed parameter
19
+
20
+ """
21
+
22
+ from __future__ import annotations
23
+
24
+ from dataclasses import dataclass
25
+ from typing import Protocol, cast
26
+
27
+ import numpy as np
28
+ import pandas as pd
29
+ from scipy import stats
30
+
31
+ from mxlpy.plot import Axes, FigAx, _default_fig_ax
32
+ from mxlpy.types import Array
33
+
34
+ __all__ = [
35
+ "Beta",
36
+ "Distribution",
37
+ "GaussianKde",
38
+ "LogNormal",
39
+ "LogUniform",
40
+ "Normal",
41
+ "RNG",
42
+ "Skewnorm",
43
+ "Uniform",
44
+ "sample",
45
+ ]
46
+
47
+ RNG = np.random.default_rng(seed=42)
48
+
49
+
50
+ class Distribution(Protocol):
51
+ """Protocol defining interface for distribution classes.
52
+
53
+ All distribution classes must implement the sample() method.
54
+ """
55
+
56
+ def sample(
57
+ self,
58
+ num: int,
59
+ rng: np.random.Generator | None = None,
60
+ ) -> Array:
61
+ """Generate random samples from the distribution.
62
+
63
+ Args:
64
+ num: Number of samples to generate
65
+ rng: Random number generator
66
+
67
+ Returns:
68
+ Array of random samples
69
+
70
+ """
71
+ ...
72
+
73
+
74
+ @dataclass
75
+ class Beta:
76
+ """Beta distribution for parameters bounded between 0 and 1.
77
+
78
+ Args:
79
+ a: Alpha shape parameter (>0)
80
+ b: Beta shape parameter (>0)
81
+
82
+ """
83
+
84
+ a: float
85
+ b: float
86
+
87
+ def sample(self, num: int, rng: np.random.Generator | None = None) -> Array:
88
+ """Generate random samples from the beta distribution.
89
+
90
+ Args:
91
+ num: Number of samples to generate
92
+ rng: Random number generator
93
+
94
+ """
95
+ if rng is None:
96
+ rng = RNG
97
+ return rng.beta(self.a, self.b, num)
98
+
99
+
100
+ @dataclass
101
+ class Uniform:
102
+ """Uniform distribution for parameters with simple bounds.
103
+
104
+ Args:
105
+ lower_bound: Minimum value
106
+ upper_bound: Maximum value
107
+
108
+ """
109
+
110
+ lower_bound: float
111
+ upper_bound: float
112
+
113
+ def sample(self, num: int, rng: np.random.Generator | None = None) -> Array:
114
+ """Generate random samples from the uniform distribution.
115
+
116
+ Args:
117
+ num: Number of samples to generate
118
+ rng: Random number generator
119
+
120
+ """
121
+ if rng is None:
122
+ rng = RNG
123
+ return rng.uniform(self.lower_bound, self.upper_bound, num)
124
+
125
+
126
+ @dataclass
127
+ class LogUniform:
128
+ """LogUniform distribution for parameters with simple bounds.
129
+
130
+ Args:
131
+ lower_bound: Minimum value
132
+ upper_bound: Maximum value
133
+
134
+ """
135
+
136
+ lower_bound: float
137
+ upper_bound: float
138
+
139
+ def sample(self, num: int, rng: np.random.Generator | None = None) -> Array:
140
+ """Generate random samples from the loguniform distribution.
141
+
142
+ Args:
143
+ num: Number of samples to generate
144
+ rng: Random number generator
145
+
146
+ """
147
+ if rng is None:
148
+ rng = RNG
149
+ return cast(
150
+ Array,
151
+ stats.loguniform.rvs(
152
+ self.lower_bound, self.upper_bound, size=num, random_state=rng
153
+ ),
154
+ )
155
+
156
+
157
+ @dataclass
158
+ class Normal:
159
+ """Normal (Gaussian) distribution for unbounded parameters.
160
+
161
+ Args:
162
+ loc: Mean of the distribution
163
+ scale: Standard deviation
164
+
165
+ """
166
+
167
+ loc: float
168
+ scale: float
169
+
170
+ def sample(self, num: int, rng: np.random.Generator | None = None) -> Array:
171
+ """Generate random samples from the normal distribution.
172
+
173
+ Args:
174
+ num: Number of samples to generate
175
+ rng: Random number generator
176
+
177
+ """
178
+ if rng is None:
179
+ rng = RNG
180
+ return rng.normal(self.loc, self.scale, num)
181
+
182
+
183
+ @dataclass
184
+ class LogNormal:
185
+ """Log-normal distribution for strictly positive parameters.
186
+
187
+ Args:
188
+ mean: Mean of the underlying normal distribution
189
+ sigma: Standard deviation of the underlying normal distribution
190
+ seed: Random seed for reproducibility
191
+
192
+ """
193
+
194
+ mean: float
195
+ sigma: float
196
+
197
+ def sample(self, num: int, rng: np.random.Generator | None = None) -> Array:
198
+ """Generate random samples from the log-normal distribution.
199
+
200
+ Args:
201
+ num: Number of samples to generate
202
+ rng: Random number generator
203
+
204
+ """
205
+ if rng is None:
206
+ rng = RNG
207
+ return rng.lognormal(self.mean, self.sigma, num)
208
+
209
+
210
+ @dataclass
211
+ class Skewnorm:
212
+ """Skewed normal distribution for asymmetric parameter distributions.
213
+
214
+ Args:
215
+ loc: Mean of the distribution
216
+ scale: Standard deviation
217
+ a: Skewness parameter
218
+
219
+ """
220
+
221
+ loc: float
222
+ scale: float
223
+ a: float
224
+
225
+ def sample(
226
+ self,
227
+ num: int,
228
+ rng: np.random.Generator | None = None, # noqa: ARG002
229
+ ) -> Array:
230
+ """Generate random samples from the skewed normal distribution.
231
+
232
+ Args:
233
+ num: Number of samples to generate
234
+ rng: The random generator argument is unused but required for API compatibility
235
+
236
+ """
237
+ return cast(
238
+ Array, stats.skewnorm(self.a, loc=self.loc, scale=self.scale).rvs(num)
239
+ )
240
+
241
+
242
+ @dataclass
243
+ class GaussianKde:
244
+ """Representation of a kernel-density estimate using Gaussian kernels.
245
+
246
+ Args:
247
+ mean: Mean of the underlying normal distribution
248
+ sigma: Standard deviation of the underlying normal distribution
249
+ seed: Random seed for reproducibility
250
+
251
+ """
252
+
253
+ kde: stats.gaussian_kde
254
+
255
+ @classmethod
256
+ def from_data(cls, data: Array | pd.Series) -> GaussianKde:
257
+ """Create a GaussianKde object from a data array.
258
+
259
+ Args:
260
+ data: Array of data points
261
+
262
+ """
263
+ return cls(stats.gaussian_kde(data))
264
+
265
+ def plot(
266
+ self,
267
+ xmin: float,
268
+ xmax: float,
269
+ n: int = 1000,
270
+ ax: Axes | None = None,
271
+ ) -> FigAx:
272
+ """Plot the kernel-density estimate."""
273
+ fig, ax = _default_fig_ax(ax=ax, grid=True, figsize=(5, 3))
274
+
275
+ x = np.geomspace(xmin, xmax, n)
276
+ y = self.kde(x)
277
+ ax.set_xlim(xmin, xmax)
278
+ ax.set_xscale("log")
279
+ ax.fill_between(x, y, alpha=0.2)
280
+ ax.plot(x, y)
281
+ ax.grid(visible=True)
282
+ ax.set_frame_on(False)
283
+ return fig, ax
284
+
285
+ def sample(
286
+ self,
287
+ num: int,
288
+ rng: np.random.Generator | None = None, # noqa: ARG002
289
+ ) -> Array:
290
+ """Generate random samples from the kde.
291
+
292
+ Args:
293
+ num: Number of samples to generate
294
+ rng: Random number generator. Unused but required for API compatibility
295
+
296
+ """
297
+ return cast(Array, self.kde.resample(num)[0])
298
+
299
+
300
+ def sample(
301
+ parameters: dict[str, Distribution],
302
+ n: int,
303
+ rng: np.random.Generator | None = None,
304
+ ) -> pd.DataFrame:
305
+ """Generate samples from the specified distributions.
306
+
307
+ Examples:
308
+ >>> sample({"beta": Beta(a=1.0, b=1.0),
309
+ ... "uniform": Uniform(lower_bound=0.0, upper_bound=1.0),
310
+ ... "normal": Normal(loc=1.0, scale=0.1),
311
+ ... "log_normal": LogNormal(mean=1.0, sigma=0.1),
312
+ ... "skewnorm": Skewnorm(loc=1.0, scale=0.1, a=5.0),},
313
+ ... n=2,)
314
+ beta uniform normal log_normal skewnorm
315
+ 0 0.253043 0.682496 1.067891 2.798020 1.216259
316
+ 1 0.573357 0.139752 1.006758 2.895416 1.129373
317
+
318
+ Args:
319
+ parameters: Dictionary mapping parameter names to distribution objects.
320
+ n: Number of samples to generate.
321
+ rng: Random number generator.
322
+
323
+ Returns: DataFrame containing the generated samples.
324
+
325
+ """
326
+ return pd.DataFrame({k: v.sample(n, rng) for k, v in parameters.items()})
327
+
328
+
329
+ if __name__ == "__main__":
330
+ _ = sample(
331
+ {
332
+ "beta": Beta(a=1.0, b=1.0),
333
+ "uniform": Uniform(lower_bound=0.0, upper_bound=1.0),
334
+ "normal": Normal(loc=1.0, scale=0.1),
335
+ "log_normal": LogNormal(mean=1.0, sigma=0.1),
336
+ "skewnorm": Skewnorm(loc=1.0, scale=0.1, a=5.0),
337
+ },
338
+ n=1,
339
+ )
@@ -0,0 +1,12 @@
1
+ """Experimental features for mxlpy.
2
+
3
+ APIs should be considered unstable and may change without notice.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ from .diff import model_diff
9
+
10
+ __all__ = [
11
+ "model_diff",
12
+ ]