mxlpy 0.21.0__py3-none-any.whl → 0.23.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.
- mxlpy/__init__.py +15 -2
- mxlpy/carousel.py +6 -4
- mxlpy/compare.py +4 -8
- mxlpy/experimental/diff.py +1 -1
- mxlpy/fit.py +195 -99
- mxlpy/identify.py +14 -9
- mxlpy/integrators/__init__.py +4 -0
- mxlpy/integrators/int_assimulo.py +3 -3
- mxlpy/integrators/int_diffrax.py +119 -0
- mxlpy/integrators/int_scipy.py +15 -6
- mxlpy/label_map.py +6 -7
- mxlpy/linear_label_map.py +3 -1
- mxlpy/mc.py +25 -22
- mxlpy/mca.py +10 -6
- mxlpy/meta/__init__.py +5 -3
- mxlpy/meta/codegen_latex.py +44 -30
- mxlpy/meta/codegen_model.py +175 -0
- mxlpy/meta/codegen_mxlpy.py +254 -0
- mxlpy/meta/source_tools.py +506 -221
- mxlpy/meta/sympy_tools.py +117 -0
- mxlpy/model.py +758 -257
- mxlpy/plot.py +16 -14
- mxlpy/report.py +153 -90
- mxlpy/sbml/_export.py +22 -11
- mxlpy/sbml/_import.py +68 -547
- mxlpy/scan.py +39 -243
- mxlpy/simulator.py +109 -283
- mxlpy/symbolic/symbolic_model.py +29 -17
- mxlpy/types.py +694 -97
- mxlpy/units.py +133 -0
- {mxlpy-0.21.0.dist-info → mxlpy-0.23.0.dist-info}/METADATA +4 -1
- mxlpy-0.23.0.dist-info/RECORD +57 -0
- mxlpy/meta/codegen_modebase.py +0 -112
- mxlpy/meta/codegen_py.py +0 -115
- mxlpy/sbml/_mathml.py +0 -692
- mxlpy/sbml/_unit_conversion.py +0 -74
- mxlpy-0.21.0.dist-info/RECORD +0 -56
- {mxlpy-0.21.0.dist-info → mxlpy-0.23.0.dist-info}/WHEEL +0 -0
- {mxlpy-0.21.0.dist-info → mxlpy-0.23.0.dist-info}/licenses/LICENSE +0 -0
mxlpy/units.py
ADDED
@@ -0,0 +1,133 @@
|
|
1
|
+
"""Unit definitions for MxlPy."""
|
2
|
+
|
3
|
+
from sympy.physics.units import (
|
4
|
+
ampere,
|
5
|
+
becquerel,
|
6
|
+
candela,
|
7
|
+
coulomb,
|
8
|
+
farad,
|
9
|
+
gram,
|
10
|
+
gray,
|
11
|
+
henry,
|
12
|
+
hertz,
|
13
|
+
hour,
|
14
|
+
joule,
|
15
|
+
katal,
|
16
|
+
kelvin,
|
17
|
+
kilogram,
|
18
|
+
liter,
|
19
|
+
lux,
|
20
|
+
meter,
|
21
|
+
micro,
|
22
|
+
milli,
|
23
|
+
minute,
|
24
|
+
mol,
|
25
|
+
nano,
|
26
|
+
newton,
|
27
|
+
ohm,
|
28
|
+
pascal,
|
29
|
+
pico,
|
30
|
+
radian,
|
31
|
+
second,
|
32
|
+
siemens,
|
33
|
+
steradian,
|
34
|
+
tesla,
|
35
|
+
volt,
|
36
|
+
watt,
|
37
|
+
weber,
|
38
|
+
)
|
39
|
+
from sympy.physics.units.quantities import Quantity
|
40
|
+
|
41
|
+
__all__ = [
|
42
|
+
"ampere",
|
43
|
+
"becquerel",
|
44
|
+
"coulomb",
|
45
|
+
"dimensionless",
|
46
|
+
"farad",
|
47
|
+
"gram",
|
48
|
+
"gray",
|
49
|
+
"henry",
|
50
|
+
"hertz",
|
51
|
+
"hour",
|
52
|
+
"item",
|
53
|
+
"joule",
|
54
|
+
"katal",
|
55
|
+
"kelvin",
|
56
|
+
"liter",
|
57
|
+
"lumen",
|
58
|
+
"lux",
|
59
|
+
"micro",
|
60
|
+
"milli",
|
61
|
+
"minute",
|
62
|
+
"mmol",
|
63
|
+
"mmol_g",
|
64
|
+
"mmol_s",
|
65
|
+
"mol",
|
66
|
+
"nano",
|
67
|
+
"newton",
|
68
|
+
"nmol",
|
69
|
+
"ohm",
|
70
|
+
"pascal",
|
71
|
+
"ppfd",
|
72
|
+
"radian",
|
73
|
+
"second",
|
74
|
+
"siemens",
|
75
|
+
"sievert",
|
76
|
+
"sqm",
|
77
|
+
"tesla",
|
78
|
+
"volt",
|
79
|
+
"watt",
|
80
|
+
"weber",
|
81
|
+
]
|
82
|
+
|
83
|
+
# time unit
|
84
|
+
per_second = 1 / second # type: ignore
|
85
|
+
per_minute = 1 / minute # type: ignore
|
86
|
+
per_hour = 1 / hour # type: ignore
|
87
|
+
|
88
|
+
|
89
|
+
sqm = meter**2
|
90
|
+
cbm = meter**3
|
91
|
+
|
92
|
+
mol_s = mol / second # type: ignore
|
93
|
+
mol_m = mol / minute # type: ignore
|
94
|
+
mol_h = mol / hour # type: ignore
|
95
|
+
mol_g = mol / gram # type: ignore
|
96
|
+
|
97
|
+
mmol = mol * milli
|
98
|
+
mmol_s = mmol / second
|
99
|
+
mmol_m = mmol / minute
|
100
|
+
mmol_h = mmol / hour
|
101
|
+
mmol_g = mmol / gram
|
102
|
+
|
103
|
+
mumol = mol * micro
|
104
|
+
mumol_s = mumol / second
|
105
|
+
mumol_m = mumol / minute
|
106
|
+
mumol_h = mumol / hour
|
107
|
+
mumol_g = mumol / gram
|
108
|
+
|
109
|
+
nmol = mol * nano
|
110
|
+
nmol_s = nmol / second
|
111
|
+
nmol_m = nmol / minute
|
112
|
+
nmol_h = nmol / hour
|
113
|
+
nmol_g = nmol / gram
|
114
|
+
|
115
|
+
pmol = mol * pico
|
116
|
+
pmol_s = pmol / second
|
117
|
+
pmol_m = pmol / minute
|
118
|
+
pmol_h = pmol / hour
|
119
|
+
pmol_g = pmol / gram
|
120
|
+
|
121
|
+
ppfd = mumol / sqm / second
|
122
|
+
|
123
|
+
|
124
|
+
# SBML units
|
125
|
+
avogadro = 6.02214076e23
|
126
|
+
sievert = joule / kilogram # type: ignore
|
127
|
+
lumen = candela * steradian # type: ignore
|
128
|
+
dimensionless = None
|
129
|
+
item = 1 # pseudounit for one thing
|
130
|
+
|
131
|
+
# Plant units
|
132
|
+
mol_chl = Quantity("mol_chl", abbrev="mol_chl")
|
133
|
+
mmol_mol_chl = mmol / mol_chl
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: mxlpy
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.23.0
|
4
4
|
Summary: A package to build metabolic models
|
5
5
|
Author-email: Marvin van Aalst <marvin.vanaalst@gmail.com>
|
6
6
|
Maintainer-email: Marvin van Aalst <marvin.vanaalst@gmail.com>
|
@@ -22,6 +22,7 @@ Classifier: Programming Language :: Python :: 3.11
|
|
22
22
|
Classifier: Topic :: Scientific/Engineering
|
23
23
|
Classifier: Topic :: Software Development
|
24
24
|
Requires-Python: >=3.12
|
25
|
+
Requires-Dist: diffrax>=0.7.0
|
25
26
|
Requires-Dist: dill>=0.3.9
|
26
27
|
Requires-Dist: latexify-py>=0.4.4
|
27
28
|
Requires-Dist: lazy-import>=0.2.2
|
@@ -32,6 +33,7 @@ Requires-Dist: numpy>=2.1.2
|
|
32
33
|
Requires-Dist: pandas>=2.2.3
|
33
34
|
Requires-Dist: parameteriser>=0.1.0
|
34
35
|
Requires-Dist: pebble>=5.0.7
|
36
|
+
Requires-Dist: pysbml>=0.2.0
|
35
37
|
Requires-Dist: python-libsbml>=5.20.4
|
36
38
|
Requires-Dist: salib>=1.5.1
|
37
39
|
Requires-Dist: scipy>=1.14.1
|
@@ -42,6 +44,7 @@ Requires-Dist: tabulate>=0.9.0
|
|
42
44
|
Requires-Dist: toml>=0.10.2
|
43
45
|
Requires-Dist: tqdm>=4.66.6
|
44
46
|
Requires-Dist: typing-extensions>=4.12.2
|
47
|
+
Requires-Dist: wadler-lindig>=0.1.7
|
45
48
|
Provides-Extra: keras
|
46
49
|
Requires-Dist: keras>=3.9.2; extra == 'keras'
|
47
50
|
Provides-Extra: tensorflow
|
@@ -0,0 +1,57 @@
|
|
1
|
+
mxlpy/__init__.py,sha256=XQc1WNOxUzRbM8s7hO4p6r7dFsru0BCcedJITawqIo0,4574
|
2
|
+
mxlpy/carousel.py,sha256=nYWEdxDd7lm5INfZjpwaEhYqysg9e4EH9Ubcl6bPca8,4721
|
3
|
+
mxlpy/compare.py,sha256=o-tUxHJrzzVsQkiKPCFD_4bpqsWrvBMyOPoBQ1Dl420,7746
|
4
|
+
mxlpy/distributions.py,sha256=ce6RTqn19YzMMec-u09fSIUA8A92M6rehCuHuXWcX7A,8734
|
5
|
+
mxlpy/fit.py,sha256=3hGUqJ2tOOToZLMMaJw5M9b6_UlUJwT_MhUvfPmRBd8,22355
|
6
|
+
mxlpy/fns.py,sha256=NLxYwa3ylS7SkISBjw_TgQSKEm7WnkZF9wPigX_ZCAM,13915
|
7
|
+
mxlpy/identify.py,sha256=G-Zyr_l-K2mDtIV1xGrQ52QJxoBYqRoNA5RW6GpbNjs,2213
|
8
|
+
mxlpy/label_map.py,sha256=PwYanfg07hC0ayyOOP72RFlCQNvhCTbpOhW6kZZ2GUU,17826
|
9
|
+
mxlpy/linear_label_map.py,sha256=6Ye6IziWGKkYD_5z3FmTVwnJ2T9SvVGN3U4CfXjXseo,10320
|
10
|
+
mxlpy/mc.py,sha256=6n6VAuSVcXinqcWxNJAioMYpBmSAB40WItPloK1vmBM,17017
|
11
|
+
mxlpy/mca.py,sha256=IoOHJbjPnAEDqKk64OjFjgRPX5K_aE9D4RrCJ1xFIkw,9457
|
12
|
+
mxlpy/model.py,sha256=tAz_Rbe_S1Ho-k_14gBxmE9RMYo2xJUWYkdLYUyMKHg,79072
|
13
|
+
mxlpy/parallel.py,sha256=yLQLw5O4vnPVp_Zmtw1UhPWtB3483foimxQB-TwFKPg,5016
|
14
|
+
mxlpy/parameterise.py,sha256=IgbvfEnkmaqVq_5GgFjHqApGUN9CJrsVD3Fr7pg9iFA,758
|
15
|
+
mxlpy/paths.py,sha256=TK2wO4N9lG-UV1JGfeB64q48JVDbwqIUj63rl55MKuQ,1022
|
16
|
+
mxlpy/plot.py,sha256=oZq8NqycdiJwqEm7FVpNTBkjGYgPBEbqDqZxbrvUtDI,32662
|
17
|
+
mxlpy/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
18
|
+
mxlpy/report.py,sha256=v597yzKecgtoNmlNZ_nVhBCa3czNw0tksOK5mLtAQvE,10631
|
19
|
+
mxlpy/scan.py,sha256=DI5R_xKEUV28FTXqxYz41lQFkAXHE41R_S-ztHPeCoM,13676
|
20
|
+
mxlpy/simulator.py,sha256=NaD0UxvEqiRUp8dZqW4su1Xc9QzHwa9XqGGAIgGV6D0,16559
|
21
|
+
mxlpy/types.py,sha256=FpNZT5ZkhFHYSVA1LS7eZ3ljpzK0_fHo5h48nregR6w,31440
|
22
|
+
mxlpy/units.py,sha256=4bKXkCYsONUVWRdzV7aVcWFQSA6sxilebgXXFUEKw_c,2091
|
23
|
+
mxlpy/experimental/__init__.py,sha256=kZTE-92OErpHzNRqmgSQYH4CGXrogGJ5EL35XGZQ81M,206
|
24
|
+
mxlpy/experimental/diff.py,sha256=g5hKvFsEUdEk5OGQ_aQuCxLAnenD_jG4G__EcVfKsx4,9104
|
25
|
+
mxlpy/integrators/__init__.py,sha256=TKo2dkJqdW3_n7YrmF6k3kEjr8_kr3-7MDaLu-zFWRg,533
|
26
|
+
mxlpy/integrators/int_assimulo.py,sha256=1cvR8cOBdrl_DQs9v0o7wItSG5iyYqwZVh7EO0fg3ro,5021
|
27
|
+
mxlpy/integrators/int_diffrax.py,sha256=q_8NZgIZ6T-SRRcI8kSjEb6l-DbXqPv6rjj9KApkQac,3326
|
28
|
+
mxlpy/integrators/int_scipy.py,sha256=xKyisVN1jW5hxmVD2K_RpoQ2MwNrMxSGODsAEgEt6_I,4922
|
29
|
+
mxlpy/meta/__init__.py,sha256=_Bec5aPJ6YyAkxUXlsQtAy_2VzX0tPGVSj-eGACqrXc,404
|
30
|
+
mxlpy/meta/codegen_latex.py,sha256=i4tPvk2-toAYqtf4TynuE9sfUSHUp21AMUgjgFEB0uo,23215
|
31
|
+
mxlpy/meta/codegen_model.py,sha256=mRpK7pTPYTIMmJAqno7TA6vg5poSQThGWP43_zHU7as,5124
|
32
|
+
mxlpy/meta/codegen_mxlpy.py,sha256=asXGAY0fr5sUSRw7LlBUoFgTkXcJt9M6LZtVqsiF4RU,7869
|
33
|
+
mxlpy/meta/source_tools.py,sha256=dqP2dP8OLloBY8pLPuQl2uVB6GfDDn52u8vhPOTei1M,22302
|
34
|
+
mxlpy/meta/sympy_tools.py,sha256=XaRXdyXiiiOHBXatxoRTRKwy7BaM9I0lL6PE5HP33wE,3016
|
35
|
+
mxlpy/nn/__init__.py,sha256=Qjr-ERsY2lbD75sFBOhCUwEasQDSJKcpBn_kReLZ6oA,633
|
36
|
+
mxlpy/nn/_keras.py,sha256=-5zjHRu8OjSiZeuBSIZFyB63uBsNNH5o9y4kBcPnhx8,2263
|
37
|
+
mxlpy/nn/_torch.py,sha256=GUJmLU282VU4O-fs3Sz90SEaAnfuXN2MPlBr_tHmvn4,5775
|
38
|
+
mxlpy/npe/__init__.py,sha256=hBHCUD2JYDBBGS2kTY8mTCfWB3u1R7m5l--wUupZt6o,1270
|
39
|
+
mxlpy/npe/_keras.py,sha256=ytvXMPK9KUCGOzTQm08_SgafiMb-MOIUdZQV7JjAO40,9721
|
40
|
+
mxlpy/npe/_torch.py,sha256=v3joh6lFJJxvYJj--wzmKXL9UMTaIN3h6hPNq0uX9NU,11250
|
41
|
+
mxlpy/sbml/__init__.py,sha256=Mt97CddpLi3wIrA1b_5cagLmDdNxAVF_S7QV57Pzw8s,226
|
42
|
+
mxlpy/sbml/_data.py,sha256=yYli7ZQ1_pnH9kt5EmcuHM0moQoa43rrFVdrseXlG0o,1136
|
43
|
+
mxlpy/sbml/_export.py,sha256=0R34btB5COKD1gsWisdlWdEbJ-BUrjb78s6sWnm5Ph4,20945
|
44
|
+
mxlpy/sbml/_import.py,sha256=_4MR54YyVkIh9eVAiSMd7yijhHC_ds-3v7M_C4Zn8BY,3565
|
45
|
+
mxlpy/sbml/_name_conversion.py,sha256=93muW47M7qJoE227HKHmThWpPeWsXDN9eM8cRH2pqPs,1340
|
46
|
+
mxlpy/surrogates/__init__.py,sha256=cz9qr0ToYSutIK45IvKrMe1mPP7Lj0I_V0HYGixfpZU,916
|
47
|
+
mxlpy/surrogates/_keras.py,sha256=r2pR3iTJOaMqtATbsCm5CF94TYG9b-9cKljc8kMOplQ,3852
|
48
|
+
mxlpy/surrogates/_poly.py,sha256=z2g3JTdVyQJ8dIiXP4BOun_yMZOrlYpPNvQ0wmFYDTk,3672
|
49
|
+
mxlpy/surrogates/_qss.py,sha256=9w-hPPhdcwybkyaSX0sIfYfvcKu1U5j4HHj4SlgZcYQ,723
|
50
|
+
mxlpy/surrogates/_torch.py,sha256=gU0secuRBYgewhNqZmSo6_Xf804dSzwWwIYmdKA7y60,6389
|
51
|
+
mxlpy/symbolic/__init__.py,sha256=_vM5YM5I6OH0QDbFt9uGYKO8Z5Vly0wbGuvUScVrPRU,258
|
52
|
+
mxlpy/symbolic/strikepy.py,sha256=tzo3uvPpXLDex09hWTuitVzuTNwbgl7jWGjD8g6a8iI,20033
|
53
|
+
mxlpy/symbolic/symbolic_model.py,sha256=cKfWoktvFmXjuo8egE7gXKrKBq2iBUiy_BcIKIvvz8A,3026
|
54
|
+
mxlpy-0.23.0.dist-info/METADATA,sha256=80ODNl3NCffUmuu_mJmvUmld7uNm390vMtlhNx2Soes,4696
|
55
|
+
mxlpy-0.23.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
56
|
+
mxlpy-0.23.0.dist-info/licenses/LICENSE,sha256=lHX9Eu70g3Iv1aOxXTWNHa3vq9vaVYSPQx4jOLYmDpw,1096
|
57
|
+
mxlpy-0.23.0.dist-info/RECORD,,
|
mxlpy/meta/codegen_modebase.py
DELETED
@@ -1,112 +0,0 @@
|
|
1
|
-
"""Generate mxlpy code from a model."""
|
2
|
-
|
3
|
-
from __future__ import annotations
|
4
|
-
|
5
|
-
import warnings
|
6
|
-
from typing import TYPE_CHECKING
|
7
|
-
|
8
|
-
import sympy
|
9
|
-
|
10
|
-
from mxlpy.meta.source_tools import fn_to_sympy, sympy_to_fn
|
11
|
-
from mxlpy.types import Derived
|
12
|
-
|
13
|
-
if TYPE_CHECKING:
|
14
|
-
from mxlpy.model import Model
|
15
|
-
|
16
|
-
__all__ = [
|
17
|
-
"generate_mxlpy_code",
|
18
|
-
]
|
19
|
-
|
20
|
-
|
21
|
-
def _list_of_symbols(args: list[str]) -> list[sympy.Symbol | sympy.Expr]:
|
22
|
-
return [sympy.Symbol(arg) for arg in args]
|
23
|
-
|
24
|
-
|
25
|
-
def generate_mxlpy_code(model: Model) -> str:
|
26
|
-
"""Generate a mxlpy model from a model."""
|
27
|
-
functions = {}
|
28
|
-
|
29
|
-
# Variables and parameters
|
30
|
-
variables = model.variables
|
31
|
-
parameters = model.parameters
|
32
|
-
|
33
|
-
# Derived
|
34
|
-
derived_source = []
|
35
|
-
for k, der in model.derived.items():
|
36
|
-
fn = der.fn
|
37
|
-
fn_name = fn.__name__
|
38
|
-
functions[fn_name] = (
|
39
|
-
fn_to_sympy(fn, model_args=_list_of_symbols(der.args)),
|
40
|
-
der.args,
|
41
|
-
)
|
42
|
-
|
43
|
-
derived_source.append(
|
44
|
-
f""" .add_derived(
|
45
|
-
"{k}",
|
46
|
-
fn={fn_name},
|
47
|
-
args={der.args},
|
48
|
-
)"""
|
49
|
-
)
|
50
|
-
|
51
|
-
# Reactions
|
52
|
-
reactions_source = []
|
53
|
-
for k, rxn in model.reactions.items():
|
54
|
-
fn = rxn.fn
|
55
|
-
fn_name = fn.__name__
|
56
|
-
functions[fn_name] = (
|
57
|
-
fn_to_sympy(fn, model_args=_list_of_symbols(rxn.args)),
|
58
|
-
rxn.args,
|
59
|
-
)
|
60
|
-
stoichiometry: list[str] = []
|
61
|
-
for var, stoich in rxn.stoichiometry.items():
|
62
|
-
if isinstance(stoich, Derived):
|
63
|
-
functions[fn_name] = (
|
64
|
-
fn_to_sympy(fn, model_args=_list_of_symbols(stoich.args)),
|
65
|
-
rxn.args,
|
66
|
-
)
|
67
|
-
args = ", ".join(f'"{k}"' for k in stoich.args)
|
68
|
-
stoich = ( # noqa: PLW2901
|
69
|
-
f"""Derived(fn={fn.__name__}, args=[{args}])"""
|
70
|
-
)
|
71
|
-
stoichiometry.append(f""""{var}": {stoich}""")
|
72
|
-
|
73
|
-
reactions_source.append(
|
74
|
-
f""" .add_reaction(
|
75
|
-
"{k}",
|
76
|
-
fn={fn_name},
|
77
|
-
args={rxn.args},
|
78
|
-
stoichiometry={{{",".join(stoichiometry)}}},
|
79
|
-
)"""
|
80
|
-
)
|
81
|
-
|
82
|
-
# Surrogates
|
83
|
-
if len(model._surrogates) > 0: # noqa: SLF001
|
84
|
-
warnings.warn(
|
85
|
-
"Generating code for Surrogates not yet supported.",
|
86
|
-
stacklevel=1,
|
87
|
-
)
|
88
|
-
|
89
|
-
# Combine all the sources
|
90
|
-
functions_source = "\n\n".join(
|
91
|
-
sympy_to_fn(fn_name=name, args=args, expr=expr)
|
92
|
-
for name, (expr, args) in functions.items()
|
93
|
-
)
|
94
|
-
source = [
|
95
|
-
"from mxlpy import Model\n",
|
96
|
-
functions_source,
|
97
|
-
"def create_model() -> Model:",
|
98
|
-
" return (",
|
99
|
-
" Model()",
|
100
|
-
]
|
101
|
-
if len(parameters) > 0:
|
102
|
-
source.append(f" .add_parameters({parameters})")
|
103
|
-
if len(variables) > 0:
|
104
|
-
source.append(f" .add_variables({variables})")
|
105
|
-
if len(derived_source) > 0:
|
106
|
-
source.append("\n".join(derived_source))
|
107
|
-
if len(reactions_source) > 0:
|
108
|
-
source.append("\n".join(reactions_source))
|
109
|
-
|
110
|
-
source.append(" )")
|
111
|
-
|
112
|
-
return "\n".join(source)
|
mxlpy/meta/codegen_py.py
DELETED
@@ -1,115 +0,0 @@
|
|
1
|
-
"""Module to export models as code."""
|
2
|
-
|
3
|
-
from __future__ import annotations
|
4
|
-
|
5
|
-
import warnings
|
6
|
-
from typing import TYPE_CHECKING
|
7
|
-
|
8
|
-
import sympy
|
9
|
-
|
10
|
-
from mxlpy.meta.source_tools import fn_to_sympy, sympy_to_inline
|
11
|
-
from mxlpy.types import Derived
|
12
|
-
|
13
|
-
if TYPE_CHECKING:
|
14
|
-
from collections.abc import Callable, Generator, Iterable, Iterator
|
15
|
-
|
16
|
-
from mxlpy.model import Model
|
17
|
-
|
18
|
-
__all__ = [
|
19
|
-
"generate_model_code_py",
|
20
|
-
]
|
21
|
-
|
22
|
-
|
23
|
-
def _conditional_join[T](
|
24
|
-
iterable: Iterable[T],
|
25
|
-
question: Callable[[T], bool],
|
26
|
-
true_pat: str,
|
27
|
-
false_pat: str,
|
28
|
-
) -> str:
|
29
|
-
"""Join an iterable, applying a pattern to each element based on a condition."""
|
30
|
-
|
31
|
-
def inner(it: Iterator[T]) -> Generator[str, None, None]:
|
32
|
-
yield str(next(it))
|
33
|
-
while True:
|
34
|
-
try:
|
35
|
-
el = next(it)
|
36
|
-
if question(el):
|
37
|
-
yield f"{true_pat}{el}"
|
38
|
-
else:
|
39
|
-
yield f"{false_pat}{el}"
|
40
|
-
except StopIteration:
|
41
|
-
break
|
42
|
-
|
43
|
-
return "".join(inner(iter(iterable)))
|
44
|
-
|
45
|
-
|
46
|
-
def _list_of_symbols(args: list[str]) -> list[sympy.Symbol | sympy.Expr]:
|
47
|
-
return [sympy.Symbol(arg) for arg in args]
|
48
|
-
|
49
|
-
|
50
|
-
# FIXME: generate from SymbolicModel, should be easier?
|
51
|
-
def generate_model_code_py(model: Model) -> str:
|
52
|
-
"""Transform the model into a single function, inlining the function calls."""
|
53
|
-
source = [
|
54
|
-
"from collections.abc import Iterable\n",
|
55
|
-
"from mxlpy.types import Float\n",
|
56
|
-
"def model(t: Float, variables: Float) -> Iterable[Float]:",
|
57
|
-
]
|
58
|
-
|
59
|
-
# Variables
|
60
|
-
variables = model.variables
|
61
|
-
if len(variables) > 0:
|
62
|
-
source.append(" {} = variables".format(", ".join(variables)))
|
63
|
-
|
64
|
-
# Parameters
|
65
|
-
parameters = model.parameters
|
66
|
-
if len(parameters) > 0:
|
67
|
-
source.append("\n".join(f" {k} = {v}" for k, v in model.parameters.items()))
|
68
|
-
|
69
|
-
# Derived
|
70
|
-
for name, derived in model.derived.items():
|
71
|
-
expr = fn_to_sympy(derived.fn, model_args=_list_of_symbols(derived.args))
|
72
|
-
source.append(f" {name} = {sympy_to_inline(expr)}")
|
73
|
-
|
74
|
-
# Reactions
|
75
|
-
for name, rxn in model.reactions.items():
|
76
|
-
expr = fn_to_sympy(rxn.fn, model_args=_list_of_symbols(rxn.args))
|
77
|
-
source.append(f" {name} = {sympy_to_inline(expr)}")
|
78
|
-
|
79
|
-
# Stoichiometries; FIXME: do this with sympy instead as well?
|
80
|
-
stoich_srcs = {}
|
81
|
-
for rxn_name, rxn in model.reactions.items():
|
82
|
-
for i, (cpd_name, factor) in enumerate(rxn.stoichiometry.items()):
|
83
|
-
if isinstance(factor, Derived):
|
84
|
-
expr = fn_to_sympy(factor.fn, model_args=_list_of_symbols(factor.args))
|
85
|
-
src = f"{sympy_to_inline(expr)} * {rxn_name}"
|
86
|
-
elif factor == 1:
|
87
|
-
src = rxn_name
|
88
|
-
elif factor == -1:
|
89
|
-
src = f"-{rxn_name}" if i == 0 else f"- {rxn_name}"
|
90
|
-
else:
|
91
|
-
src = f"{factor} * {rxn_name}"
|
92
|
-
stoich_srcs.setdefault(cpd_name, []).append(src)
|
93
|
-
for variable, stoich in stoich_srcs.items():
|
94
|
-
source.append(
|
95
|
-
f" d{variable}dt = {_conditional_join(stoich, lambda x: x.startswith('-'), ' ', ' + ')}"
|
96
|
-
)
|
97
|
-
|
98
|
-
# Surrogates
|
99
|
-
if len(model._surrogates) > 0: # noqa: SLF001
|
100
|
-
warnings.warn(
|
101
|
-
"Generating code for Surrogates not yet supported.",
|
102
|
-
stacklevel=1,
|
103
|
-
)
|
104
|
-
|
105
|
-
# Return
|
106
|
-
if len(variables) > 0:
|
107
|
-
source.append(
|
108
|
-
" return {}".format(
|
109
|
-
", ".join(f"d{i}dt" for i in variables),
|
110
|
-
),
|
111
|
-
)
|
112
|
-
else:
|
113
|
-
source.append(" return ()")
|
114
|
-
|
115
|
-
return "\n".join(source)
|