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
@@ -0,0 +1,656 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
import ast
|
4
|
+
import inspect
|
5
|
+
import re
|
6
|
+
import textwrap
|
7
|
+
from datetime import UTC, datetime
|
8
|
+
from typing import TYPE_CHECKING, Any, cast
|
9
|
+
|
10
|
+
import dill
|
11
|
+
import libsbml
|
12
|
+
import numpy as np
|
13
|
+
|
14
|
+
from modelbase2.sbml._data import AtomicUnit, Compartment
|
15
|
+
from modelbase2.types import Derived
|
16
|
+
|
17
|
+
if TYPE_CHECKING:
|
18
|
+
from collections.abc import Callable
|
19
|
+
from pathlib import Path
|
20
|
+
|
21
|
+
from modelbase2.model import Model
|
22
|
+
|
23
|
+
|
24
|
+
__all__ = [
|
25
|
+
"BINARY",
|
26
|
+
"DocstringRemover",
|
27
|
+
"IdentifierReplacer",
|
28
|
+
"NARY",
|
29
|
+
"RE_LAMBDA_ALGEBRAIC_MODULE_FUNC",
|
30
|
+
"RE_LAMBDA_FUNC",
|
31
|
+
"RE_LAMBDA_RATE_FUNC",
|
32
|
+
"RE_TO_SBML",
|
33
|
+
"SBML_DOT",
|
34
|
+
"UNARY",
|
35
|
+
"write",
|
36
|
+
]
|
37
|
+
|
38
|
+
RE_LAMBDA_FUNC = re.compile(r".*(lambda)(.+?):(.*?)")
|
39
|
+
RE_LAMBDA_RATE_FUNC = re.compile(r".*(lambda)(.+?):(.*?),")
|
40
|
+
RE_LAMBDA_ALGEBRAIC_MODULE_FUNC = re.compile(r".*(lambda)(.+?):(.*[\(\[].+[\)\]]),")
|
41
|
+
RE_TO_SBML = re.compile(r"([^0-9_a-zA-Z])")
|
42
|
+
|
43
|
+
SBML_DOT = "__SBML_DOT__"
|
44
|
+
|
45
|
+
|
46
|
+
UNARY = {
|
47
|
+
"sqrt": libsbml.AST_FUNCTION_ROOT,
|
48
|
+
"remainder": libsbml.AST_FUNCTION_REM,
|
49
|
+
"abs": libsbml.AST_FUNCTION_ABS,
|
50
|
+
"ceil": libsbml.AST_FUNCTION_CEILING,
|
51
|
+
"sin": libsbml.AST_FUNCTION_SIN,
|
52
|
+
"cos": libsbml.AST_FUNCTION_COS,
|
53
|
+
"tan": libsbml.AST_FUNCTION_TAN,
|
54
|
+
"arcsin": libsbml.AST_FUNCTION_ARCSIN,
|
55
|
+
"arccos": libsbml.AST_FUNCTION_ARCCOS,
|
56
|
+
"arctan": libsbml.AST_FUNCTION_ARCTAN,
|
57
|
+
"sinh": libsbml.AST_FUNCTION_SINH,
|
58
|
+
"cosh": libsbml.AST_FUNCTION_COSH,
|
59
|
+
"tanh": libsbml.AST_FUNCTION_TANH,
|
60
|
+
"arcsinh": libsbml.AST_FUNCTION_ARCSINH,
|
61
|
+
"arccosh": libsbml.AST_FUNCTION_ARCCOSH,
|
62
|
+
"arctanh": libsbml.AST_FUNCTION_ARCTANH,
|
63
|
+
"log": libsbml.AST_FUNCTION_LN,
|
64
|
+
"log10": libsbml.AST_FUNCTION_LOG,
|
65
|
+
}
|
66
|
+
|
67
|
+
BINARY = {
|
68
|
+
"power": libsbml.AST_POWER,
|
69
|
+
}
|
70
|
+
|
71
|
+
NARY = {
|
72
|
+
"max": libsbml.AST_FUNCTION_MAX,
|
73
|
+
"min": libsbml.AST_FUNCTION_MIN,
|
74
|
+
}
|
75
|
+
|
76
|
+
|
77
|
+
class IdentifierReplacer(ast.NodeTransformer):
|
78
|
+
def __init__(self, mapping: dict[str, str]) -> None:
|
79
|
+
self.mapping = mapping
|
80
|
+
|
81
|
+
def visit_Name(self, node: ast.Name) -> ast.Name: # noqa: N802
|
82
|
+
return ast.Name(
|
83
|
+
id=self.mapping.get(node.id, node.id),
|
84
|
+
ctx=node.ctx,
|
85
|
+
)
|
86
|
+
|
87
|
+
|
88
|
+
class DocstringRemover(ast.NodeTransformer):
|
89
|
+
def visit_Expr(self, node: ast.Expr) -> ast.Expr | None: # noqa: N802
|
90
|
+
if isinstance(const := node.value, ast.Constant) and isinstance(
|
91
|
+
const.value, str
|
92
|
+
):
|
93
|
+
return None
|
94
|
+
return node
|
95
|
+
|
96
|
+
|
97
|
+
def _convert_unaryop(node: ast.UnaryOp) -> libsbml.ASTNode:
|
98
|
+
operand = _convert_node(node.operand)
|
99
|
+
|
100
|
+
match node.op:
|
101
|
+
case ast.USub():
|
102
|
+
op = libsbml.AST_MINUS
|
103
|
+
case ast.Not():
|
104
|
+
op = libsbml.AST_LOGICAL_NOT
|
105
|
+
case _:
|
106
|
+
raise NotImplementedError(type(node.op))
|
107
|
+
|
108
|
+
sbml_node = libsbml.ASTNode(op)
|
109
|
+
sbml_node.addChild(operand)
|
110
|
+
return sbml_node
|
111
|
+
|
112
|
+
|
113
|
+
def _convert_binop(node: ast.BinOp) -> libsbml.ASTNode:
|
114
|
+
left = _convert_node(node.left)
|
115
|
+
right = _convert_node(node.right)
|
116
|
+
|
117
|
+
match node.op:
|
118
|
+
case ast.Mult():
|
119
|
+
op = libsbml.AST_TIMES
|
120
|
+
case ast.Add():
|
121
|
+
op = libsbml.AST_PLUS
|
122
|
+
case ast.Sub():
|
123
|
+
op = libsbml.AST_MINUS
|
124
|
+
case ast.Div():
|
125
|
+
op = libsbml.AST_DIVIDE
|
126
|
+
case ast.Pow():
|
127
|
+
op = libsbml.AST_POWER
|
128
|
+
case ast.FloorDiv():
|
129
|
+
op = libsbml.AST_FUNCTION_QUOTIENT
|
130
|
+
case _:
|
131
|
+
raise NotImplementedError(type(node.op))
|
132
|
+
|
133
|
+
sbml_node = libsbml.ASTNode(op)
|
134
|
+
sbml_node.addChild(left)
|
135
|
+
sbml_node.addChild(right)
|
136
|
+
return sbml_node
|
137
|
+
|
138
|
+
|
139
|
+
def _convert_attribute(node: ast.Attribute) -> libsbml.ASTNode:
|
140
|
+
parent = cast(ast.Name, node.value).id
|
141
|
+
attr = node.attr
|
142
|
+
|
143
|
+
if parent in ("math", "np", "numpy"):
|
144
|
+
if attr == "e":
|
145
|
+
return libsbml.ASTNode(libsbml.AST_CONSTANT_E)
|
146
|
+
if attr == "pi":
|
147
|
+
return libsbml.ASTNode(libsbml.AST_CONSTANT_PI)
|
148
|
+
if attr == "inf":
|
149
|
+
sbml_node = libsbml.ASTNode(libsbml.AST_REAL)
|
150
|
+
sbml_node.setValue(np.inf)
|
151
|
+
return sbml_node
|
152
|
+
if attr == "nan":
|
153
|
+
sbml_node = libsbml.ASTNode(libsbml.AST_REAL)
|
154
|
+
sbml_node.setValue(np.nan)
|
155
|
+
return sbml_node
|
156
|
+
|
157
|
+
msg = f"{parent}.{attr}"
|
158
|
+
raise NotImplementedError(msg)
|
159
|
+
|
160
|
+
|
161
|
+
def _convert_constant(node: ast.Constant) -> libsbml.ASTNode:
|
162
|
+
value = node.value
|
163
|
+
if isinstance(value, bool):
|
164
|
+
if value:
|
165
|
+
return libsbml.ASTNode(libsbml.AST_CONSTANT_TRUE)
|
166
|
+
return libsbml.ASTNode(libsbml.AST_CONSTANT_FALSE)
|
167
|
+
|
168
|
+
sbml_node = libsbml.ASTNode(libsbml.AST_REAL)
|
169
|
+
sbml_node.setValue(value)
|
170
|
+
return sbml_node
|
171
|
+
|
172
|
+
|
173
|
+
def _convert_ifexp(node: ast.IfExp) -> libsbml.ASTNode:
|
174
|
+
condition = _convert_node(node.test)
|
175
|
+
true = _convert_node(node.body)
|
176
|
+
false = _convert_node(node.orelse)
|
177
|
+
|
178
|
+
sbml_node = libsbml.ASTNode(libsbml.AST_FUNCTION_PIECEWISE)
|
179
|
+
sbml_node.addChild(condition)
|
180
|
+
sbml_node.addChild(true)
|
181
|
+
sbml_node.addChild(false)
|
182
|
+
return sbml_node
|
183
|
+
|
184
|
+
|
185
|
+
def _convert_direct_call(node: ast.Call) -> libsbml.ASTNode:
|
186
|
+
func = cast(ast.Name, node.func).id
|
187
|
+
|
188
|
+
if (typ := UNARY.get(func)) is not None:
|
189
|
+
sbml_node = libsbml.ASTNode(typ)
|
190
|
+
sbml_node.addChild(_convert_node(node.args[0]))
|
191
|
+
return sbml_node
|
192
|
+
if (typ := BINARY.get(func)) is not None:
|
193
|
+
sbml_node = libsbml.ASTNode(typ)
|
194
|
+
sbml_node.addChild(_convert_node(node.args[0]))
|
195
|
+
sbml_node.addChild(_convert_node(node.args[1]))
|
196
|
+
return sbml_node
|
197
|
+
if (typ := NARY.get(func)) is not None:
|
198
|
+
sbml_node = libsbml.ASTNode(typ)
|
199
|
+
for arg in node.args:
|
200
|
+
sbml_node.addChild(_convert_node(arg))
|
201
|
+
return sbml_node
|
202
|
+
|
203
|
+
# General function call
|
204
|
+
sbml_node = libsbml.ASTNode(libsbml.AST_FUNCTION)
|
205
|
+
for arg in node.args:
|
206
|
+
sbml_node.addChild(_convert_node(arg))
|
207
|
+
return sbml_node
|
208
|
+
|
209
|
+
|
210
|
+
def _convert_library_call(node: ast.Call) -> libsbml.ASTNode:
|
211
|
+
func = cast(ast.Attribute, node.func)
|
212
|
+
parent = cast(ast.Name, func.value).id
|
213
|
+
attr = func.attr
|
214
|
+
|
215
|
+
if parent in ("math", "np", "numpy"):
|
216
|
+
if (typ := UNARY.get(attr)) is not None:
|
217
|
+
sbml_node = libsbml.ASTNode(typ)
|
218
|
+
sbml_node.addChild(_convert_node(node.args[0]))
|
219
|
+
return sbml_node
|
220
|
+
if (typ := BINARY.get(attr)) is not None:
|
221
|
+
sbml_node = libsbml.ASTNode(typ)
|
222
|
+
sbml_node.addChild(_convert_node(node.args[0]))
|
223
|
+
sbml_node.addChild(_convert_node(node.args[1]))
|
224
|
+
return sbml_node
|
225
|
+
if (typ := NARY.get(attr)) is not None:
|
226
|
+
sbml_node = libsbml.ASTNode(typ)
|
227
|
+
for arg in node.args:
|
228
|
+
sbml_node.addChild(_convert_node(arg))
|
229
|
+
return sbml_node
|
230
|
+
|
231
|
+
# General library call
|
232
|
+
sbml_node = libsbml.ASTNode(libsbml.AST_FUNCTION)
|
233
|
+
for arg in node.args:
|
234
|
+
sbml_node.addChild(_convert_node(arg))
|
235
|
+
return sbml_node
|
236
|
+
|
237
|
+
|
238
|
+
def _convert_call(node: ast.Call) -> libsbml.ASTNode:
|
239
|
+
func = node.func
|
240
|
+
if isinstance(func, ast.Name):
|
241
|
+
return _convert_direct_call(node)
|
242
|
+
if isinstance(func, ast.Attribute):
|
243
|
+
return _convert_library_call(node)
|
244
|
+
|
245
|
+
msg = f"Unknown call type: {type(func)}"
|
246
|
+
raise NotImplementedError(msg)
|
247
|
+
|
248
|
+
|
249
|
+
def _convert_compare(node: ast.Compare) -> libsbml.ASTNode:
|
250
|
+
# FIXME: handle cases such as x < y < z
|
251
|
+
|
252
|
+
left = _convert_node(node.left)
|
253
|
+
right = _convert_node(node.comparators[0])
|
254
|
+
|
255
|
+
match node.ops[0]:
|
256
|
+
case ast.Eq():
|
257
|
+
op = libsbml.AST_RELATIONAL_EQ
|
258
|
+
case ast.NotEq():
|
259
|
+
op = libsbml.AST_RELATIONAL_NEQ
|
260
|
+
case ast.Lt():
|
261
|
+
op = libsbml.AST_RELATIONAL_LT
|
262
|
+
case ast.LtE():
|
263
|
+
op = libsbml.AST_RELATIONAL_LEQ
|
264
|
+
case ast.Gt():
|
265
|
+
op = libsbml.AST_RELATIONAL_GT
|
266
|
+
case ast.GtE():
|
267
|
+
op = libsbml.AST_RELATIONAL_GEQ
|
268
|
+
case _:
|
269
|
+
raise NotImplementedError(type(node.ops[0]))
|
270
|
+
|
271
|
+
sbml_node = libsbml.ASTNode(op)
|
272
|
+
sbml_node.addChild(left)
|
273
|
+
sbml_node.addChild(right)
|
274
|
+
return sbml_node
|
275
|
+
|
276
|
+
|
277
|
+
def _convert_node(node: ast.stmt | ast.expr) -> libsbml.ASTNode:
|
278
|
+
match node:
|
279
|
+
case ast.Return(value):
|
280
|
+
if value is None:
|
281
|
+
msg = "Model function cannot return `None`"
|
282
|
+
raise ValueError(msg)
|
283
|
+
return _convert_node(value)
|
284
|
+
case ast.UnaryOp():
|
285
|
+
return _convert_unaryop(node)
|
286
|
+
case ast.BinOp():
|
287
|
+
return _convert_binop(node)
|
288
|
+
case ast.Name(id):
|
289
|
+
sbml_node = libsbml.ASTNode(libsbml.AST_NAME)
|
290
|
+
sbml_node.setName(id)
|
291
|
+
return sbml_node
|
292
|
+
case ast.Constant():
|
293
|
+
return _convert_constant(node)
|
294
|
+
case ast.Attribute():
|
295
|
+
return _convert_attribute(node)
|
296
|
+
case ast.IfExp():
|
297
|
+
return _convert_ifexp(node)
|
298
|
+
case ast.Call():
|
299
|
+
return _convert_call(node)
|
300
|
+
case ast.Compare():
|
301
|
+
return _convert_compare(node)
|
302
|
+
case _:
|
303
|
+
raise NotImplementedError(type(node))
|
304
|
+
|
305
|
+
|
306
|
+
def _handle_body(stmts: list[ast.stmt]) -> libsbml.ASTNode:
|
307
|
+
code = libsbml.ASTNode()
|
308
|
+
for stmt in stmts:
|
309
|
+
code = _convert_node(stmt)
|
310
|
+
return code
|
311
|
+
|
312
|
+
|
313
|
+
def _tree_to_sbml(
|
314
|
+
tree: ast.FunctionDef, args: list[str] | None = None
|
315
|
+
) -> libsbml.ASTNode:
|
316
|
+
DocstringRemover().visit(tree)
|
317
|
+
if args is not None:
|
318
|
+
fn_args = [i.arg for i in tree.args.args]
|
319
|
+
argmap = dict(zip(fn_args, args, strict=True))
|
320
|
+
IdentifierReplacer(argmap).visit(tree)
|
321
|
+
return _handle_body(tree.body)
|
322
|
+
|
323
|
+
|
324
|
+
def _sbmlify_fn(fn: Callable, user_args: list[str]) -> libsbml.ASTNode:
|
325
|
+
try:
|
326
|
+
source = inspect.getsource(fn)
|
327
|
+
except OSError: # could not get source code
|
328
|
+
source = dill.source.getsource(fn)
|
329
|
+
|
330
|
+
tree = ast.parse(textwrap.dedent(source))
|
331
|
+
if not isinstance(fn_def := tree.body[0], ast.FunctionDef):
|
332
|
+
msg = "Not a function"
|
333
|
+
raise TypeError(msg)
|
334
|
+
|
335
|
+
return _tree_to_sbml(fn_def, args=user_args)
|
336
|
+
|
337
|
+
|
338
|
+
##########################################################################
|
339
|
+
# SBML functions
|
340
|
+
##########################################################################
|
341
|
+
|
342
|
+
|
343
|
+
def _escape_non_alphanumeric(re_sub: Any) -> str:
|
344
|
+
"""Convert a non-alphanumeric charactor to a string representation of its ascii number."""
|
345
|
+
return f"__{ord(re_sub.group(0))}__"
|
346
|
+
|
347
|
+
|
348
|
+
def _convert_id_to_sbml(id_: str, prefix: str) -> str:
|
349
|
+
"""Add prefix if id startswith number."""
|
350
|
+
new_id = RE_TO_SBML.sub(_escape_non_alphanumeric, id_).replace(".", SBML_DOT)
|
351
|
+
if not new_id[0].isalpha():
|
352
|
+
return f"{prefix}_{new_id}"
|
353
|
+
return new_id
|
354
|
+
|
355
|
+
|
356
|
+
def _create_sbml_document() -> libsbml.SBMLDocument:
|
357
|
+
"""Create an sbml document, into which sbml information can be written.
|
358
|
+
|
359
|
+
Returns:
|
360
|
+
doc : libsbml.Document
|
361
|
+
|
362
|
+
"""
|
363
|
+
# SBML namespaces
|
364
|
+
sbml_ns = libsbml.SBMLNamespaces(3, 2)
|
365
|
+
sbml_ns.addPackageNamespace("fbc", 2)
|
366
|
+
# SBML document
|
367
|
+
doc = libsbml.SBMLDocument(sbml_ns)
|
368
|
+
doc.setPackageRequired("fbc", flag=False)
|
369
|
+
doc.setSBOTerm("SBO:0000004")
|
370
|
+
return doc
|
371
|
+
|
372
|
+
|
373
|
+
def _create_sbml_model(
|
374
|
+
*,
|
375
|
+
model_name: str,
|
376
|
+
doc: libsbml.SBMLDocument,
|
377
|
+
extent_units: str,
|
378
|
+
substance_units: str,
|
379
|
+
time_units: str,
|
380
|
+
) -> libsbml.Model:
|
381
|
+
"""Create an sbml model.
|
382
|
+
|
383
|
+
Args:
|
384
|
+
model_name: Name of the model.
|
385
|
+
doc: libsbml.Document
|
386
|
+
extent_units: Units for the extent of reactions.
|
387
|
+
substance_units: Units for the amount of substances.
|
388
|
+
time_units: Units for time.
|
389
|
+
|
390
|
+
Returns:
|
391
|
+
sbml_model : libsbml.Model
|
392
|
+
|
393
|
+
"""
|
394
|
+
name = f"{model_name}_{datetime.now(UTC).date().strftime('%Y-%m-%d')}"
|
395
|
+
sbml_model = doc.createModel()
|
396
|
+
sbml_model.setId(_convert_id_to_sbml(id_=name, prefix="MODEL"))
|
397
|
+
sbml_model.setName(_convert_id_to_sbml(id_=name, prefix="MODEL"))
|
398
|
+
sbml_model.setTimeUnits(time_units)
|
399
|
+
sbml_model.setExtentUnits(extent_units)
|
400
|
+
sbml_model.setSubstanceUnits(substance_units)
|
401
|
+
sbml_model_fbc = sbml_model.getPlugin("fbc")
|
402
|
+
sbml_model_fbc.setStrict(True)
|
403
|
+
return sbml_model
|
404
|
+
|
405
|
+
|
406
|
+
def _create_sbml_units(
|
407
|
+
*,
|
408
|
+
units: dict[str, AtomicUnit],
|
409
|
+
sbml_model: libsbml.Model,
|
410
|
+
) -> None:
|
411
|
+
"""Create sbml units out of the meta_info.
|
412
|
+
|
413
|
+
Args:
|
414
|
+
units: Dictionary of units to use in the SBML file.
|
415
|
+
sbml_model : libsbml Model
|
416
|
+
|
417
|
+
"""
|
418
|
+
for unit_id, unit in units.items():
|
419
|
+
sbml_definition = sbml_model.createUnitDefinition()
|
420
|
+
sbml_definition.setId(unit_id)
|
421
|
+
sbml_unit = sbml_definition.createUnit()
|
422
|
+
sbml_unit.setKind(unit.kind)
|
423
|
+
sbml_unit.setExponent(unit.exponent)
|
424
|
+
sbml_unit.setScale(unit.scale)
|
425
|
+
sbml_unit.setMultiplier(unit.multiplier)
|
426
|
+
|
427
|
+
|
428
|
+
def _create_sbml_compartments(
|
429
|
+
*,
|
430
|
+
compartments: dict[str, Compartment],
|
431
|
+
sbml_model: libsbml.Model,
|
432
|
+
) -> None:
|
433
|
+
for compartment_id, compartment in compartments.items():
|
434
|
+
sbml_compartment = sbml_model.createCompartment()
|
435
|
+
sbml_compartment.setId(compartment_id)
|
436
|
+
sbml_compartment.setName(compartment.name)
|
437
|
+
sbml_compartment.setConstant(compartment.is_constant)
|
438
|
+
sbml_compartment.setSize(compartment.size)
|
439
|
+
sbml_compartment.setSpatialDimensions(compartment.dimensions)
|
440
|
+
sbml_compartment.setUnits(compartment.units)
|
441
|
+
|
442
|
+
|
443
|
+
def _create_sbml_variables(
|
444
|
+
*,
|
445
|
+
model: Model,
|
446
|
+
sbml_model: libsbml.Model,
|
447
|
+
) -> None:
|
448
|
+
"""Create the variables for the sbml model.
|
449
|
+
|
450
|
+
Args:
|
451
|
+
model: Model instance to export.
|
452
|
+
sbml_model : libsbml.Model
|
453
|
+
|
454
|
+
"""
|
455
|
+
for name, value in model.variables.items():
|
456
|
+
cpd = sbml_model.createSpecies()
|
457
|
+
cpd.setId(_convert_id_to_sbml(id_=name, prefix="CPD"))
|
458
|
+
|
459
|
+
cpd.setConstant(False)
|
460
|
+
cpd.setBoundaryCondition(False)
|
461
|
+
cpd.setHasOnlySubstanceUnits(False)
|
462
|
+
cpd.setInitialAmount(float(value))
|
463
|
+
|
464
|
+
|
465
|
+
def _create_sbml_derived_variables(*, model: Model, sbml_model: libsbml.Model) -> None:
|
466
|
+
for name, dv in model.derived_variables.items():
|
467
|
+
sbml_ar = sbml_model.createAssignmentRule()
|
468
|
+
sbml_ar.setId(_convert_id_to_sbml(id_=name, prefix="AR"))
|
469
|
+
sbml_ar.setName(_convert_id_to_sbml(id_=name, prefix="AR"))
|
470
|
+
sbml_ar.setVariable(_convert_id_to_sbml(id_=name, prefix="AR"))
|
471
|
+
sbml_ar.setMath(_sbmlify_fn(dv.fn, dv.args))
|
472
|
+
|
473
|
+
|
474
|
+
def _create_derived_parameter(
|
475
|
+
sbml_model: libsbml.Model,
|
476
|
+
name: str,
|
477
|
+
dp: Derived,
|
478
|
+
) -> None:
|
479
|
+
"""Create a derived parameter for the sbml model."""
|
480
|
+
ar = sbml_model.createAssignmentRule()
|
481
|
+
ar.setId(_convert_id_to_sbml(id_=name, prefix="AR"))
|
482
|
+
ar.setName(_convert_id_to_sbml(id_=name, prefix="AR"))
|
483
|
+
ar.setVariable(_convert_id_to_sbml(id_=name, prefix="AR"))
|
484
|
+
ar.setMath(_sbmlify_fn(dp.fn, dp.args))
|
485
|
+
|
486
|
+
|
487
|
+
def _create_sbml_parameters(
|
488
|
+
*,
|
489
|
+
model: Model,
|
490
|
+
sbml_model: libsbml.Model,
|
491
|
+
) -> None:
|
492
|
+
"""Create the parameters for the sbml model.
|
493
|
+
|
494
|
+
Args:
|
495
|
+
model: Model instance to export.
|
496
|
+
sbml_model : libsbml.Model
|
497
|
+
|
498
|
+
"""
|
499
|
+
for parameter_id, value in model.parameters.items():
|
500
|
+
k = sbml_model.createParameter()
|
501
|
+
k.setId(_convert_id_to_sbml(id_=parameter_id, prefix="PAR"))
|
502
|
+
k.setConstant(True)
|
503
|
+
k.setValue(float(value))
|
504
|
+
|
505
|
+
|
506
|
+
def _create_sbml_derived_parameters(*, model: Model, sbml_model: libsbml.Model) -> None:
|
507
|
+
for name, dp in model.derived_parameters.items():
|
508
|
+
_create_derived_parameter(sbml_model, name, dp)
|
509
|
+
|
510
|
+
|
511
|
+
def _create_sbml_reactions(
|
512
|
+
*,
|
513
|
+
model: Model,
|
514
|
+
sbml_model: libsbml.Model,
|
515
|
+
) -> None:
|
516
|
+
"""Create the reactions for the sbml model."""
|
517
|
+
for name, rxn in model.reactions.items():
|
518
|
+
sbml_rxn = sbml_model.createReaction()
|
519
|
+
sbml_rxn.setId(_convert_id_to_sbml(id_=name, prefix="RXN"))
|
520
|
+
sbml_rxn.setName(name)
|
521
|
+
sbml_rxn.setFast(False)
|
522
|
+
|
523
|
+
for compound_id, factor in rxn.stoichiometry.items():
|
524
|
+
match factor:
|
525
|
+
case float() | int():
|
526
|
+
sref = (
|
527
|
+
sbml_rxn.createReactant()
|
528
|
+
if factor < 0
|
529
|
+
else sbml_rxn.createProduct()
|
530
|
+
)
|
531
|
+
sref.setSpecies(_convert_id_to_sbml(id_=compound_id, prefix="CPD"))
|
532
|
+
sref.setStoichiometry(abs(factor))
|
533
|
+
sref.setConstant(False)
|
534
|
+
case Derived():
|
535
|
+
# SBML uses species references for derived stoichiometries
|
536
|
+
# So we need to create a assignment rule and then refer to it
|
537
|
+
reference = f"{compound_id}ref"
|
538
|
+
_create_derived_parameter(sbml_model, reference, factor)
|
539
|
+
|
540
|
+
sref = sbml_rxn.createReactant()
|
541
|
+
sref.setId(_convert_id_to_sbml(id_=reference, prefix="CPD"))
|
542
|
+
sref.setSpecies(_convert_id_to_sbml(id_=compound_id, prefix="CPD"))
|
543
|
+
case _:
|
544
|
+
msg = f"Stoichiometry type {type(factor)} not supported"
|
545
|
+
raise NotImplementedError(msg)
|
546
|
+
for compound_id in rxn.get_modifiers(model):
|
547
|
+
sref = sbml_rxn.createModifier()
|
548
|
+
sref.setSpecies(_convert_id_to_sbml(id_=compound_id, prefix="CPD"))
|
549
|
+
|
550
|
+
sbml_rxn.createKineticLaw().setMath(_sbmlify_fn(rxn.fn, rxn.args))
|
551
|
+
|
552
|
+
|
553
|
+
def _model_to_sbml(
|
554
|
+
model: Model,
|
555
|
+
*,
|
556
|
+
model_name: str,
|
557
|
+
units: dict[str, AtomicUnit],
|
558
|
+
extent_units: str,
|
559
|
+
substance_units: str,
|
560
|
+
time_units: str,
|
561
|
+
compartments: dict[str, Compartment],
|
562
|
+
) -> libsbml.SBMLDocument:
|
563
|
+
"""Export model to sbml."""
|
564
|
+
doc = _create_sbml_document()
|
565
|
+
sbml_model = _create_sbml_model(
|
566
|
+
model_name=model_name,
|
567
|
+
doc=doc,
|
568
|
+
extent_units=extent_units,
|
569
|
+
substance_units=substance_units,
|
570
|
+
time_units=time_units,
|
571
|
+
)
|
572
|
+
_create_sbml_units(units=units, sbml_model=sbml_model)
|
573
|
+
_create_sbml_compartments(compartments=compartments, sbml_model=sbml_model)
|
574
|
+
# Actual model components
|
575
|
+
_create_sbml_parameters(model=model, sbml_model=sbml_model)
|
576
|
+
_create_sbml_derived_parameters(model=model, sbml_model=sbml_model)
|
577
|
+
_create_sbml_variables(model=model, sbml_model=sbml_model)
|
578
|
+
_create_sbml_derived_variables(model=model, sbml_model=sbml_model)
|
579
|
+
_create_sbml_reactions(model=model, sbml_model=sbml_model)
|
580
|
+
return doc
|
581
|
+
|
582
|
+
|
583
|
+
def _default_compartments(
|
584
|
+
compartments: dict[str, Compartment] | None,
|
585
|
+
) -> dict[str, Compartment]:
|
586
|
+
if compartments is None:
|
587
|
+
return {
|
588
|
+
"c": Compartment(
|
589
|
+
name="cytosol",
|
590
|
+
dimensions=3,
|
591
|
+
size=1,
|
592
|
+
units="litre",
|
593
|
+
is_constant=True,
|
594
|
+
)
|
595
|
+
}
|
596
|
+
return compartments
|
597
|
+
|
598
|
+
|
599
|
+
def _default_model_name(model_name: str | None) -> str:
|
600
|
+
if model_name is None:
|
601
|
+
return "model"
|
602
|
+
return model_name
|
603
|
+
|
604
|
+
|
605
|
+
def _default_units(units: dict[str, AtomicUnit] | None) -> dict[str, AtomicUnit]:
|
606
|
+
if units is None:
|
607
|
+
return {
|
608
|
+
"per_second": AtomicUnit(
|
609
|
+
kind=libsbml.UNIT_KIND_SECOND,
|
610
|
+
exponent=-1,
|
611
|
+
scale=0,
|
612
|
+
multiplier=1,
|
613
|
+
)
|
614
|
+
}
|
615
|
+
return units
|
616
|
+
|
617
|
+
|
618
|
+
def write(
|
619
|
+
model: Model,
|
620
|
+
file: Path,
|
621
|
+
*,
|
622
|
+
model_name: str | None = None,
|
623
|
+
units: dict[str, AtomicUnit] | None = None,
|
624
|
+
compartments: dict[str, Compartment] | None = None,
|
625
|
+
extent_units: str = "mole",
|
626
|
+
substance_units: str = "mole",
|
627
|
+
time_units: str = "second",
|
628
|
+
) -> Path:
|
629
|
+
"""Export a metabolic model to an SBML file.
|
630
|
+
|
631
|
+
Args:
|
632
|
+
model: Model instance to export.
|
633
|
+
file: Name of the SBML file to create.
|
634
|
+
model_name: Name of the model.
|
635
|
+
units: Dictionary of units to use in the SBML file (default: None).
|
636
|
+
compartments: Dictionary of compartments to use in the SBML file (default: None).
|
637
|
+
extent_units: Units for the extent of reactions (default: "mole").
|
638
|
+
substance_units: Units for the amount of substances (default: "mole").
|
639
|
+
time_units: Units for time (default: "second").
|
640
|
+
|
641
|
+
Returns:
|
642
|
+
str | None: None if the export is successful.
|
643
|
+
|
644
|
+
"""
|
645
|
+
doc = _model_to_sbml(
|
646
|
+
model=model,
|
647
|
+
model_name=_default_model_name(model_name),
|
648
|
+
units=_default_units(units),
|
649
|
+
extent_units=extent_units,
|
650
|
+
substance_units=substance_units,
|
651
|
+
time_units=time_units,
|
652
|
+
compartments=_default_compartments(compartments),
|
653
|
+
)
|
654
|
+
|
655
|
+
libsbml.writeSBMLToFile(doc, str(file))
|
656
|
+
return file
|