PyAntiGen 1.0.9__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.
- framework/AntimonyGen.py +48 -0
- framework/RxnDict_to_antimony.py +594 -0
- framework/TelluriumGen.py +16 -0
- framework/__init__.py +0 -0
- framework/antimony_utils.py +294 -0
- framework/cli.py +229 -0
- framework/data_interpolation.py +340 -0
- framework/isotopomer_tools.py +41 -0
- framework/model_generation.py +46 -0
- framework/models.py +189 -0
- framework/module_base.py +42 -0
- framework/pyantigen.py +51 -0
- framework/rate_laws.py +101 -0
- framework/reaction_creation.py +43 -0
- framework/template/Example/AntiGen_paths.py +23 -0
- framework/template/Example/Engine/Anchor_cache.py +193 -0
- framework/template/Example/Engine/Deadline.py +535 -0
- framework/template/Example/Engine/Evaluator.py +1176 -0
- framework/template/Example/Engine/Event_times.py +491 -0
- framework/template/Example/Engine/Fast_profile.py +701 -0
- framework/template/Example/Engine/Fit_cache.py +329 -0
- framework/template/Example/Engine/Identifiability.py +698 -0
- framework/template/Example/Engine/Model_optimize.py +1483 -0
- framework/template/Example/Engine/Model_simulate.py +124 -0
- framework/template/Example/Engine/Nuisance_sensitivity.py +298 -0
- framework/template/Example/Engine/Optimize.py +6862 -0
- framework/template/Example/Engine/Petab_export.py +398 -0
- framework/template/Example/Engine/Preequil_cache.py +361 -0
- framework/template/Example/Engine/Profile_checkpoint.py +399 -0
- framework/template/Example/Engine/Results.py +395 -0
- framework/template/Example/Engine/Sensitivity_analysis.py +320 -0
- framework/template/Example/Engine/Simulate.py +617 -0
- framework/template/Example/Flipflop_reference.py +401 -0
- framework/template/Example/Model_generate.py +37 -0
- framework/template/Example/Model_run.py +261 -0
- framework/template/Example/Modules/Data.py +63 -0
- framework/template/Example/Modules/Events.py +14 -0
- framework/template/Example/Modules/Experiment.py +194 -0
- framework/template/Example/Modules/Loss_config.py +61 -0
- framework/template/Example/Modules/Observed_species.py +3 -0
- framework/template/Example/Modules/Optimizer_settings.py +258 -0
- framework/template/Example/Modules/Plots.py +89 -0
- framework/template/Example/Modules/Solver_settings.py +16 -0
- framework/template/Example/Modules/Update_opt_parameters.py +24 -0
- framework/template/Example/Modules/Update_parameters.py +49 -0
- framework/template/data/ADneg.csv +27 -0
- framework/template/data/ADpos.csv +27 -0
- framework/template/data/Flipflop.csv +29 -0
- framework/template/data/make_flipflop_data.py +174 -0
- pyantigen-1.0.9.dist-info/METADATA +129 -0
- pyantigen-1.0.9.dist-info/RECORD +55 -0
- pyantigen-1.0.9.dist-info/WHEEL +5 -0
- pyantigen-1.0.9.dist-info/entry_points.txt +2 -0
- pyantigen-1.0.9.dist-info/licenses/LICENSE +21 -0
- pyantigen-1.0.9.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
from dataclasses import dataclass, field
|
|
2
|
+
from .Loss_config import *
|
|
3
|
+
|
|
4
|
+
@dataclass
|
|
5
|
+
class Optimization:
|
|
6
|
+
param_names: list
|
|
7
|
+
x0: list
|
|
8
|
+
bounds: list = None
|
|
9
|
+
method: str = "Nelder-Mead"
|
|
10
|
+
optimizer_kwargs: dict = field(default_factory=dict)
|
|
11
|
+
group_normalization: str = "mean_over_groups" # "mean_over_groups" | "sum_over_groups"
|
|
12
|
+
groups: dict = field(default_factory=dict) # nested group/loss configuration
|
|
13
|
+
passive_simulations: list = field(default_factory=list) # passive simulations to run for plotting
|
|
14
|
+
# Per-parameter optimizer scale: None/"lin" keeps the parameter linear,
|
|
15
|
+
# "log10" fits log10(p) instead. Accepts a single string for all parameters,
|
|
16
|
+
# a {name: scale} dict, or a list aligned with param_names. x0 and bounds
|
|
17
|
+
# stay in linear units; so does everything reported back.
|
|
18
|
+
parameter_scale: object = None
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _build_example_opt1_ADpos():
|
|
22
|
+
"""
|
|
23
|
+
"k_A_to_B" and "SF" are jointly identifiable from the ADpos data, so they
|
|
24
|
+
are fit together in one optimization against the ADpos replicates.
|
|
25
|
+
"""
|
|
26
|
+
return Optimization(
|
|
27
|
+
param_names=["k_A_to_B", "SF"],
|
|
28
|
+
x0=[0.5, 2.0],
|
|
29
|
+
bounds=[(0.01, 10.0), (0.01, 10.0)],
|
|
30
|
+
method="Nelder-Mead",
|
|
31
|
+
optimizer_kwargs={"options": {"maxiter": 500}},
|
|
32
|
+
group_normalization="mean_over_groups",
|
|
33
|
+
groups={
|
|
34
|
+
"ADpos": {
|
|
35
|
+
"group_weight": 1.0,
|
|
36
|
+
"loss_elements": [
|
|
37
|
+
{"simulation": "ADpos_Early", "loss_config": Example1_loss_config, "weight": 1.0},
|
|
38
|
+
{"simulation": "ADpos_Late", "loss_config": Example1_loss_config, "weight": 1.0},
|
|
39
|
+
]
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
passive_simulations=[],
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _build_example_opt1_ADneg():
|
|
47
|
+
"""
|
|
48
|
+
"V_Comp1" is fit separately, against the ADneg data. It is not folded
|
|
49
|
+
into the ADpos optimization above because it is not jointly identifiable
|
|
50
|
+
with "SF" there: both trade off against the same [B] trajectories, so a
|
|
51
|
+
combined fit lands on a ridge rather than a minimum. Splitting the fit by
|
|
52
|
+
group (rather than lumping all three parameters into one optimization)
|
|
53
|
+
keeps each individual fit well-posed.
|
|
54
|
+
"""
|
|
55
|
+
return Optimization(
|
|
56
|
+
param_names=["V_Comp1"],
|
|
57
|
+
x0=[0.5],
|
|
58
|
+
bounds=[(0.01, 10.0)],
|
|
59
|
+
method="Nelder-Mead",
|
|
60
|
+
optimizer_kwargs={"options": {"maxiter": 500}},
|
|
61
|
+
group_normalization="mean_over_groups",
|
|
62
|
+
groups={
|
|
63
|
+
"ADneg": {
|
|
64
|
+
"group_weight": 1.0,
|
|
65
|
+
"loss_elements": [
|
|
66
|
+
{"simulation": "ADneg_Early", "loss_config": Example1_loss_config, "weight": 1.0},
|
|
67
|
+
{"simulation": "ADneg_Late", "loss_config": Example1_loss_config, "weight": 1.0},
|
|
68
|
+
]
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
passive_simulations=[],
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
OPTIMIZATION_Example1_ADpos = _build_example_opt1_ADpos()
|
|
75
|
+
OPTIMIZATION_Example1_ADneg = _build_example_opt1_ADneg()
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _build_example3_joint():
|
|
79
|
+
"""
|
|
80
|
+
NEGATIVE EXAMPLE: fits "k_A_to_B", "SF", and "V_Comp1" jointly against the
|
|
81
|
+
ADpos data, instead of splitting "V_Comp1" out as Example1 does. This is
|
|
82
|
+
the fit Example1 deliberately avoids.
|
|
83
|
+
|
|
84
|
+
The confound is exact, not approximate: the reaction rate law is
|
|
85
|
+
"k_A_to_B * (A_Comp1/V_Comp1) * V_Comp1", so V_Comp1 cancels out of the
|
|
86
|
+
ODE entirely (see antimony_models/Example/Example_reactions.txt) and
|
|
87
|
+
B_Comp1(t) never depends on it. The only place V_Comp1 appears is the
|
|
88
|
+
output map "predicted_B := SF * B_Comp1 / V_Comp1" (Example_manual.txt),
|
|
89
|
+
so predicted_B depends only on the ratio SF/V_Comp1 -- any (SF, V_Comp1)
|
|
90
|
+
pair with the same ratio fits identically. "SF" and "V_Comp1" are
|
|
91
|
+
therefore structurally unidentifiable together; only "k_A_to_B" (which
|
|
92
|
+
shapes the amount trajectory itself) is well-identified here.
|
|
93
|
+
|
|
94
|
+
Run with Example2's diagnostics settings to see the signature of this in
|
|
95
|
+
practice: profile likelihood gives k_A_to_B a tight, finite 95% CI while
|
|
96
|
+
SF and V_Comp1 both come back [nan, nan] (their profiles never cross the
|
|
97
|
+
threshold, because the optimizer can always trade one against the other
|
|
98
|
+
to hold SF/V_Comp1 constant and recover the identical loss).
|
|
99
|
+
|
|
100
|
+
CAVEAT -- Sobol says the opposite, and that is expected, not a bug:
|
|
101
|
+
run_sobol_analysis samples k_A_to_B, SF, and V_Comp1 *independently* and
|
|
102
|
+
uniformly across their bounds and measures variance of the raw loss, with
|
|
103
|
+
no re-optimization. Since the model only depends on the ratio SF/V_Comp1,
|
|
104
|
+
independently randomizing them over a 1000x range almost always lands far
|
|
105
|
+
off the SF/V_Comp1 = const ridge that fits the data -- that scale mismatch
|
|
106
|
+
dominates the loss variance, so SF and V_Comp1 get large ST (in a run of
|
|
107
|
+
this example, ST(V_Comp1)=4.25, ST(SF)=0.64) while k_A_to_B's genuine but
|
|
108
|
+
comparatively small effect on trajectory timing gets washed out
|
|
109
|
+
(ST(k_A_to_B)=0.00000), flipping the "identifiable" (ST > 0.01) label onto
|
|
110
|
+
exactly the two confounded parameters and off the one real one. Sobol-on-
|
|
111
|
+
loss measures uncorrelated sensitivity ("does perturbing this parameter
|
|
112
|
+
alone move the loss"), not identifiability ("can the other parameters
|
|
113
|
+
compensate for it") -- for a ridge/confound like this, those two
|
|
114
|
+
questions have opposite answers. Treat Sobol here as a sensitivity
|
|
115
|
+
screen, and profile likelihood as the identifiability check.
|
|
116
|
+
"""
|
|
117
|
+
return Optimization(
|
|
118
|
+
param_names=["k_A_to_B", "SF", "V_Comp1"],
|
|
119
|
+
x0=[0.5, 2.0, 0.5],
|
|
120
|
+
bounds=[(0.01, 10.0), (0.01, 10.0), (0.01, 10.0)],
|
|
121
|
+
method="Nelder-Mead",
|
|
122
|
+
optimizer_kwargs={"options": {"maxiter": 500}},
|
|
123
|
+
group_normalization="mean_over_groups",
|
|
124
|
+
groups={
|
|
125
|
+
"ADpos": {
|
|
126
|
+
"group_weight": 1.0,
|
|
127
|
+
"loss_elements": [
|
|
128
|
+
{"simulation": "ADpos_Early", "loss_config": Example1_loss_config, "weight": 1.0},
|
|
129
|
+
{"simulation": "ADpos_Late", "loss_config": Example1_loss_config, "weight": 1.0},
|
|
130
|
+
]
|
|
131
|
+
}
|
|
132
|
+
},
|
|
133
|
+
passive_simulations=[],
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
OPTIMIZATION_Example3_joint = _build_example3_joint()
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def _flipflop_groups():
|
|
140
|
+
return {
|
|
141
|
+
"Flipflop": {
|
|
142
|
+
"group_weight": 1.0,
|
|
143
|
+
"loss_elements": [
|
|
144
|
+
{"simulation": "Flipflop_Early", "loss_config": Flipflop_loss_config, "weight": 1.0},
|
|
145
|
+
{"simulation": "Flipflop_Late", "loss_config": Flipflop_loss_config, "weight": 1.0},
|
|
146
|
+
],
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def _build_example4_flipflop():
|
|
152
|
+
"""
|
|
153
|
+
MULTIMODAL EXAMPLE: tests the *accuracy* of profile likelihood dNLL, not
|
|
154
|
+
just its ability to flag a flat direction (which Example3 covers).
|
|
155
|
+
|
|
156
|
+
The model is the chain A -> B -> C (rates k_A_to_B, k_B_to_C) observed
|
|
157
|
+
through predicted_B := SF*B_Comp1/V_Comp1 on a log10 scale. Since
|
|
158
|
+
|
|
159
|
+
B(t) = dose * k_A_to_B * (exp(-k_A_to_B*t) - exp(-k_B_to_C*t))
|
|
160
|
+
/ (k_B_to_C - k_A_to_B),
|
|
161
|
+
|
|
162
|
+
swapping the two rate constants rescales B by a constant factor that the
|
|
163
|
+
fitted SF absorbs exactly: (k1, k2, SF) and (k2, k1, SF*k1/k2) produce
|
|
164
|
+
IDENTICAL predicted_B trajectories (pharmacokinetic "flip-flop"). Four
|
|
165
|
+
deliberately noisy predicted_A points on the Early treatment break the
|
|
166
|
+
symmetry by a small, known amount: relative to the NLL optimum the
|
|
167
|
+
swapped mode is a genuine local minimum at dNLL ~ 2.4, just above the
|
|
168
|
+
95% threshold of 1.9207 (printed by data/make_flipflop_data.py).
|
|
169
|
+
|
|
170
|
+
What accurate diagnostics must show here (numbers from
|
|
171
|
+
Flipflop_reference.py, which replicates the full pipeline):
|
|
172
|
+
* likelihood slices: a single sharp minimum — slices cannot see the
|
|
173
|
+
second mode at all, because reaching it requires the OTHER two
|
|
174
|
+
parameters to move (k_B_to_C and SF swap along with k_A_to_B).
|
|
175
|
+
* every true profile dips to dNLL ~ -2.12 next to the fit point. That
|
|
176
|
+
is not an error: the FITTING objective averages each observable's
|
|
177
|
+
chi-square over its own points, which upweights the 4 noisy logA
|
|
178
|
+
points ~11x against 45 logB points, so the fit optimum (k_A_to_B ~
|
|
179
|
+
0.363) sits measurably away from the NLL optimum (~0.324) that
|
|
180
|
+
diagnostics are anchored to. A profile that does not dip is wrong.
|
|
181
|
+
* true profile likelihood for each parameter: TWO minima. Because of
|
|
182
|
+
that anchor offset, the swapped mode (k_A_to_B ~ 0.074, k_B_to_C ~
|
|
183
|
+
0.32, SF ~ 7.2) reports at dNLL ~ +0.8 — BELOW the threshold — so
|
|
184
|
+
the correct 95% confidence set is a union of two disjoint intervals
|
|
185
|
+
even though the fit started in the right basin. A profile walker
|
|
186
|
+
that stops at the first threshold crossing never discovers the
|
|
187
|
+
second mode, and a first-crossing CI extractor cannot represent the
|
|
188
|
+
disjoint set; both failures make the reported CI silently
|
|
189
|
+
conditional on the wrong assumption of unimodality. And if the dNLL
|
|
190
|
+
scale is off (the log10-objective sigma pitfalls described in
|
|
191
|
+
Flipflop_loss_config), everything above shifts across the threshold
|
|
192
|
+
and the CI changes shape entirely.
|
|
193
|
+
|
|
194
|
+
Flipflop_reference.py recomputes the exact profiles from the closed-form
|
|
195
|
+
solution with scipy (no RoadRunner, no framework loss code) under the same
|
|
196
|
+
frozen-sigma convention — run it and overlay the two sets of curves. Any
|
|
197
|
+
disagreement is an error in the profile machinery, not in the model.
|
|
198
|
+
"""
|
|
199
|
+
return Optimization(
|
|
200
|
+
param_names=["k_A_to_B", "k_B_to_C", "SF"],
|
|
201
|
+
x0=[0.3, 0.08, 1.5], # inside the true-mode basin (k_A_to_B > k_B_to_C)
|
|
202
|
+
bounds=[(0.005, 5.0), (0.005, 5.0), (0.05, 50.0)],
|
|
203
|
+
method="Nelder-Mead",
|
|
204
|
+
optimizer_kwargs={"options": {"maxiter": 2000, "xatol": 1e-8, "fatol": 1e-10}},
|
|
205
|
+
group_normalization="sum_over_groups",
|
|
206
|
+
parameter_scale="log10",
|
|
207
|
+
groups=_flipflop_groups(),
|
|
208
|
+
passive_simulations=[],
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def _build_example5_flipflop_swapped():
|
|
213
|
+
"""
|
|
214
|
+
Same problem as Example4 but started inside the WRONG basin
|
|
215
|
+
(k_A_to_B < k_B_to_C), so Nelder-Mead converges to the swapped local
|
|
216
|
+
minimum. This stresses the diagnostics in the way multimodal problems
|
|
217
|
+
stress them in practice, where nobody tells you the optimizer found the
|
|
218
|
+
wrong mode:
|
|
219
|
+
|
|
220
|
+
* every sigma is frozen by MLE at the *local* optimum. The logA sigma
|
|
221
|
+
absorbs the swapped mode's A-misfit (~1.73 dex instead of ~0.92), and
|
|
222
|
+
that inflation deflates every dNLL built on it — the same
|
|
223
|
+
log10-objective sigma sensitivity described in Flipflop_loss_config,
|
|
224
|
+
arising here from mode choice rather than from a heuristic;
|
|
225
|
+
* an accurate profile must go NEGATIVE — dropping to dNLL ~ -0.98 at
|
|
226
|
+
the true mode (not the -2.4 the true-mode sigmas would give: the
|
|
227
|
+
inflated sigma has already flattened the landscape). A negative
|
|
228
|
+
profile minimum is the unambiguous signature that the fit missed the
|
|
229
|
+
global optimum, and the analysis must report it rather than clip,
|
|
230
|
+
re-anchor, or hide it;
|
|
231
|
+
* with the true mode at -0.98, BOTH modes lie below the 1.9207
|
|
232
|
+
threshold: the correct 95% confidence set for every parameter is a
|
|
233
|
+
union of two disjoint intervals (e.g. k_A_to_B in [0.072, 0.076] U
|
|
234
|
+
[0.284, 0.363]), which a first-threshold-crossing CI extractor
|
|
235
|
+
cannot represent — it reports the narrow interval around the wrong
|
|
236
|
+
mode and silently discards the one containing the truth.
|
|
237
|
+
|
|
238
|
+
Compare against Flipflop_reference.py --anchor swapped, which computes
|
|
239
|
+
the reference profiles anchored at the same wrong mode.
|
|
240
|
+
"""
|
|
241
|
+
return Optimization(
|
|
242
|
+
param_names=["k_A_to_B", "k_B_to_C", "SF"],
|
|
243
|
+
x0=[0.06, 0.4, 7.0], # inside the swapped-mode basin
|
|
244
|
+
bounds=[(0.005, 5.0), (0.005, 5.0), (0.05, 50.0)],
|
|
245
|
+
method="Nelder-Mead",
|
|
246
|
+
optimizer_kwargs={"options": {"maxiter": 2000, "xatol": 1e-8, "fatol": 1e-10}},
|
|
247
|
+
group_normalization="sum_over_groups",
|
|
248
|
+
parameter_scale="log10",
|
|
249
|
+
groups=_flipflop_groups(),
|
|
250
|
+
passive_simulations=[],
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
OPTIMIZATION_Example4_flipflop = _build_example4_flipflop()
|
|
254
|
+
OPTIMIZATION_Example5_flipflop_swapped = _build_example5_flipflop_swapped()
|
|
255
|
+
|
|
256
|
+
def get_OPTIMIZATION(name):
|
|
257
|
+
"""Returns a single Optimization configuration by name."""
|
|
258
|
+
return globals().get(name)
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import matplotlib.pyplot as plt
|
|
2
|
+
import matplotlib.gridspec as gridspec
|
|
3
|
+
import os
|
|
4
|
+
import numpy as np
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def plot_results(paths, results_dict):
|
|
8
|
+
"""
|
|
9
|
+
Plot simulation results for N experiments.
|
|
10
|
+
|
|
11
|
+
Args:
|
|
12
|
+
plot_path: Directory to save the plot.
|
|
13
|
+
MODEL_NAME: Model name for title/filename.
|
|
14
|
+
results: List of dicts from Experiment.run_all: each has "result", "data", "label".
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
plot_path = paths["plot_path"]
|
|
18
|
+
MODEL_NAME = paths["MODEL_NAME"]
|
|
19
|
+
repo_root = paths["repo_root"]
|
|
20
|
+
n = max(len(results_dict), 1)
|
|
21
|
+
color_A = ["blue","green","cyan","black"]
|
|
22
|
+
color_B = ["red","orange","purple","brown"]
|
|
23
|
+
fig = plt.figure(figsize=(10, 10))
|
|
24
|
+
gs = gridspec.GridSpec(1, 1, figure=fig, hspace=0.5, wspace=0.35)
|
|
25
|
+
ax = fig.add_subplot(gs[0, 0])
|
|
26
|
+
for i, (label, item) in enumerate(results_dict.items()):
|
|
27
|
+
|
|
28
|
+
results = item["results"]
|
|
29
|
+
data_dict = item["data"]
|
|
30
|
+
|
|
31
|
+
data = data_dict[f"{label}"]
|
|
32
|
+
|
|
33
|
+
time_points = results["time"]
|
|
34
|
+
|
|
35
|
+
ax.plot(time_points, results["predicted_A"], label=f"[A] {label}", color=color_A[i])
|
|
36
|
+
ax.plot(time_points, results["predicted_B"], label=f"[B] {label}", color=color_B[i], linestyle='--')
|
|
37
|
+
if "time" in data.columns and "B" in data.columns:
|
|
38
|
+
ax.scatter(data["time"], data["B"], color=color_B[i], s=30, zorder=5, label=f"Measured [B] {label}")
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
# plot_tag carries the run label + group names (e.g. "Example1_ADpos") so
|
|
43
|
+
# each example/group writes its own figure instead of overwriting one file.
|
|
44
|
+
tag = paths.get("plot_tag")
|
|
45
|
+
ax.set_xlabel("Time")
|
|
46
|
+
ax.set_ylabel("Concentration")
|
|
47
|
+
ax.set_title("Simulation Results for " + MODEL_NAME + (f" — {tag}" if tag else ""))
|
|
48
|
+
ax.legend(loc="center left", bbox_to_anchor=(1.02, 0.5))
|
|
49
|
+
plt.subplots_adjust(right=0.65)
|
|
50
|
+
plot_name = os.path.join(plot_path, MODEL_NAME + (f"_{tag}" if tag else "") + ".png")
|
|
51
|
+
plt.savefig(plot_name, bbox_inches="tight")
|
|
52
|
+
print(f"Plot saved to: {plot_name}")
|
|
53
|
+
plt.show()
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def plot_flipflop(paths, results_dict):
|
|
58
|
+
"""Plot the flip-flop example on a log scale (the scale the loss uses)."""
|
|
59
|
+
plot_path = paths["plot_path"]
|
|
60
|
+
MODEL_NAME = paths["MODEL_NAME"]
|
|
61
|
+
colors = ["tab:blue", "tab:red", "tab:green", "tab:orange"]
|
|
62
|
+
fig, ax = plt.subplots(figsize=(9, 6))
|
|
63
|
+
for i, (label, item) in enumerate(results_dict.items()):
|
|
64
|
+
results = item["results"]
|
|
65
|
+
data_dict = item["data"]
|
|
66
|
+
c = colors[i % len(colors)]
|
|
67
|
+
ax.semilogy(results["time"], np.maximum(results["predicted_B"], 1e-12),
|
|
68
|
+
color=c, label=f"predicted_B {label}")
|
|
69
|
+
ax.semilogy(results["time"], np.maximum(results["predicted_A"], 1e-12),
|
|
70
|
+
color=c, linestyle=":", label=f"predicted_A {label}")
|
|
71
|
+
data_B = data_dict.get(label)
|
|
72
|
+
if data_B is not None and "logB" in data_B.columns:
|
|
73
|
+
ax.scatter(data_B["time"], 10.0 ** data_B["logB"], color=c, s=20,
|
|
74
|
+
zorder=5, label=f"logB data {label}")
|
|
75
|
+
data_A = data_dict.get(f"{label}_A")
|
|
76
|
+
if data_A is not None and "logA" in data_A.columns:
|
|
77
|
+
ax.scatter(data_A["time"], 10.0 ** data_A["logA"], color=c, s=40,
|
|
78
|
+
marker="x", zorder=5, label=f"logA data {label}")
|
|
79
|
+
tag = paths.get("plot_tag")
|
|
80
|
+
ax.set_xlabel("Time")
|
|
81
|
+
ax.set_ylabel("Output (log scale)")
|
|
82
|
+
ax.set_ylim(1e-3, 30)
|
|
83
|
+
ax.set_title("Flip-flop example: " + MODEL_NAME + (f" — {tag}" if tag else ""))
|
|
84
|
+
ax.legend(loc="center left", bbox_to_anchor=(1.02, 0.5), fontsize=8)
|
|
85
|
+
plt.subplots_adjust(right=0.62)
|
|
86
|
+
plot_name = os.path.join(plot_path, MODEL_NAME + (f"_{tag}" if tag else "") + "_flipflop.png")
|
|
87
|
+
plt.savefig(plot_name, bbox_inches="tight")
|
|
88
|
+
print(f"Plot saved to: {plot_name}")
|
|
89
|
+
plt.show()
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
def make_solver_settings(blocks, abs_tol=1e-10, rel_tol=1e-10, max_steps=20000):
|
|
2
|
+
"""Factory for solver settings dicts. Reduces boilerplate across all figure settings."""
|
|
3
|
+
return {
|
|
4
|
+
'integrator': 'cvode',
|
|
5
|
+
'absolute_tolerance': abs_tol,
|
|
6
|
+
'relative_tolerance': rel_tol,
|
|
7
|
+
'maximum_num_steps': max_steps,
|
|
8
|
+
'stiff': True,
|
|
9
|
+
'variable_step_size': True,
|
|
10
|
+
'simulation_blocks': blocks,
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
def solver_settings_Example(replicate):
|
|
14
|
+
return make_solver_settings(
|
|
15
|
+
{'block1': {'start': 0, 'end': 48, 'n_points': 1000, 'abs_tol':1e-12, 'rel_tol':1e-12}}
|
|
16
|
+
)
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Unlike Update_parameters.py, this function is called at each
|
|
2
|
+
# iteration of the optimization problem. The purpose is to modify some
|
|
3
|
+
# non-optimized parameters that depend upon an optimized parameter.
|
|
4
|
+
# The parameters are modified in the RoadRunner instance (r).
|
|
5
|
+
#
|
|
6
|
+
# Use cases:
|
|
7
|
+
# 1. Some parameters depend on parameters that are being optimized.
|
|
8
|
+
# 2. Event parameters change depending on experimental design.
|
|
9
|
+
#
|
|
10
|
+
#
|
|
11
|
+
# Example:
|
|
12
|
+
#
|
|
13
|
+
# def update_opt_parameters_antibody(r, experiment, parameters):
|
|
14
|
+
# drug_name = experiment.get("Drug")
|
|
15
|
+
# if drug_name == "Lecanemab":
|
|
16
|
+
# if 'k_f_DensePlaque_Antibody_Lecanemab' in parameters:
|
|
17
|
+
# r['k_f_DensePlaque_Antibody'] = parameters['k_f_DensePlaque_Antibody_Lecanemab']
|
|
18
|
+
# elif drug_name == "Aducanumab":
|
|
19
|
+
# if 'k_f_DensePlaque_Antibody_Aducanumab' in parameters:
|
|
20
|
+
# r['k_f_DensePlaque_Antibody'] = parameters['k_f_DensePlaque_Antibody_Aducanumab']
|
|
21
|
+
#
|
|
22
|
+
|
|
23
|
+
def update_opt_no_parameters(r, experiment, parameters):
|
|
24
|
+
return
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# Update_parameters.py is used in these cases:
|
|
2
|
+
# 1. The base parameters change from one treatment to another
|
|
3
|
+
# 2. The base parameters change from one replicate to another
|
|
4
|
+
# 3. The base parameters are modified by events during the treatment
|
|
5
|
+
# Update_parameters is called after the parameters are set during
|
|
6
|
+
# construction of the RoadRunner model, but before the simulation is run.
|
|
7
|
+
# It is not called during each round of optimization, only at the
|
|
8
|
+
# setup of the optimization problem. If parameters need to be modified
|
|
9
|
+
# at each parameter update in an optimization problem, then
|
|
10
|
+
# contruct a function within Update_opt_parameters.py.
|
|
11
|
+
# The function does not need to return anything, but modifies the parameters in the
|
|
12
|
+
# RoadRunner instance (r).
|
|
13
|
+
# 'mode' is optional and is given "Simulator" for a pure simulation
|
|
14
|
+
# and "Optimizer" for an optimization problem. The purpose is to allow for
|
|
15
|
+
# different parameter updates for simulations and optimizations.
|
|
16
|
+
# 'replicate' is a dictionary containing the replicate information.
|
|
17
|
+
# 'r' is the RoadRunner instance.
|
|
18
|
+
#
|
|
19
|
+
# Example:
|
|
20
|
+
#
|
|
21
|
+
# def update_parameters(r, replicate):
|
|
22
|
+
# dose = replicate.get("dose", 10)
|
|
23
|
+
# r.A_Comp1 = dose
|
|
24
|
+
#
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def update_no_parameters(r, replicate):
|
|
29
|
+
return
|
|
30
|
+
|
|
31
|
+
def update_flipflop(r, replicate):
|
|
32
|
+
# Ground-truth values used to generate data/Flipflop.csv
|
|
33
|
+
# (see data/make_flipflop_data.py). During optimization these are
|
|
34
|
+
# overwritten every evaluation by the fitted parameters; for --simulate
|
|
35
|
+
# they make the model reproduce the data-generating truth.
|
|
36
|
+
r['k_A_to_B'] = 0.35
|
|
37
|
+
r['k_B_to_C'] = 0.07
|
|
38
|
+
r['SF'] = 1.6
|
|
39
|
+
return
|
|
40
|
+
|
|
41
|
+
def update_Example(r, replicate):
|
|
42
|
+
|
|
43
|
+
if replicate["amyloid_positive"]:
|
|
44
|
+
r['k_A_to_B'] = 0.2
|
|
45
|
+
print("Replicate: ", replicate["Label"], " k_A_to_B: ", r['k_A_to_B'])
|
|
46
|
+
else:
|
|
47
|
+
r['k_A_to_B'] = 0.1
|
|
48
|
+
print("Replicate: ", replicate["Label"], " k_A_to_B: ", r['k_A_to_B'])
|
|
49
|
+
return
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
Treatment,time,B1,B2,B3
|
|
2
|
+
Early,0,0.38342206790138894,0.091549388,0.5900498343353935
|
|
3
|
+
Early,4,0.2703454588887822,0.17383910077895837,0.6020884589334081
|
|
4
|
+
Early,8,2.296824911197708,2.487658225541256,2.293891968985253
|
|
5
|
+
Early,12,5.277374209126811,5.601574235385922,5.1235773990747475
|
|
6
|
+
Early,16,6.294924781277804,6.741032603463997,6.207362167461668
|
|
7
|
+
Early,20,7.627622224028859,7.827731764768038,7.637070537276994
|
|
8
|
+
Early,24,8.095154334660107,8.483255473019799,8.632232828229437
|
|
9
|
+
Early,28,8.824407618146214,9.305510261116316,8.776431868004604
|
|
10
|
+
Early,32,9.577892749513929,9.121325348548881,9.481106334
|
|
11
|
+
Early,36,9.803956887789985,8.999973449725445,9.596915791033005
|
|
12
|
+
Early,40,9.972862529,9.945594885,9.674311540321838
|
|
13
|
+
Early,44,9.367666080270844,10.008398237577786,9.880527147349609
|
|
14
|
+
Early,48,9.655247074718085,10.145675796028533,9.929383743361324
|
|
15
|
+
Late,0,0.1791263071636229,0.10003528830134165,0.12159208964547695
|
|
16
|
+
Late,4,0.19491688193363405,0.12378603847264884,0.2462734469696091
|
|
17
|
+
Late,8,0.092482469,0.29851688388349296,0.19202822519921844
|
|
18
|
+
Late,12,0.7697289334950888,0.6124998769902201,1.318366837872463
|
|
19
|
+
Late,16,2.418552127081854,2.4872151677545156,2.5537485789706498
|
|
20
|
+
Late,20,2.5945616441206996,2.6845832675225005,3.1989915955519637
|
|
21
|
+
Late,24,3.5197094831506863,4.049684883564003,3.848379545818031
|
|
22
|
+
Late,28,4.095113399003736,4.132972884332342,3.9449664938773834
|
|
23
|
+
Late,32,4.557037667813649,4.4178217770342565,4.252760815246755
|
|
24
|
+
Late,36,4.498622402962995,4.424129799453062,4.968822520193124
|
|
25
|
+
Late,40,4.796580380353387,4.575674896370977,4.614222306643472
|
|
26
|
+
Late,44,4.9971092743924075,4.492494963763387,5.129134868989082
|
|
27
|
+
Late,48,5.067487553321051,5.042477656094307,4.6794104140856545
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
Treatment,time,B1,B2,B3
|
|
2
|
+
Early,0,0.19539234562313734,0.10477882730649994,0.2973846164284792
|
|
3
|
+
Early,4,0.2779771221561284,0.001055937,0.15111804708517929
|
|
4
|
+
Early,8,4.782679084451829,4.928558620981794,4.334643272
|
|
5
|
+
Early,12,7.695253302802565,7.486506183447338,7.670888582077174
|
|
6
|
+
Early,16,8.638920256993988,8.764901952880617,8.564767024924421
|
|
7
|
+
Early,20,10.068564764063265,9.200881089445513,9.537327159244036
|
|
8
|
+
Early,24,9.771893527990473,9.680509836078317,10.036671602844494
|
|
9
|
+
Early,28,9.766074610781745,9.724111693188496,9.633603887143744
|
|
10
|
+
Early,32,9.771636547315907,10.058348006501559,9.835037442469508
|
|
11
|
+
Early,36,9.741328028636595,9.841645089251156,10.324624435991593
|
|
12
|
+
Early,40,10.254511279938015,10.221952974860242,10.491660840441746
|
|
13
|
+
Early,44,9.786108617,9.761913566948376,10.119088405111514
|
|
14
|
+
Early,48,10.015206566912964,9.845875563835472,10.189985242581589
|
|
15
|
+
Late,0,0.059418625,0.43622598860070966,0.051827353
|
|
16
|
+
Late,4,0.032439186,0.29560196972284575,0.47402815513002483
|
|
17
|
+
Late,8,0.24482615492977228,0.33293636113115616,0.18588304790121768
|
|
18
|
+
Late,12,1.8331570082144877,1.5068034514622044,1.9884429742073217
|
|
19
|
+
Late,16,3.5858548374647796,3.7843160670685094,3.184916367764073
|
|
20
|
+
Late,20,4.454215165716751,3.9987812269179885,4.507405268303338
|
|
21
|
+
Late,24,5.213944309839263,4.7283469122991875,4.606607241602831
|
|
22
|
+
Late,28,4.607051917370045,4.827165416,4.678734126
|
|
23
|
+
Late,32,5.035079704004494,4.6585003935782145,4.863747375143898
|
|
24
|
+
Late,36,5.088830444826097,4.725803807279649,4.991999062797336
|
|
25
|
+
Late,40,4.657881902164966,4.586923631573959,4.980773876820317
|
|
26
|
+
Late,44,4.676904224019542,4.985150713710271,5.478033425560597
|
|
27
|
+
Late,48,5.070358496196444,5.466873701873418,5.129366196
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
Treatment,time,logB1,logB2,logB3,logA
|
|
2
|
+
Early,6,0.618307,0.592186,0.645986,0.87279
|
|
3
|
+
Early,9,1.02893,1.06471,1.01339,0.424712
|
|
4
|
+
Early,12,0.994658,0.983051,1.05973,-1.55534
|
|
5
|
+
Early,15,1.05152,0.983416,0.908111,-0.695098
|
|
6
|
+
Early,18,0.846355,0.974269,0.904413,
|
|
7
|
+
Early,21,0.723063,0.805485,0.751509,
|
|
8
|
+
Early,24,0.689824,0.696888,0.685623,
|
|
9
|
+
Early,27,0.658967,0.628144,0.601827,
|
|
10
|
+
Early,30,0.5611,0.582111,0.458467,
|
|
11
|
+
Early,33,0.436805,0.400604,0.440984,
|
|
12
|
+
Early,36,0.294066,0.359572,0.356643,
|
|
13
|
+
Early,39,0.25216,0.214981,0.247568,
|
|
14
|
+
Early,42,0.121627,0.108433,0.187433,
|
|
15
|
+
Early,45,0.029532,0.143514,0.120829,
|
|
16
|
+
Early,48,-0.10609,0.00740748,-0.0612848,
|
|
17
|
+
Late,12,0.558655,0.619544,0.512372,
|
|
18
|
+
Late,15,0.761926,0.670076,0.70846,
|
|
19
|
+
Late,18,0.665886,0.780346,0.73632,
|
|
20
|
+
Late,21,0.766747,0.677256,0.68741,
|
|
21
|
+
Late,24,0.607722,0.535357,0.562186,
|
|
22
|
+
Late,27,0.546973,0.459626,0.488894,
|
|
23
|
+
Late,30,0.389318,0.42084,0.372133,
|
|
24
|
+
Late,33,0.292474,0.312211,0.305243,
|
|
25
|
+
Late,36,0.166036,0.254074,0.144361,
|
|
26
|
+
Late,39,0.0581972,0.0541284,0.166602,
|
|
27
|
+
Late,42,0.00909416,-0.0214272,-0.0296765,
|
|
28
|
+
Late,45,-0.042989,-0.116788,-0.127649,
|
|
29
|
+
Late,48,-0.124534,-0.215069,-0.171356,
|