modelbase2 0.5.0__py3-none-any.whl → 0.7.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 +2 -0
- modelbase2/experimental/codegen.py +48 -57
- modelbase2/experimental/diff.py +3 -4
- modelbase2/experimental/source_tools.py +30 -0
- modelbase2/experimental/symbolic.py +102 -61
- modelbase2/experimental/tex.py +12 -7
- modelbase2/fit.py +11 -4
- modelbase2/fns.py +6 -0
- modelbase2/integrators/int_assimulo.py +3 -3
- modelbase2/integrators/int_scipy.py +4 -5
- modelbase2/linear_label_map.py +4 -2
- modelbase2/mca.py +6 -6
- modelbase2/model.py +182 -130
- modelbase2/nnarchitectures.py +9 -8
- modelbase2/npe.py +2 -1
- modelbase2/parameterise.py +1 -2
- modelbase2/sbml/_export.py +2 -14
- modelbase2/sbml/_import.py +11 -1
- modelbase2/scan.py +8 -8
- modelbase2/simulator.py +340 -329
- modelbase2/surrogates/_poly.py +6 -2
- modelbase2/surrogates/_torch.py +1 -1
- modelbase2/types.py +129 -16
- {modelbase2-0.5.0.dist-info → modelbase2-0.7.0.dist-info}/METADATA +11 -1
- modelbase2-0.7.0.dist-info/RECORD +44 -0
- modelbase2/experimental/_backup.py +0 -1017
- modelbase2/scope.py +0 -96
- modelbase2/surrogates.py +0 -322
- modelbase2-0.5.0.dist-info/RECORD +0 -46
- {modelbase2-0.5.0.dist-info → modelbase2-0.7.0.dist-info}/WHEEL +0 -0
- {modelbase2-0.5.0.dist-info → modelbase2-0.7.0.dist-info}/licenses/LICENSE +0 -0
modelbase2/__init__.py
CHANGED
@@ -1,10 +1,10 @@
|
|
1
1
|
"""Module to export models as code."""
|
2
2
|
|
3
3
|
import ast
|
4
|
-
import inspect
|
5
4
|
import warnings
|
6
5
|
from collections.abc import Callable, Generator, Iterable, Iterator
|
7
6
|
|
7
|
+
from modelbase2.experimental.source_tools import get_fn_ast, get_fn_source
|
8
8
|
from modelbase2.model import Model
|
9
9
|
from modelbase2.types import Derived
|
10
10
|
|
@@ -15,7 +15,6 @@ __all__ = [
|
|
15
15
|
"conditional_join",
|
16
16
|
"generate_model_code_py",
|
17
17
|
"generate_modelbase_code",
|
18
|
-
"get_fn_source",
|
19
18
|
"handle_fn",
|
20
19
|
]
|
21
20
|
|
@@ -55,28 +54,9 @@ class ReturnRemover(ast.NodeTransformer):
|
|
55
54
|
return node.value
|
56
55
|
|
57
56
|
|
58
|
-
def get_fn_source(fn: Callable) -> ast.FunctionDef:
|
59
|
-
"""Get the source code of a function as an AST."""
|
60
|
-
import inspect
|
61
|
-
import textwrap
|
62
|
-
|
63
|
-
import dill
|
64
|
-
|
65
|
-
try:
|
66
|
-
source = inspect.getsource(fn)
|
67
|
-
except OSError: # could not get source code
|
68
|
-
source = dill.source.getsource(fn)
|
69
|
-
|
70
|
-
tree = ast.parse(textwrap.dedent(source))
|
71
|
-
if not isinstance(fn_def := tree.body[0], ast.FunctionDef):
|
72
|
-
msg = "Not a function"
|
73
|
-
raise TypeError(msg)
|
74
|
-
return fn_def
|
75
|
-
|
76
|
-
|
77
57
|
def handle_fn(fn: Callable, args: list[str]) -> str:
|
78
58
|
"""Get the source code of a function, removing docstrings and return statements."""
|
79
|
-
tree =
|
59
|
+
tree = get_fn_ast(fn)
|
80
60
|
|
81
61
|
argmap = dict(zip([i.arg for i in tree.args.args], args, strict=True))
|
82
62
|
tree = DocstringRemover().visit(tree)
|
@@ -118,31 +98,41 @@ def generate_modelbase_code(model: Model) -> str:
|
|
118
98
|
|
119
99
|
# Derived
|
120
100
|
derived_source = []
|
121
|
-
for k,
|
122
|
-
fn =
|
101
|
+
for k, rxn in model.derived.items():
|
102
|
+
fn = rxn.fn
|
123
103
|
fn_name = fn.__name__
|
124
|
-
functions[fn_name] =
|
104
|
+
functions[fn_name] = get_fn_source(fn)
|
125
105
|
|
126
106
|
derived_source.append(
|
127
|
-
f""".add_derived(
|
107
|
+
f""" .add_derived(
|
128
108
|
"{k}",
|
129
109
|
fn={fn_name},
|
130
|
-
args={
|
110
|
+
args={rxn.args},
|
131
111
|
)"""
|
132
112
|
)
|
133
113
|
|
134
114
|
# Reactions
|
135
115
|
reactions_source = []
|
136
|
-
for k,
|
137
|
-
fn =
|
116
|
+
for k, rxn in model.reactions.items():
|
117
|
+
fn = rxn.fn
|
138
118
|
fn_name = fn.__name__
|
139
|
-
functions[fn_name] =
|
119
|
+
functions[fn_name] = get_fn_source(fn)
|
120
|
+
stoichiometry: list[str] = []
|
121
|
+
for var, stoich in rxn.stoichiometry.items():
|
122
|
+
if isinstance(stoich, Derived):
|
123
|
+
functions[fn_name] = get_fn_source(fn)
|
124
|
+
args = ", ".join(f'"{k}"' for k in stoich.args)
|
125
|
+
stoich = ( # noqa: PLW2901
|
126
|
+
f"""Derived(name="{var}", fn={fn.__name__}, args=[{args}])"""
|
127
|
+
)
|
128
|
+
stoichiometry.append(f""""{var}": {stoich}""")
|
129
|
+
|
140
130
|
reactions_source.append(
|
141
131
|
f""" .add_reaction(
|
142
132
|
"{k}",
|
143
133
|
fn={fn_name},
|
144
|
-
args={
|
145
|
-
stoichiometry={
|
134
|
+
args={rxn.args},
|
135
|
+
stoichiometry={{{",".join(stoichiometry)}}},
|
146
136
|
)"""
|
147
137
|
)
|
148
138
|
|
@@ -178,25 +168,32 @@ def generate_modelbase_code(model: Model) -> str:
|
|
178
168
|
|
179
169
|
def generate_model_code_py(model: Model) -> str:
|
180
170
|
"""Transform the model into a single function, inlining the function calls."""
|
171
|
+
source = [
|
172
|
+
"from collections.abc import Iterable\n",
|
173
|
+
"from modelbase2.types import Float\n",
|
174
|
+
"def model(t: Float, y: Float) -> Iterable[Float]:",
|
175
|
+
]
|
176
|
+
|
181
177
|
# Variables
|
182
178
|
variables = model.variables
|
183
|
-
|
179
|
+
if len(variables) > 0:
|
180
|
+
source.append(" {} = y".format(", ".join(variables)))
|
184
181
|
|
185
182
|
# Parameters
|
186
|
-
|
183
|
+
parameters = model.parameters
|
184
|
+
if len(parameters) > 0:
|
185
|
+
source.append("\n".join(f" {k} = {v}" for k, v in model.parameters.items()))
|
187
186
|
|
188
187
|
# Derived
|
189
|
-
derived_source = []
|
190
188
|
for name, derived in model.derived.items():
|
191
|
-
|
189
|
+
source.append(f" {name} = {handle_fn(derived.fn, derived.args)}")
|
192
190
|
|
193
191
|
# Reactions
|
194
|
-
reactions = []
|
195
192
|
for name, rxn in model.reactions.items():
|
196
|
-
|
193
|
+
source.append(f" {name} = {handle_fn(rxn.fn, rxn.args)}")
|
197
194
|
|
198
195
|
# Stoichiometries
|
199
|
-
|
196
|
+
stoich_srcs = {}
|
200
197
|
for rxn_name, rxn in model.reactions.items():
|
201
198
|
for cpd_name, factor in rxn.stoichiometry.items():
|
202
199
|
if isinstance(factor, Derived):
|
@@ -207,10 +204,9 @@ def generate_model_code_py(model: Model) -> str:
|
|
207
204
|
src = f"- {rxn_name}"
|
208
205
|
else:
|
209
206
|
src = f"{factor} * {rxn_name}"
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
stoich_source.append(
|
207
|
+
stoich_srcs.setdefault(cpd_name, []).append(src)
|
208
|
+
for variable, stoich in stoich_srcs.items():
|
209
|
+
source.append(
|
214
210
|
f" d{variable}dt = {conditional_join(stoich, lambda x: x.startswith('-'), ' ', ' + ')}"
|
215
211
|
)
|
216
212
|
|
@@ -221,19 +217,14 @@ def generate_model_code_py(model: Model) -> str:
|
|
221
217
|
stacklevel=1,
|
222
218
|
)
|
223
219
|
|
224
|
-
#
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
*stoich_source,
|
234
|
-
" return {}".format(
|
235
|
-
", ".join(f"d{i}dt" for i in variables),
|
236
|
-
),
|
237
|
-
]
|
220
|
+
# Return
|
221
|
+
if len(variables) > 0:
|
222
|
+
source.append(
|
223
|
+
" return {}".format(
|
224
|
+
", ".join(f"d{i}dt" for i in variables),
|
225
|
+
),
|
226
|
+
)
|
227
|
+
else:
|
228
|
+
source.append(" return ()")
|
238
229
|
|
239
230
|
return "\n".join(source)
|
modelbase2/experimental/diff.py
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
"""Diffing utilities for comparing models."""
|
2
2
|
|
3
|
+
from collections.abc import Mapping
|
3
4
|
from dataclasses import dataclass, field
|
4
|
-
from typing import Mapping
|
5
5
|
|
6
6
|
from modelbase2.model import Model
|
7
7
|
from modelbase2.types import Derived
|
@@ -128,9 +128,8 @@ def _soft_eq_stoichiometries(
|
|
128
128
|
return False
|
129
129
|
if v1.args != v2.args:
|
130
130
|
return False
|
131
|
-
|
132
|
-
|
133
|
-
return False
|
131
|
+
elif v1 != v2:
|
132
|
+
return False
|
134
133
|
|
135
134
|
return True
|
136
135
|
|
@@ -0,0 +1,30 @@
|
|
1
|
+
"""Tools for working with python source files."""
|
2
|
+
|
3
|
+
import ast
|
4
|
+
import inspect
|
5
|
+
import textwrap
|
6
|
+
from collections.abc import Callable
|
7
|
+
|
8
|
+
import dill
|
9
|
+
|
10
|
+
__all__ = [
|
11
|
+
"get_fn_ast",
|
12
|
+
"get_fn_source",
|
13
|
+
]
|
14
|
+
|
15
|
+
|
16
|
+
def get_fn_source(fn: Callable) -> str:
|
17
|
+
"""Get the string representation of a function."""
|
18
|
+
try:
|
19
|
+
return inspect.getsource(fn)
|
20
|
+
except OSError: # could not get source code
|
21
|
+
return dill.source.getsource(fn)
|
22
|
+
|
23
|
+
|
24
|
+
def get_fn_ast(fn: Callable) -> ast.FunctionDef:
|
25
|
+
"""Get the source code of a function as an AST."""
|
26
|
+
tree = ast.parse(textwrap.dedent(get_fn_source(fn)))
|
27
|
+
if not isinstance(fn_def := tree.body[0], ast.FunctionDef):
|
28
|
+
msg = "Not a function"
|
29
|
+
raise TypeError(msg)
|
30
|
+
return fn_def
|
@@ -2,22 +2,43 @@
|
|
2
2
|
|
3
3
|
import ast
|
4
4
|
import inspect
|
5
|
-
import textwrap
|
6
5
|
from collections.abc import Callable
|
7
6
|
from dataclasses import dataclass
|
7
|
+
from types import ModuleType
|
8
8
|
from typing import Any, cast
|
9
9
|
|
10
10
|
import sympy
|
11
11
|
|
12
|
+
from modelbase2.experimental.source_tools import get_fn_ast
|
12
13
|
from modelbase2.model import Model
|
13
14
|
|
14
|
-
__all__ = [
|
15
|
+
__all__ = [
|
16
|
+
"Context",
|
17
|
+
"SymbolicModel",
|
18
|
+
"model_fn_to_sympy",
|
19
|
+
"to_symbolic_model",
|
20
|
+
]
|
15
21
|
|
16
22
|
|
17
23
|
@dataclass
|
18
24
|
class Context:
|
19
25
|
symbols: dict[str, sympy.Symbol | sympy.Expr]
|
20
26
|
caller: Callable
|
27
|
+
parent_module: ModuleType | None
|
28
|
+
|
29
|
+
def updated(
|
30
|
+
self,
|
31
|
+
symbols: dict[str, sympy.Symbol | sympy.Expr] | None = None,
|
32
|
+
caller: Callable | None = None,
|
33
|
+
parent_module: ModuleType | None = None,
|
34
|
+
) -> "Context":
|
35
|
+
return Context(
|
36
|
+
symbols=self.symbols if symbols is None else symbols,
|
37
|
+
caller=self.caller if caller is None else caller,
|
38
|
+
parent_module=self.parent_module
|
39
|
+
if parent_module is None
|
40
|
+
else parent_module,
|
41
|
+
)
|
21
42
|
|
22
43
|
|
23
44
|
@dataclass
|
@@ -27,68 +48,21 @@ class SymbolicModel:
|
|
27
48
|
eqs: list[sympy.Expr]
|
28
49
|
|
29
50
|
|
30
|
-
def to_symbolic_model(model: Model) -> SymbolicModel:
|
31
|
-
cache = model._create_cache() # noqa: SLF001
|
32
|
-
|
33
|
-
variables = dict(
|
34
|
-
zip(model.variables, sympy.symbols(list(model.variables)), strict=True)
|
35
|
-
)
|
36
|
-
parameters = dict(
|
37
|
-
zip(model.parameters, sympy.symbols(list(model.parameters)), strict=True)
|
38
|
-
)
|
39
|
-
symbols = variables | parameters
|
40
|
-
|
41
|
-
for k, v in model.derived.items():
|
42
|
-
symbols[k] = model_fn_to_sympy(v.fn, [symbols[i] for i in v.args])
|
43
|
-
|
44
|
-
rxns = {
|
45
|
-
k: model_fn_to_sympy(v.fn, [symbols[i] for i in v.args])
|
46
|
-
for k, v in model.reactions.items()
|
47
|
-
}
|
48
|
-
|
49
|
-
eqs: dict[str, sympy.Expr] = {}
|
50
|
-
for cpd, stoich in cache.stoich_by_cpds.items():
|
51
|
-
for rxn, stoich_value in stoich.items():
|
52
|
-
eqs[cpd] = (
|
53
|
-
eqs.get(cpd, sympy.Float(0.0)) + sympy.Float(stoich_value) * rxns[rxn] # type: ignore
|
54
|
-
)
|
55
|
-
|
56
|
-
for cpd, dstoich in cache.dyn_stoich_by_cpds.items():
|
57
|
-
for rxn, der in dstoich.items():
|
58
|
-
eqs[cpd] = eqs.get(cpd, sympy.Float(0.0)) + model_fn_to_sympy(
|
59
|
-
der.fn,
|
60
|
-
[symbols[i] for i in der.args] * rxns[rxn], # type: ignore
|
61
|
-
) # type: ignore
|
62
|
-
|
63
|
-
return SymbolicModel(
|
64
|
-
variables=variables,
|
65
|
-
parameters=parameters,
|
66
|
-
eqs=[eqs[i] for i in cache.var_names],
|
67
|
-
)
|
68
|
-
|
69
|
-
|
70
51
|
def model_fn_to_sympy(
|
71
52
|
fn: Callable, model_args: list[sympy.Symbol | sympy.Expr] | None = None
|
72
53
|
) -> sympy.Expr:
|
73
|
-
|
74
|
-
|
75
|
-
if not isinstance(fn_def := ast.parse(source).body[0], ast.FunctionDef):
|
76
|
-
msg = "Expected a function definition"
|
77
|
-
raise TypeError(msg)
|
78
|
-
|
54
|
+
fn_def = get_fn_ast(fn)
|
79
55
|
fn_args = [str(arg.arg) for arg in fn_def.args.args]
|
80
|
-
|
81
56
|
sympy_expr = _handle_fn_body(
|
82
57
|
fn_def.body,
|
83
58
|
ctx=Context(
|
84
59
|
symbols={name: sympy.Symbol(name) for name in fn_args},
|
85
60
|
caller=fn,
|
61
|
+
parent_module=inspect.getmodule(fn),
|
86
62
|
),
|
87
63
|
)
|
88
|
-
|
89
64
|
if model_args is not None:
|
90
65
|
sympy_expr = sympy_expr.subs(dict(zip(fn_args, model_args, strict=True)))
|
91
|
-
|
92
66
|
return cast(sympy.Expr, sympy_expr)
|
93
67
|
|
94
68
|
|
@@ -215,18 +189,45 @@ def _handle_binop(node: ast.BinOp, ctx: Context) -> sympy.Expr:
|
|
215
189
|
|
216
190
|
|
217
191
|
def _handle_call(node: ast.Call, ctx: Context) -> sympy.Expr:
|
218
|
-
|
219
|
-
|
220
|
-
|
192
|
+
# direct call, e.g. mass_action(x, k1)
|
193
|
+
if isinstance(callee := node.func, ast.Name):
|
194
|
+
fn_name = str(callee.id)
|
195
|
+
fns = dict(inspect.getmembers(ctx.parent_module, predicate=callable))
|
196
|
+
|
197
|
+
return model_fn_to_sympy(
|
198
|
+
fns[fn_name],
|
199
|
+
model_args=[_handle_expr(i, ctx) for i in node.args],
|
200
|
+
)
|
201
|
+
|
202
|
+
# search for fn in other namespace
|
203
|
+
if isinstance(attr := node.func, ast.Attribute):
|
204
|
+
imports = dict(inspect.getmembers(ctx.parent_module, inspect.ismodule))
|
205
|
+
|
206
|
+
# Single level, e.g. fns.mass_action(x, k1)
|
207
|
+
if isinstance(module_name := attr.value, ast.Name):
|
208
|
+
return _handle_call(
|
209
|
+
ast.Call(func=ast.Name(attr.attr), args=node.args),
|
210
|
+
ctx=ctx.updated(parent_module=imports[module_name.id]),
|
211
|
+
)
|
221
212
|
|
222
|
-
|
223
|
-
|
224
|
-
|
213
|
+
# Multiple levels, e.g. modelbase2.fns.mass_action(x, k1)
|
214
|
+
if isinstance(inner_attr := attr.value, ast.Attribute):
|
215
|
+
if not isinstance(module_name := inner_attr.value, ast.Name):
|
216
|
+
msg = f"Unknown target kind {module_name}"
|
217
|
+
raise NotImplementedError(msg)
|
218
|
+
return _handle_call(
|
219
|
+
ast.Call(
|
220
|
+
func=ast.Attribute(
|
221
|
+
value=ast.Name(inner_attr.attr),
|
222
|
+
attr=attr.attr,
|
223
|
+
),
|
224
|
+
args=node.args,
|
225
|
+
),
|
226
|
+
ctx=ctx.updated(parent_module=imports[module_name.id]),
|
227
|
+
)
|
225
228
|
|
226
|
-
|
227
|
-
|
228
|
-
model_args=[_handle_expr(i, ctx) for i in node.args],
|
229
|
-
)
|
229
|
+
msg = f"Onsupported function type {node.func}"
|
230
|
+
raise NotImplementedError(msg)
|
230
231
|
|
231
232
|
|
232
233
|
def _handle_name(node: ast.Name, ctx: Context) -> sympy.Symbol | sympy.Expr:
|
@@ -284,3 +285,43 @@ def _handle_expr(node: ast.expr, ctx: Context) -> sympy.Expr:
|
|
284
285
|
|
285
286
|
msg = f"Expression type {type(node).__name__} not implemented"
|
286
287
|
raise NotImplementedError(msg)
|
288
|
+
|
289
|
+
|
290
|
+
def to_symbolic_model(model: Model) -> SymbolicModel:
|
291
|
+
cache = model._create_cache() # noqa: SLF001
|
292
|
+
|
293
|
+
variables = dict(
|
294
|
+
zip(model.variables, sympy.symbols(list(model.variables)), strict=True)
|
295
|
+
)
|
296
|
+
parameters = dict(
|
297
|
+
zip(model.parameters, sympy.symbols(list(model.parameters)), strict=True)
|
298
|
+
)
|
299
|
+
symbols = variables | parameters
|
300
|
+
|
301
|
+
for k, v in model.derived.items():
|
302
|
+
symbols[k] = model_fn_to_sympy(v.fn, [symbols[i] for i in v.args])
|
303
|
+
|
304
|
+
rxns = {
|
305
|
+
k: model_fn_to_sympy(v.fn, [symbols[i] for i in v.args])
|
306
|
+
for k, v in model.reactions.items()
|
307
|
+
}
|
308
|
+
|
309
|
+
eqs: dict[str, sympy.Expr] = {}
|
310
|
+
for cpd, stoich in cache.stoich_by_cpds.items():
|
311
|
+
for rxn, stoich_value in stoich.items():
|
312
|
+
eqs[cpd] = (
|
313
|
+
eqs.get(cpd, sympy.Float(0.0)) + sympy.Float(stoich_value) * rxns[rxn] # type: ignore
|
314
|
+
)
|
315
|
+
|
316
|
+
for cpd, dstoich in cache.dyn_stoich_by_cpds.items():
|
317
|
+
for rxn, der in dstoich.items():
|
318
|
+
eqs[cpd] = eqs.get(cpd, sympy.Float(0.0)) + model_fn_to_sympy(
|
319
|
+
der.fn,
|
320
|
+
[symbols[i] for i in der.args] * rxns[rxn], # type: ignore
|
321
|
+
) # type: ignore
|
322
|
+
|
323
|
+
return SymbolicModel(
|
324
|
+
variables=variables,
|
325
|
+
parameters=parameters,
|
326
|
+
eqs=[eqs[i] for i in cache.var_names],
|
327
|
+
)
|
modelbase2/experimental/tex.py
CHANGED
@@ -2,16 +2,21 @@
|
|
2
2
|
|
3
3
|
from __future__ import annotations
|
4
4
|
|
5
|
-
import ast
|
6
|
-
import inspect
|
7
5
|
from dataclasses import dataclass
|
8
6
|
from typing import TYPE_CHECKING, cast
|
9
7
|
|
10
8
|
import latexify
|
11
9
|
|
10
|
+
from modelbase2.experimental.source_tools import get_fn_ast
|
12
11
|
from modelbase2.types import Derived, RateFn
|
13
12
|
|
14
|
-
__all__ = [
|
13
|
+
__all__ = [
|
14
|
+
"TexExport",
|
15
|
+
"TexReaction",
|
16
|
+
"default_init",
|
17
|
+
"get_model_tex_diff",
|
18
|
+
"to_tex",
|
19
|
+
]
|
15
20
|
|
16
21
|
if TYPE_CHECKING:
|
17
22
|
from collections.abc import Callable, Mapping
|
@@ -67,9 +72,7 @@ def _escape_non_math(s: str) -> str:
|
|
67
72
|
|
68
73
|
|
69
74
|
def _fn_to_latex(fn: Callable, arg_names: list[str]) -> str:
|
70
|
-
|
71
|
-
src = cast(ast.Module, ast.parse(code))
|
72
|
-
fn_def = cast(ast.FunctionDef, src.body[0])
|
75
|
+
fn_def = get_fn_ast(fn)
|
73
76
|
args: list[str] = [i.arg for i in fn_def.args.args]
|
74
77
|
arg_mapping: dict[str, str] = dict(zip(args, arg_names, strict=True))
|
75
78
|
return cast(
|
@@ -329,7 +332,9 @@ class TexExport:
|
|
329
332
|
parameters={gls.get(k, k): v for k, v in self.parameters.items()},
|
330
333
|
variables={gls.get(k, k): v for k, v in self.variables.items()},
|
331
334
|
derived={
|
332
|
-
gls.get(k, k): Derived(
|
335
|
+
gls.get(k, k): Derived(
|
336
|
+
name=k, fn=v.fn, args=[gls.get(i, i) for i in v.args]
|
337
|
+
)
|
333
338
|
for k, v in self.derived.items()
|
334
339
|
},
|
335
340
|
reactions={
|
modelbase2/fit.py
CHANGED
@@ -19,7 +19,14 @@ from scipy.optimize import minimize
|
|
19
19
|
|
20
20
|
from modelbase2.integrators import DefaultIntegrator
|
21
21
|
from modelbase2.simulator import Simulator
|
22
|
-
from modelbase2.types import
|
22
|
+
from modelbase2.types import (
|
23
|
+
Array,
|
24
|
+
ArrayLike,
|
25
|
+
Callable,
|
26
|
+
IntegratorProtocol,
|
27
|
+
cast,
|
28
|
+
unwrap,
|
29
|
+
)
|
23
30
|
|
24
31
|
__all__ = [
|
25
32
|
"InitialGuess",
|
@@ -117,7 +124,7 @@ def _steady_state_residual(
|
|
117
124
|
float: Root mean square error between model and data
|
118
125
|
|
119
126
|
"""
|
120
|
-
c_ss, v_ss = (
|
127
|
+
c_ss, v_ss = unwrap(
|
121
128
|
Simulator(
|
122
129
|
model.update_parameters(
|
123
130
|
dict(
|
@@ -132,7 +139,7 @@ def _steady_state_residual(
|
|
132
139
|
integrator=integrator,
|
133
140
|
)
|
134
141
|
.simulate_to_steady_state()
|
135
|
-
.
|
142
|
+
.get_result()
|
136
143
|
)
|
137
144
|
if c_ss is None or v_ss is None:
|
138
145
|
return cast(float, np.inf)
|
@@ -171,7 +178,7 @@ def _time_course_residual(
|
|
171
178
|
integrator=integrator,
|
172
179
|
)
|
173
180
|
.simulate_time_course(data.index) # type: ignore
|
174
|
-
.
|
181
|
+
.get_result()
|
175
182
|
)
|
176
183
|
if c_ss is None or v_ss is None:
|
177
184
|
return cast(float, np.inf)
|
modelbase2/fns.py
CHANGED
@@ -8,6 +8,7 @@ if TYPE_CHECKING:
|
|
8
8
|
from modelbase2.types import Float
|
9
9
|
|
10
10
|
__all__ = [
|
11
|
+
"add",
|
11
12
|
"constant",
|
12
13
|
"diffusion_1s_1p",
|
13
14
|
"div",
|
@@ -75,6 +76,11 @@ def twice(x: Float) -> Float:
|
|
75
76
|
return x * 2
|
76
77
|
|
77
78
|
|
79
|
+
def add(x: Float, y: Float) -> Float:
|
80
|
+
"""Proportional function."""
|
81
|
+
return x + y
|
82
|
+
|
83
|
+
|
78
84
|
def proportional(x: Float, y: Float) -> Float:
|
79
85
|
"""Proportional function."""
|
80
86
|
return x * y
|
@@ -22,7 +22,7 @@ with contextlib.redirect_stderr(open(os.devnull, "w")): # noqa: PTH123
|
|
22
22
|
if TYPE_CHECKING:
|
23
23
|
from collections.abc import Callable
|
24
24
|
|
25
|
-
from modelbase2.types import ArrayLike
|
25
|
+
from modelbase2.types import Array, ArrayLike
|
26
26
|
|
27
27
|
|
28
28
|
@dataclass
|
@@ -77,7 +77,7 @@ class Assimulo:
|
|
77
77
|
*,
|
78
78
|
t_end: float,
|
79
79
|
steps: int | None = None,
|
80
|
-
) -> tuple[
|
80
|
+
) -> tuple[Array | None, ArrayLike | None]:
|
81
81
|
"""Integrate the ODE system.
|
82
82
|
|
83
83
|
Args:
|
@@ -100,7 +100,7 @@ class Assimulo:
|
|
100
100
|
self,
|
101
101
|
*,
|
102
102
|
time_points: ArrayLike,
|
103
|
-
) -> tuple[
|
103
|
+
) -> tuple[Array | None, ArrayLike | None]:
|
104
104
|
"""Integrate the ODE system over a time course.
|
105
105
|
|
106
106
|
Args:
|
@@ -14,7 +14,7 @@ from typing import TYPE_CHECKING, cast
|
|
14
14
|
import numpy as np
|
15
15
|
import scipy.integrate as spi
|
16
16
|
|
17
|
-
from modelbase2.types import
|
17
|
+
from modelbase2.types import Array, ArrayLike
|
18
18
|
|
19
19
|
if TYPE_CHECKING:
|
20
20
|
from collections.abc import Callable
|
@@ -66,7 +66,7 @@ class Scipy:
|
|
66
66
|
*,
|
67
67
|
t_end: float,
|
68
68
|
steps: int | None = None,
|
69
|
-
) -> tuple[
|
69
|
+
) -> tuple[Array | None, ArrayLike | None]:
|
70
70
|
"""Integrate the ODE system.
|
71
71
|
|
72
72
|
Args:
|
@@ -87,7 +87,7 @@ class Scipy:
|
|
87
87
|
|
88
88
|
def integrate_time_course(
|
89
89
|
self, *, time_points: ArrayLike
|
90
|
-
) -> tuple[
|
90
|
+
) -> tuple[Array | None, ArrayLike | None]:
|
91
91
|
"""Integrate the ODE system over a time course.
|
92
92
|
|
93
93
|
Args:
|
@@ -97,7 +97,6 @@ class Scipy:
|
|
97
97
|
tuple[ArrayLike, ArrayLike]: Tuple containing the time points and the integrated values.
|
98
98
|
|
99
99
|
"""
|
100
|
-
|
101
100
|
y = spi.odeint(
|
102
101
|
func=self.rhs,
|
103
102
|
y0=self.y0,
|
@@ -108,7 +107,7 @@ class Scipy:
|
|
108
107
|
)
|
109
108
|
self.t0 = time_points[-1]
|
110
109
|
self.y0 = y[-1, :]
|
111
|
-
return time_points, y
|
110
|
+
return np.array(time_points, dtype=float), y
|
112
111
|
|
113
112
|
def integrate_to_steady_state(
|
114
113
|
self,
|
modelbase2/linear_label_map.py
CHANGED
@@ -287,10 +287,12 @@ class LinearLabelMapper:
|
|
287
287
|
stoichiometry = {}
|
288
288
|
if substrate != "EXT":
|
289
289
|
stoichiometry[substrate] = Derived(
|
290
|
-
_neg_one_div, [substrate.split("__")[0]]
|
290
|
+
name=substrate, fn=_neg_one_div, args=[substrate.split("__")[0]]
|
291
291
|
)
|
292
292
|
if product != "EXT":
|
293
|
-
stoichiometry[product] = Derived(
|
293
|
+
stoichiometry[product] = Derived(
|
294
|
+
name=product, fn=_one_div, args=[product.split("__")[0]]
|
295
|
+
)
|
294
296
|
|
295
297
|
m.add_reaction(
|
296
298
|
name=f"{rxn_name}__{i}",
|