modelbase2 0.1.79__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.79.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.79.dist-info/METADATA +0 -44
- modelbase2-0.1.79.dist-info/RECORD +0 -22
- {modelbase2-0.1.79.dist-info → modelbase2-0.2.0.dist-info/licenses}/LICENSE +0 -0
@@ -0,0 +1,239 @@
|
|
1
|
+
"""Module to export models as code."""
|
2
|
+
|
3
|
+
import ast
|
4
|
+
import inspect
|
5
|
+
import warnings
|
6
|
+
from collections.abc import Callable, Generator, Iterable, Iterator
|
7
|
+
|
8
|
+
from modelbase2.model import Model
|
9
|
+
from modelbase2.types import Derived
|
10
|
+
|
11
|
+
__all__ = [
|
12
|
+
"DocstringRemover",
|
13
|
+
"IdentifierReplacer",
|
14
|
+
"ReturnRemover",
|
15
|
+
"conditional_join",
|
16
|
+
"generate_model_code_py",
|
17
|
+
"generate_modelbase_code",
|
18
|
+
"get_fn_source",
|
19
|
+
"handle_fn",
|
20
|
+
]
|
21
|
+
|
22
|
+
|
23
|
+
class IdentifierReplacer(ast.NodeTransformer):
|
24
|
+
"""Replace identifiers in an AST."""
|
25
|
+
|
26
|
+
def __init__(self, mapping: dict[str, str]) -> None:
|
27
|
+
"""Initialize the transformer with a mapping."""
|
28
|
+
self.mapping = mapping
|
29
|
+
|
30
|
+
def visit_Name(self, node: ast.Name) -> ast.Name: # noqa: N802
|
31
|
+
"""Replace the identifier with the mapped value."""
|
32
|
+
return ast.Name(
|
33
|
+
id=self.mapping.get(node.id, node.id),
|
34
|
+
ctx=node.ctx,
|
35
|
+
)
|
36
|
+
|
37
|
+
|
38
|
+
class DocstringRemover(ast.NodeTransformer):
|
39
|
+
"""Remove docstrings from an AST."""
|
40
|
+
|
41
|
+
def visit_Expr(self, node: ast.Expr) -> ast.Expr | None: # noqa: N802
|
42
|
+
"""Remove docstrings."""
|
43
|
+
if isinstance(const := node.value, ast.Constant) and isinstance(
|
44
|
+
const.value, str
|
45
|
+
):
|
46
|
+
return None
|
47
|
+
return node
|
48
|
+
|
49
|
+
|
50
|
+
class ReturnRemover(ast.NodeTransformer):
|
51
|
+
"""Remove return statements from an AST."""
|
52
|
+
|
53
|
+
def visit_Return(self, node: ast.Return) -> ast.expr | None: # noqa: N802
|
54
|
+
"""Remove return statements."""
|
55
|
+
return node.value
|
56
|
+
|
57
|
+
|
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
|
+
def handle_fn(fn: Callable, args: list[str]) -> str:
|
78
|
+
"""Get the source code of a function, removing docstrings and return statements."""
|
79
|
+
tree = get_fn_source(fn)
|
80
|
+
|
81
|
+
argmap = dict(zip([i.arg for i in tree.args.args], args, strict=True))
|
82
|
+
tree = DocstringRemover().visit(tree)
|
83
|
+
tree = IdentifierReplacer(argmap).visit(tree)
|
84
|
+
tree = ReturnRemover().visit(tree)
|
85
|
+
return ast.unparse(tree.body)
|
86
|
+
|
87
|
+
|
88
|
+
def conditional_join[T](
|
89
|
+
iterable: Iterable[T],
|
90
|
+
question: Callable[[T], bool],
|
91
|
+
true_pat: str,
|
92
|
+
false_pat: str,
|
93
|
+
) -> str:
|
94
|
+
"""Join an iterable, applying a pattern to each element based on a condition."""
|
95
|
+
|
96
|
+
def inner(it: Iterator[T]) -> Generator[str, None, None]:
|
97
|
+
yield str(next(it))
|
98
|
+
while True:
|
99
|
+
try:
|
100
|
+
el = next(it)
|
101
|
+
if question(el):
|
102
|
+
yield f"{true_pat}{el}"
|
103
|
+
else:
|
104
|
+
yield f"{false_pat}{el}"
|
105
|
+
except StopIteration:
|
106
|
+
break
|
107
|
+
|
108
|
+
return "".join(inner(iter(iterable)))
|
109
|
+
|
110
|
+
|
111
|
+
def generate_modelbase_code(model: Model) -> str:
|
112
|
+
"""Generate a modelbase model from a model."""
|
113
|
+
functions = {}
|
114
|
+
|
115
|
+
# Variables and parameters
|
116
|
+
variables = model.variables
|
117
|
+
parameters = model.parameters
|
118
|
+
|
119
|
+
# Derived
|
120
|
+
derived_source = []
|
121
|
+
for k, v in model.derived.items():
|
122
|
+
fn = v.fn
|
123
|
+
fn_name = fn.__name__
|
124
|
+
functions[fn_name] = inspect.getsource(fn)
|
125
|
+
|
126
|
+
derived_source.append(
|
127
|
+
f""".add_derived(
|
128
|
+
"{k}",
|
129
|
+
fn={fn_name},
|
130
|
+
args={v.args},
|
131
|
+
)"""
|
132
|
+
)
|
133
|
+
|
134
|
+
# Reactions
|
135
|
+
reactions_source = []
|
136
|
+
for k, v in model.reactions.items():
|
137
|
+
fn = v.fn
|
138
|
+
fn_name = fn.__name__
|
139
|
+
functions[fn_name] = inspect.getsource(fn)
|
140
|
+
reactions_source.append(
|
141
|
+
f""" .add_reaction(
|
142
|
+
"{k}",
|
143
|
+
fn={fn_name},
|
144
|
+
args={v.args},
|
145
|
+
stoichiometry={v.stoichiometry},
|
146
|
+
)"""
|
147
|
+
)
|
148
|
+
|
149
|
+
# Surrogates
|
150
|
+
if len(model._surrogates) > 0: # noqa: SLF001
|
151
|
+
warnings.warn(
|
152
|
+
"Generating code for Surrogates not yet supported.",
|
153
|
+
stacklevel=1,
|
154
|
+
)
|
155
|
+
|
156
|
+
# Combine all the sources
|
157
|
+
functions_source = "\n".join(functions.values())
|
158
|
+
source = [
|
159
|
+
"from modelbase2 import Model\n",
|
160
|
+
functions_source,
|
161
|
+
"def create_model() -> Model:",
|
162
|
+
" return (",
|
163
|
+
" Model()",
|
164
|
+
]
|
165
|
+
if len(parameters) > 0:
|
166
|
+
source.append(f" .add_parameters({parameters})")
|
167
|
+
if len(variables) > 0:
|
168
|
+
source.append(f" .add_variables({variables})")
|
169
|
+
if len(derived_source) > 0:
|
170
|
+
source.append("\n".join(derived_source))
|
171
|
+
if len(reactions_source) > 0:
|
172
|
+
source.append("\n".join(reactions_source))
|
173
|
+
|
174
|
+
source.append(" )")
|
175
|
+
|
176
|
+
return "\n".join(source)
|
177
|
+
|
178
|
+
|
179
|
+
def generate_model_code_py(model: Model) -> str:
|
180
|
+
"""Transform the model into a single function, inlining the function calls."""
|
181
|
+
# Variables
|
182
|
+
variables = model.variables
|
183
|
+
variable_source = " {} = y".format(", ".join(variables))
|
184
|
+
|
185
|
+
# Parameters
|
186
|
+
parameter_source = "\n".join(f" {k} = {v}" for k, v in model.parameters.items())
|
187
|
+
|
188
|
+
# Derived
|
189
|
+
derived_source = []
|
190
|
+
for name, derived in model.derived.items():
|
191
|
+
derived_source.append(f" {name} = {handle_fn(derived.fn, derived.args)}")
|
192
|
+
|
193
|
+
# Reactions
|
194
|
+
reactions = []
|
195
|
+
for name, rxn in model.reactions.items():
|
196
|
+
reactions.append(f" {name} = {handle_fn(rxn.fn, rxn.args)}")
|
197
|
+
|
198
|
+
# Stoichiometries
|
199
|
+
stoichiometries = {}
|
200
|
+
for rxn_name, rxn in model.reactions.items():
|
201
|
+
for cpd_name, factor in rxn.stoichiometry.items():
|
202
|
+
if isinstance(factor, Derived):
|
203
|
+
src = f"{handle_fn(factor.fn, factor.args)} * {rxn_name}"
|
204
|
+
elif factor == 1:
|
205
|
+
src = rxn_name
|
206
|
+
elif factor == -1:
|
207
|
+
src = f"- {rxn_name}"
|
208
|
+
else:
|
209
|
+
src = f"{factor} * {rxn_name}"
|
210
|
+
stoichiometries.setdefault(cpd_name, []).append(src)
|
211
|
+
stoich_source = []
|
212
|
+
for variable, stoich in stoichiometries.items():
|
213
|
+
stoich_source.append(
|
214
|
+
f" d{variable}dt = {conditional_join(stoich, lambda x: x.startswith("-"), " ", " + ")}"
|
215
|
+
)
|
216
|
+
|
217
|
+
# Surrogates
|
218
|
+
if len(model._surrogates) > 0: # noqa: SLF001
|
219
|
+
warnings.warn(
|
220
|
+
"Generating code for Surrogates not yet supported.",
|
221
|
+
stacklevel=1,
|
222
|
+
)
|
223
|
+
|
224
|
+
# Combine all the sources
|
225
|
+
source = [
|
226
|
+
"from collections.abc import Iterable\n",
|
227
|
+
"from modelbase2.types import Float\n",
|
228
|
+
"def model(t: Float, y: Float) -> Iterable[Float]:",
|
229
|
+
variable_source,
|
230
|
+
parameter_source,
|
231
|
+
*derived_source,
|
232
|
+
*reactions,
|
233
|
+
*stoich_source,
|
234
|
+
" return {}".format(
|
235
|
+
", ".join(f"d{i}dt" for i in variables),
|
236
|
+
),
|
237
|
+
]
|
238
|
+
|
239
|
+
return "\n".join(source)
|
@@ -0,0 +1,227 @@
|
|
1
|
+
"""Diffing utilities for comparing models."""
|
2
|
+
|
3
|
+
from dataclasses import dataclass, field
|
4
|
+
from typing import Mapping
|
5
|
+
|
6
|
+
from modelbase2.model import Model
|
7
|
+
from modelbase2.types import Derived
|
8
|
+
|
9
|
+
__all__ = ["DerivedDiff", "ModelDiff", "ReactionDiff", "model_diff", "soft_eq"]
|
10
|
+
|
11
|
+
|
12
|
+
@dataclass
|
13
|
+
class DerivedDiff:
|
14
|
+
"""Difference between two derived variables."""
|
15
|
+
|
16
|
+
args1: list[str] = field(default_factory=list)
|
17
|
+
args2: list[str] = field(default_factory=list)
|
18
|
+
|
19
|
+
|
20
|
+
@dataclass
|
21
|
+
class ReactionDiff:
|
22
|
+
"""Difference between two reactions."""
|
23
|
+
|
24
|
+
args1: list[str] = field(default_factory=list)
|
25
|
+
args2: list[str] = field(default_factory=list)
|
26
|
+
stoichiometry1: dict[str, float | Derived] = field(default_factory=dict)
|
27
|
+
stoichiometry2: dict[str, float | Derived] = field(default_factory=dict)
|
28
|
+
|
29
|
+
|
30
|
+
@dataclass
|
31
|
+
class ModelDiff:
|
32
|
+
"""Difference between two models."""
|
33
|
+
|
34
|
+
missing_parameters: set[str] = field(default_factory=set)
|
35
|
+
missing_variables: set[str] = field(default_factory=set)
|
36
|
+
missing_reactions: set[str] = field(default_factory=set)
|
37
|
+
missing_surrogates: set[str] = field(default_factory=set)
|
38
|
+
missing_readouts: set[str] = field(default_factory=set)
|
39
|
+
missing_derived: set[str] = field(default_factory=set)
|
40
|
+
different_parameters: dict[str, tuple[float, float]] = field(default_factory=dict)
|
41
|
+
different_variables: dict[str, tuple[float, float]] = field(default_factory=dict)
|
42
|
+
different_reactions: dict[str, ReactionDiff] = field(default_factory=dict)
|
43
|
+
different_surrogates: dict[str, ReactionDiff] = field(default_factory=dict)
|
44
|
+
different_readouts: dict[str, DerivedDiff] = field(default_factory=dict)
|
45
|
+
different_derived: dict[str, DerivedDiff] = field(default_factory=dict)
|
46
|
+
|
47
|
+
def __str__(self) -> str:
|
48
|
+
"""Return a human-readable string representation of the diff."""
|
49
|
+
content = ["Model Diff", "----------"]
|
50
|
+
|
51
|
+
# Parameters
|
52
|
+
if self.missing_parameters:
|
53
|
+
content.append(
|
54
|
+
"Missing Parameters: {}".format(", ".join(self.missing_parameters))
|
55
|
+
)
|
56
|
+
if self.different_parameters:
|
57
|
+
content.append("Different Parameters:")
|
58
|
+
for k, (v1, v2) in self.different_parameters.items():
|
59
|
+
content.append(f" {k}: {v1} != {v2}")
|
60
|
+
|
61
|
+
# Variables
|
62
|
+
if self.missing_variables:
|
63
|
+
content.append(
|
64
|
+
"Missing Variables: {}".format(", ".join(self.missing_variables))
|
65
|
+
)
|
66
|
+
if self.different_variables:
|
67
|
+
content.append("Different Variables:")
|
68
|
+
for k, (v1, v2) in self.different_variables.items():
|
69
|
+
content.append(f" {k}: {v1} != {v2}")
|
70
|
+
|
71
|
+
# Derived
|
72
|
+
if self.missing_derived:
|
73
|
+
content.append(
|
74
|
+
"Missing Derived: {}".format(", ".join(self.missing_derived))
|
75
|
+
)
|
76
|
+
if self.different_derived:
|
77
|
+
content.append("Different Derived:")
|
78
|
+
for k, diff in self.different_derived.items():
|
79
|
+
content.append(f" {k}:")
|
80
|
+
if diff.args1 != diff.args2:
|
81
|
+
content.append(f" Args: {diff.args1} != {diff.args2}")
|
82
|
+
|
83
|
+
# Reactions
|
84
|
+
if self.missing_reactions:
|
85
|
+
content.append(
|
86
|
+
"Missing Reactions: {}".format(", ".join(self.missing_reactions))
|
87
|
+
)
|
88
|
+
if self.different_reactions:
|
89
|
+
content.append("Different Reactions:")
|
90
|
+
for k, diff in self.different_reactions.items():
|
91
|
+
content.append(f" {k}:")
|
92
|
+
if diff.args1 != diff.args2:
|
93
|
+
content.append(f" Args: {diff.args1} != {diff.args2}")
|
94
|
+
if diff.stoichiometry1 != diff.stoichiometry2:
|
95
|
+
content.append(
|
96
|
+
f" Stoichiometry: {diff.stoichiometry1} != {diff.stoichiometry2}"
|
97
|
+
)
|
98
|
+
|
99
|
+
# Surrogates
|
100
|
+
if self.missing_surrogates:
|
101
|
+
content.append(
|
102
|
+
"Missing Surrogates: {}".format(", ".join(self.missing_surrogates))
|
103
|
+
)
|
104
|
+
if self.different_surrogates:
|
105
|
+
content.append("Different Surrogates:")
|
106
|
+
for k, diff in self.different_surrogates.items():
|
107
|
+
content.append(f" {k}:")
|
108
|
+
if diff.args1 != diff.args2:
|
109
|
+
content.append(f" Args: {diff.args1} != {diff.args2}")
|
110
|
+
if diff.stoichiometry1 != diff.stoichiometry2:
|
111
|
+
content.append(
|
112
|
+
f" Stoichiometry: {diff.stoichiometry1} != {diff.stoichiometry2}"
|
113
|
+
)
|
114
|
+
return "\n".join(content)
|
115
|
+
|
116
|
+
|
117
|
+
def _soft_eq_stoichiometries(
|
118
|
+
s1: Mapping[str, float | Derived], s2: Mapping[str, float | Derived]
|
119
|
+
) -> bool:
|
120
|
+
"""Check if two stoichiometries are equal, ignoring the functions."""
|
121
|
+
if s1.keys() != s2.keys():
|
122
|
+
return False
|
123
|
+
|
124
|
+
for k, v1 in s1.items():
|
125
|
+
v2 = s2[k]
|
126
|
+
if isinstance(v1, Derived):
|
127
|
+
if not isinstance(v2, Derived):
|
128
|
+
return False
|
129
|
+
if v1.args != v2.args:
|
130
|
+
return False
|
131
|
+
else:
|
132
|
+
if v1 != v2:
|
133
|
+
return False
|
134
|
+
|
135
|
+
return True
|
136
|
+
|
137
|
+
|
138
|
+
def soft_eq(m1: Model, m2: Model) -> bool:
|
139
|
+
"""Check if two models are equal, ignoring the functions."""
|
140
|
+
if m1._parameters != m2._parameters: # noqa: SLF001
|
141
|
+
return False
|
142
|
+
if m1._variables != m2._variables: # noqa: SLF001
|
143
|
+
return False
|
144
|
+
for k, d1 in m1._derived.items(): # noqa: SLF001
|
145
|
+
if (d2 := m2._derived.get(k)) is None: # noqa: SLF001
|
146
|
+
return False
|
147
|
+
if d1.args != d2.args:
|
148
|
+
return False
|
149
|
+
for k, r1 in m1._readouts.items(): # noqa: SLF001
|
150
|
+
if (r2 := m2._readouts.get(k)) is None: # noqa: SLF001
|
151
|
+
return False
|
152
|
+
if r1.args != r2.args:
|
153
|
+
return False
|
154
|
+
for k, v1 in m1._reactions.items(): # noqa: SLF001
|
155
|
+
if (v2 := m2._reactions.get(k)) is None: # noqa: SLF001
|
156
|
+
return False
|
157
|
+
if v1.args != v2.args:
|
158
|
+
return False
|
159
|
+
if not _soft_eq_stoichiometries(v1.stoichiometry, v2.stoichiometry):
|
160
|
+
return False
|
161
|
+
for k, s1 in m1._surrogates.items(): # noqa: SLF001
|
162
|
+
if (s2 := m2._surrogates.get(k)) is None: # noqa: SLF001
|
163
|
+
return False
|
164
|
+
if s1.args != s2.args:
|
165
|
+
return False
|
166
|
+
if s1.stoichiometries != s2.stoichiometries:
|
167
|
+
return False
|
168
|
+
return True
|
169
|
+
|
170
|
+
|
171
|
+
def model_diff(m1: Model, m2: Model) -> ModelDiff:
|
172
|
+
"""Compute the difference between two models."""
|
173
|
+
diff = ModelDiff()
|
174
|
+
|
175
|
+
for k, v1 in m1._parameters.items(): # noqa: SLF001
|
176
|
+
if (v2 := m2._parameters.get(k)) is None: # noqa: SLF001
|
177
|
+
diff.missing_parameters.add(k)
|
178
|
+
elif v1 != v2:
|
179
|
+
diff.different_parameters[k] = (v1, v2)
|
180
|
+
|
181
|
+
for k, v1 in m1._variables.items(): # noqa: SLF001
|
182
|
+
if (v2 := m2._variables.get(k)) is None: # noqa: SLF001
|
183
|
+
diff.missing_variables.add(k)
|
184
|
+
elif v1 != v2:
|
185
|
+
diff.different_variables[k] = (v1, v2)
|
186
|
+
|
187
|
+
for k, v1 in m1._readouts.items(): # noqa: SLF001
|
188
|
+
if (v2 := m2._readouts.get(k)) is None: # noqa: SLF001
|
189
|
+
diff.missing_readouts.add(k)
|
190
|
+
elif v1.args != v2.args:
|
191
|
+
diff.different_readouts[k] = DerivedDiff(v1.args, v2.args)
|
192
|
+
|
193
|
+
for k, v1 in m1._derived.items(): # noqa: SLF001
|
194
|
+
if (v2 := m2._derived.get(k)) is None: # noqa: SLF001
|
195
|
+
diff.missing_derived.add(k)
|
196
|
+
elif v1.args != v2.args:
|
197
|
+
diff.different_derived[k] = DerivedDiff(v1.args, v2.args)
|
198
|
+
|
199
|
+
for k, v1 in m1._reactions.items(): # noqa: SLF001
|
200
|
+
if (v2 := m2._reactions.get(k)) is None: # noqa: SLF001
|
201
|
+
diff.missing_reactions.add(k)
|
202
|
+
else:
|
203
|
+
if v1.args != v2.args:
|
204
|
+
rxn_diff: ReactionDiff = diff.different_reactions.get(k, ReactionDiff())
|
205
|
+
rxn_diff.args1 = v1.args
|
206
|
+
rxn_diff.args2 = v2.args
|
207
|
+
diff.different_reactions[k] = rxn_diff
|
208
|
+
if v1.stoichiometry != v2.stoichiometry:
|
209
|
+
rxn_diff = diff.different_reactions.get(k, ReactionDiff())
|
210
|
+
rxn_diff.stoichiometry1 = dict(v1.stoichiometry)
|
211
|
+
rxn_diff.stoichiometry2 = dict(v2.stoichiometry)
|
212
|
+
diff.different_reactions[k] = rxn_diff
|
213
|
+
|
214
|
+
for k, v1 in m1._surrogates.items(): # noqa: SLF001
|
215
|
+
if (v2 := m2._surrogates.get(k)) is None: # noqa: SLF001
|
216
|
+
diff.missing_surrogates.add(k)
|
217
|
+
else:
|
218
|
+
if v1.args != v2.args:
|
219
|
+
rxn_diff = diff.different_surrogates.get(k, ReactionDiff())
|
220
|
+
rxn_diff.args1 = v1.args
|
221
|
+
rxn_diff.args2 = v2.args
|
222
|
+
if v1.stoichiometries != v2.stoichiometries:
|
223
|
+
rxn_diff = diff.different_surrogates.get(k, ReactionDiff())
|
224
|
+
rxn_diff.stoichiometry1 = dict(v1.stoichiometries) # type: ignore
|
225
|
+
rxn_diff.stoichiometry2 = dict(v2.stoichiometries) # type: ignore
|
226
|
+
|
227
|
+
return diff
|