PyAntiGen 2.1.3__tar.gz → 2.1.5__tar.gz
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.
- {pyantigen-2.1.3 → pyantigen-2.1.5}/PKG-INFO +1 -1
- {pyantigen-2.1.3 → pyantigen-2.1.5}/PyAntiGen.egg-info/PKG-INFO +1 -1
- {pyantigen-2.1.3 → pyantigen-2.1.5}/PyAntiGen.egg-info/scm_file_list.json +2 -0
- pyantigen-2.1.5/PyAntiGen.egg-info/scm_version.json +8 -0
- {pyantigen-2.1.3 → pyantigen-2.1.5}/docs/ANALYTIC_GRADIENT.md +3 -0
- {pyantigen-2.1.3 → pyantigen-2.1.5}/pyantigen/_version.py +3 -3
- {pyantigen-2.1.3 → pyantigen-2.1.5}/pyantigen/engine/Analytic_gradient.py +14 -1
- {pyantigen-2.1.3 → pyantigen-2.1.5}/pyantigen/engine/Segmented.py +2 -2
- {pyantigen-2.1.3 → pyantigen-2.1.5}/pyantigen/engine/Simulate.py +147 -2
- {pyantigen-2.1.3 → pyantigen-2.1.5}/pyantigen/generate/scaled_time.py +47 -1
- pyantigen-2.1.3/PyAntiGen.egg-info/scm_version.json +0 -8
- {pyantigen-2.1.3 → pyantigen-2.1.5}/.github/workflows/publish.yml +0 -0
- {pyantigen-2.1.3 → pyantigen-2.1.5}/.github/workflows/tests.yml +0 -0
- {pyantigen-2.1.3 → pyantigen-2.1.5}/.gitignore +0 -0
- {pyantigen-2.1.3 → pyantigen-2.1.5}/.vscode/launch.json +0 -0
- {pyantigen-2.1.3 → pyantigen-2.1.5}/LICENSE +0 -0
- {pyantigen-2.1.3 → pyantigen-2.1.5}/MANIFEST.in +0 -0
- {pyantigen-2.1.3 → pyantigen-2.1.5}/PyAntiGen.egg-info/SOURCES.txt +0 -0
- {pyantigen-2.1.3 → pyantigen-2.1.5}/PyAntiGen.egg-info/dependency_links.txt +0 -0
- {pyantigen-2.1.3 → pyantigen-2.1.5}/PyAntiGen.egg-info/entry_points.txt +0 -0
- {pyantigen-2.1.3 → pyantigen-2.1.5}/PyAntiGen.egg-info/requires.txt +0 -0
- {pyantigen-2.1.3 → pyantigen-2.1.5}/PyAntiGen.egg-info/top_level.txt +0 -0
- {pyantigen-2.1.3 → pyantigen-2.1.5}/README.md +0 -0
- {pyantigen-2.1.3 → pyantigen-2.1.5}/docs/MULTISTART.md +0 -0
- {pyantigen-2.1.3 → pyantigen-2.1.5}/docs/PROTOCOL_LAYER.md +0 -0
- {pyantigen-2.1.3 → pyantigen-2.1.5}/docs/V2_DESIGN.md +0 -0
- {pyantigen-2.1.3 → pyantigen-2.1.5}/pyantigen/.agents/skills/module_generation/SKILL.md +0 -0
- {pyantigen-2.1.3 → pyantigen-2.1.5}/pyantigen/.agents/skills/ode_conversion_antimony/SKILL.md +0 -0
- {pyantigen-2.1.3 → pyantigen-2.1.5}/pyantigen/.agents/skills/ode_extraction_pipeline/SKILL.md +0 -0
- {pyantigen-2.1.3 → pyantigen-2.1.5}/pyantigen/.gitignore +0 -0
- {pyantigen-2.1.3 → pyantigen-2.1.5}/pyantigen/__init__.py +0 -0
- {pyantigen-2.1.3 → pyantigen-2.1.5}/pyantigen/cli.py +0 -0
- {pyantigen-2.1.3 → pyantigen-2.1.5}/pyantigen/engine/Anchor_cache.py +0 -0
- {pyantigen-2.1.3 → pyantigen-2.1.5}/pyantigen/engine/Deadline.py +0 -0
- {pyantigen-2.1.3 → pyantigen-2.1.5}/pyantigen/engine/Differential_evolution.py +0 -0
- {pyantigen-2.1.3 → pyantigen-2.1.5}/pyantigen/engine/Evaluator.py +0 -0
- {pyantigen-2.1.3 → pyantigen-2.1.5}/pyantigen/engine/Event_times.py +0 -0
- {pyantigen-2.1.3 → pyantigen-2.1.5}/pyantigen/engine/Fast_profile.py +0 -0
- {pyantigen-2.1.3 → pyantigen-2.1.5}/pyantigen/engine/Fit_cache.py +0 -0
- {pyantigen-2.1.3 → pyantigen-2.1.5}/pyantigen/engine/Identifiability.py +0 -0
- {pyantigen-2.1.3 → pyantigen-2.1.5}/pyantigen/engine/Model_cache.py +0 -0
- {pyantigen-2.1.3 → pyantigen-2.1.5}/pyantigen/engine/Model_optimize.py +0 -0
- {pyantigen-2.1.3 → pyantigen-2.1.5}/pyantigen/engine/Model_simulate.py +0 -0
- {pyantigen-2.1.3 → pyantigen-2.1.5}/pyantigen/engine/Multistart.py +0 -0
- {pyantigen-2.1.3 → pyantigen-2.1.5}/pyantigen/engine/Nelder_mead.py +0 -0
- {pyantigen-2.1.3 → pyantigen-2.1.5}/pyantigen/engine/Noise_floor.py +0 -0
- {pyantigen-2.1.3 → pyantigen-2.1.5}/pyantigen/engine/Optimize.py +0 -0
- {pyantigen-2.1.3 → pyantigen-2.1.5}/pyantigen/engine/Petab_export.py +0 -0
- {pyantigen-2.1.3 → pyantigen-2.1.5}/pyantigen/engine/Preequil_cache.py +0 -0
- {pyantigen-2.1.3 → pyantigen-2.1.5}/pyantigen/engine/Profile_checkpoint.py +0 -0
- {pyantigen-2.1.3 → pyantigen-2.1.5}/pyantigen/engine/Profile_plan.py +0 -0
- {pyantigen-2.1.3 → pyantigen-2.1.5}/pyantigen/engine/README.md +0 -0
- {pyantigen-2.1.3 → pyantigen-2.1.5}/pyantigen/engine/Results.py +0 -0
- {pyantigen-2.1.3 → pyantigen-2.1.5}/pyantigen/engine/Scaled_amici.py +0 -0
- {pyantigen-2.1.3 → pyantigen-2.1.5}/pyantigen/engine/Sensitivity_analysis.py +0 -0
- {pyantigen-2.1.3 → pyantigen-2.1.5}/pyantigen/engine/Stop_rules.py +0 -0
- {pyantigen-2.1.3 → pyantigen-2.1.5}/pyantigen/engine/__init__.py +0 -0
- {pyantigen-2.1.3 → pyantigen-2.1.5}/pyantigen/generate/AntimonyGen.py +0 -0
- {pyantigen-2.1.3 → pyantigen-2.1.5}/pyantigen/generate/RxnDict_to_antimony.py +0 -0
- {pyantigen-2.1.3 → pyantigen-2.1.5}/pyantigen/generate/TelluriumGen.py +0 -0
- {pyantigen-2.1.3 → pyantigen-2.1.5}/pyantigen/generate/__init__.py +0 -0
- {pyantigen-2.1.3 → pyantigen-2.1.5}/pyantigen/generate/antimony_utils.py +0 -0
- {pyantigen-2.1.3 → pyantigen-2.1.5}/pyantigen/generate/data_interpolation.py +0 -0
- {pyantigen-2.1.3 → pyantigen-2.1.5}/pyantigen/generate/input_schedule.py +0 -0
- {pyantigen-2.1.3 → pyantigen-2.1.5}/pyantigen/generate/isotopomer_tools.py +0 -0
- {pyantigen-2.1.3 → pyantigen-2.1.5}/pyantigen/generate/model_generation.py +0 -0
- {pyantigen-2.1.3 → pyantigen-2.1.5}/pyantigen/generate/models.py +0 -0
- {pyantigen-2.1.3 → pyantigen-2.1.5}/pyantigen/generate/module_base.py +0 -0
- {pyantigen-2.1.3 → pyantigen-2.1.5}/pyantigen/generate/pyantigen.py +0 -0
- {pyantigen-2.1.3 → pyantigen-2.1.5}/pyantigen/generate/rate_laws.py +0 -0
- {pyantigen-2.1.3 → pyantigen-2.1.5}/pyantigen/generate/reaction_creation.py +0 -0
- {pyantigen-2.1.3 → pyantigen-2.1.5}/pyantigen/study/__init__.py +0 -0
- {pyantigen-2.1.3 → pyantigen-2.1.5}/pyantigen/study/assay.py +0 -0
- {pyantigen-2.1.3 → pyantigen-2.1.5}/pyantigen/study/describe.py +0 -0
- {pyantigen-2.1.3 → pyantigen-2.1.5}/pyantigen/study/design.py +0 -0
- {pyantigen-2.1.3 → pyantigen-2.1.5}/pyantigen/study/lower_v1.py +0 -0
- {pyantigen-2.1.3 → pyantigen-2.1.5}/pyantigen/study/optimization.py +0 -0
- {pyantigen-2.1.3 → pyantigen-2.1.5}/pyantigen/study/params.py +0 -0
- {pyantigen-2.1.3 → pyantigen-2.1.5}/pyantigen/study/quantity.py +0 -0
- {pyantigen-2.1.3 → pyantigen-2.1.5}/pyantigen/study/reagent.py +0 -0
- {pyantigen-2.1.3 → pyantigen-2.1.5}/pyantigen/study/refs.py +0 -0
- {pyantigen-2.1.3 → pyantigen-2.1.5}/pyantigen/study/remarks.py +0 -0
- {pyantigen-2.1.3 → pyantigen-2.1.5}/pyantigen/study/serialize.py +0 -0
- {pyantigen-2.1.3 → pyantigen-2.1.5}/pyantigen/study/validate.py +0 -0
- {pyantigen-2.1.3 → pyantigen-2.1.5}/pyantigen/template/Example/AntiGen_paths.py +0 -0
- {pyantigen-2.1.3 → pyantigen-2.1.5}/pyantigen/template/Example/Flipflop_reference.py +0 -0
- {pyantigen-2.1.3 → pyantigen-2.1.5}/pyantigen/template/Example/Model_generate.py +0 -0
- {pyantigen-2.1.3 → pyantigen-2.1.5}/pyantigen/template/Example/Model_run.py +0 -0
- {pyantigen-2.1.3 → pyantigen-2.1.5}/pyantigen/template/Example/Modules/Data.py +0 -0
- {pyantigen-2.1.3 → pyantigen-2.1.5}/pyantigen/template/Example/Modules/Events.py +0 -0
- {pyantigen-2.1.3 → pyantigen-2.1.5}/pyantigen/template/Example/Modules/Experiment.py +0 -0
- {pyantigen-2.1.3 → pyantigen-2.1.5}/pyantigen/template/Example/Modules/Loss_config.py +0 -0
- {pyantigen-2.1.3 → pyantigen-2.1.5}/pyantigen/template/Example/Modules/Observed_species.py +0 -0
- {pyantigen-2.1.3 → pyantigen-2.1.5}/pyantigen/template/Example/Modules/Optimizer_settings.py +0 -0
- {pyantigen-2.1.3 → pyantigen-2.1.5}/pyantigen/template/Example/Modules/Plots.py +0 -0
- {pyantigen-2.1.3 → pyantigen-2.1.5}/pyantigen/template/Example/Modules/Solver_settings.py +0 -0
- {pyantigen-2.1.3 → pyantigen-2.1.5}/pyantigen/template/Example/Modules/Update_opt_parameters.py +0 -0
- {pyantigen-2.1.3 → pyantigen-2.1.5}/pyantigen/template/Example/Modules/Update_parameters.py +0 -0
- {pyantigen-2.1.3 → pyantigen-2.1.5}/pyantigen/template/Example/optimizations/Example1_ADneg.json +0 -0
- {pyantigen-2.1.3 → pyantigen-2.1.5}/pyantigen/template/Example/optimizations/Example1_ADpos.json +0 -0
- {pyantigen-2.1.3 → pyantigen-2.1.5}/pyantigen/template/Example/optimizations/Example3_joint.json +0 -0
- {pyantigen-2.1.3 → pyantigen-2.1.5}/pyantigen/template/Example/optimizations/Example4_flipflop.json +0 -0
- {pyantigen-2.1.3 → pyantigen-2.1.5}/pyantigen/template/Example/optimizations/example.py +0 -0
- {pyantigen-2.1.3 → pyantigen-2.1.5}/pyantigen/template/Example/studies/example.json +0 -0
- {pyantigen-2.1.3 → pyantigen-2.1.5}/pyantigen/template/Example/studies/example.py +0 -0
- {pyantigen-2.1.3 → pyantigen-2.1.5}/pyantigen/template/Example/studies/flipflop.json +0 -0
- {pyantigen-2.1.3 → pyantigen-2.1.5}/pyantigen/template/data/ADneg.csv +0 -0
- {pyantigen-2.1.3 → pyantigen-2.1.5}/pyantigen/template/data/ADpos.csv +0 -0
- {pyantigen-2.1.3 → pyantigen-2.1.5}/pyantigen/template/data/Flipflop.csv +0 -0
- {pyantigen-2.1.3 → pyantigen-2.1.5}/pyantigen/template/data/make_flipflop_data.py +0 -0
- {pyantigen-2.1.3 → pyantigen-2.1.5}/pyproject.toml +0 -0
- {pyantigen-2.1.3 → pyantigen-2.1.5}/setup.cfg +0 -0
|
@@ -103,6 +103,7 @@
|
|
|
103
103
|
"tests/README.md",
|
|
104
104
|
"tests/__init__.py",
|
|
105
105
|
"tests/conftest.py",
|
|
106
|
+
"tests/engine/test_amici_flux_outputs.py",
|
|
106
107
|
"tests/engine/test_amici_jumps.py",
|
|
107
108
|
"tests/engine/test_amici_sensitivity.py",
|
|
108
109
|
"tests/engine/test_analytic_gradient.py",
|
|
@@ -142,6 +143,7 @@
|
|
|
142
143
|
"tests/engine/test_solver_fingerprint.py",
|
|
143
144
|
"tests/engine/test_stop_rules.py",
|
|
144
145
|
"tests/engine/test_tolerance_floor.py",
|
|
146
|
+
"tests/engine/test_tolerance_pinning.py",
|
|
145
147
|
"tests/engine/test_wald_units.py",
|
|
146
148
|
"tests/generate/test_gen_antimony_utils.py",
|
|
147
149
|
"tests/generate/test_gen_data_interpolation.py",
|
|
@@ -123,6 +123,9 @@ dynamics read them.
|
|
|
123
123
|
* Columns that are not species, parameters or compartments (reaction rates) are not available to the
|
|
124
124
|
loss; the gradient is then zero for anything that depends only on them.
|
|
125
125
|
* The profile-likelihood stage keeps its own optimizers.
|
|
126
|
+
The gradient can differentiate what a profile point minimizes: `provider.freeze_sigmas(block_sigmas)` pins the floored blocks'
|
|
127
|
+
sigmas as `evaluate_nll_fixed(frozen_sigmas=...)` does (without it the gradient is off by 10 to 30% away from the fit);
|
|
128
|
+
`freeze_sigmas(None)` releases them. Nothing calls it yet.
|
|
126
129
|
* Cost: the gradient costs one AMICI forward-sensitivity run, which grows with the number of
|
|
127
130
|
parameters it differentiates. On SILK, 8 parameters: about 1.2 s a gradient against 9 objective
|
|
128
131
|
evaluations (about 1.4 s) for forward differences; with all 48 parameters AMICI needs about 40 s a
|
|
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
|
|
|
18
18
|
commit_id: str | None
|
|
19
19
|
__commit_id__: str | None
|
|
20
20
|
|
|
21
|
-
__version__ = version = '2.1.
|
|
22
|
-
__version_tuple__ = version_tuple = (2, 1,
|
|
21
|
+
__version__ = version = '2.1.5'
|
|
22
|
+
__version_tuple__ = version_tuple = (2, 1, 5)
|
|
23
23
|
|
|
24
|
-
__commit_id__ = commit_id = '
|
|
24
|
+
__commit_id__ = commit_id = 'g244c235d3'
|
|
@@ -91,6 +91,8 @@ class LossGradient:
|
|
|
91
91
|
self.param_names = list(param_names)
|
|
92
92
|
self.rel_step = float(rel_step)
|
|
93
93
|
self.explicit = self._explicit_parameters()
|
|
94
|
+
# {(block, observable): sigma}: the floored blocks' sigmas a profile point holds still (see freeze_sigmas)
|
|
95
|
+
self.frozen_sigmas = None
|
|
94
96
|
|
|
95
97
|
# -- which parameters the loss reads by name ------------------------------------
|
|
96
98
|
def _explicit_parameters(self):
|
|
@@ -123,7 +125,8 @@ class LossGradient:
|
|
|
123
125
|
from pyantigen.engine.Optimize import collect_loss_blocks, concentrated_nll
|
|
124
126
|
p_dict = dict(zip(self.param_names, np.asarray(p_lin, dtype=float).tolist()))
|
|
125
127
|
blocks = collect_loss_blocks(items, self.groups, self.replicates, self.param_names,
|
|
126
|
-
np.asarray(p_lin, dtype=float), p_dict
|
|
128
|
+
np.asarray(p_lin, dtype=float), p_dict,
|
|
129
|
+
frozen_sigmas=self.frozen_sigmas)
|
|
127
130
|
if not blocks:
|
|
128
131
|
raise AnalyticGradientError("the loss has no data to score")
|
|
129
132
|
v = concentrated_nll(blocks)
|
|
@@ -581,6 +584,16 @@ class AmiciGradient:
|
|
|
581
584
|
points += [p for p in (lo, hi) if np.all(np.isfinite(p)) and not np.array_equal(p, x0)]
|
|
582
585
|
return points
|
|
583
586
|
|
|
587
|
+
def freeze_sigmas(self, sigmas):
|
|
588
|
+
"""Differentiate the loss a profile point minimizes: floored blocks keep the sigma they had at the fit.
|
|
589
|
+
|
|
590
|
+
*sigmas* is what ``Optimize.block_sigmas`` returns for the fit (``{(block, observable): sigma_used}``),
|
|
591
|
+
the same dictionary the profile passes to ``evaluate_nll_fixed(frozen_sigmas=...)``. A frozen block is
|
|
592
|
+
scored with that sigma instead of ``min(sigma_hat, floor)`` at the current point, which changes its
|
|
593
|
+
derivative (the sigma no longer moves with the parameters). ``None`` releases them.
|
|
594
|
+
"""
|
|
595
|
+
self.loss.frozen_sigmas = dict(sigmas) if sigmas else None
|
|
596
|
+
|
|
584
597
|
def linear_gradient(self, p_lin):
|
|
585
598
|
"""``(nll, d nll / d p_lin)`` evaluated on AMICI's own simulation."""
|
|
586
599
|
items, dirs = {}, {}
|
|
@@ -44,7 +44,7 @@ caches separately must not contain a switch: ``switches_before`` says whether it
|
|
|
44
44
|
"""
|
|
45
45
|
import numpy as np
|
|
46
46
|
|
|
47
|
-
from pyantigen.engine.Simulate import StackedResult,
|
|
47
|
+
from pyantigen.engine.Simulate import StackedResult, apply_tolerance_vector, safe_simulate
|
|
48
48
|
from pyantigen.generate.input_schedule import ScheduleError
|
|
49
49
|
|
|
50
50
|
|
|
@@ -154,7 +154,7 @@ class SegmentRunner:
|
|
|
154
154
|
between = mine[(mine > u + eu) & (mine < v - ev)]
|
|
155
155
|
times = [u] + between.tolist() + [v]
|
|
156
156
|
|
|
157
|
-
|
|
157
|
+
apply_tolerance_vector(self.r)
|
|
158
158
|
res, _meta = safe_simulate(
|
|
159
159
|
self.r, {"times": times, "variable_step_size": False},
|
|
160
160
|
observed_species, label=label)
|
|
@@ -233,6 +233,150 @@ def floor_tolerance_vector(r, floor=_MIN_ABSOLUTE_TOLERANCE):
|
|
|
233
233
|
return n
|
|
234
234
|
|
|
235
235
|
|
|
236
|
+
# ---------------------------------------------------------------------------
|
|
237
|
+
# Pinning the vector, not only flooring it
|
|
238
|
+
# ---------------------------------------------------------------------------
|
|
239
|
+
# floor_tolerance_vector raises what has fallen below the floor and leaves
|
|
240
|
+
# everything else as RoadRunner has it. That is enough for a vector that has
|
|
241
|
+
# drifted DOWN, which is what the floor was written for. It does nothing about
|
|
242
|
+
# two other things:
|
|
243
|
+
#
|
|
244
|
+
# * The retry ladder sets the scalar before every attempt, and setting the
|
|
245
|
+
# scalar re-derives the whole vector from the amounts the model holds
|
|
246
|
+
# *at that moment* -- the failing state, not the initial one. Measured on
|
|
247
|
+
# Aducanumab 60 mg/kg: between the segment-start vector and the vector of
|
|
248
|
+
# attempt 2, 176 entries were more than 10x larger and 62 more than 10x
|
|
249
|
+
# smaller (ratios 1.7e-11 .. 2.9e+07). The floor raised 3 of them. So the
|
|
250
|
+
# loosened retries do not run at "the same tolerances, looser"; they run at
|
|
251
|
+
# tolerances derived from wherever the integration died.
|
|
252
|
+
#
|
|
253
|
+
# * RoadRunner's initial-derived scale is atol_i = scalar * amount_i, or
|
|
254
|
+
# scalar * volume_i for a species that starts at 0 (see the note above
|
|
255
|
+
# _MIN_ABSOLUTE_TOLERANCE). Every species that starts empty therefore gets
|
|
256
|
+
# one number, scalar * its compartment volume -- 6.7e-11 at scalar 1e-9 for
|
|
257
|
+
# the 0.067-volume endosome -- however small it later runs. After the FcRn
|
|
258
|
+
# saturation front of a 60 mg/kg dose the endosomal antibody species sit at
|
|
259
|
+
# 1.6e-9 .. 1.3e-8, so that budget is 0.5-4% of their value, and CVODE fails
|
|
260
|
+
# (CV_ILL_INPUT / CV_TOO_MUCH_ACC) ~200 h after the dose. Lowering the scalar
|
|
261
|
+
# 10x, pinning those five species at 1e-15, or capping every entry at 1e-12
|
|
262
|
+
# each took the displaced-parameter failures from 6/40 to 0/40.
|
|
263
|
+
#
|
|
264
|
+
# Pinning keeps ONE reference -- the per-state scale RoadRunner derives at
|
|
265
|
+
# configure_integrator when the model is at its initial time -- and from it
|
|
266
|
+
# sets every entry explicitly before each block, each segment and each ladder
|
|
267
|
+
# attempt:
|
|
268
|
+
#
|
|
269
|
+
# atol_i = clip( scalar * scale_i , floor , cap_ratio * scalar )
|
|
270
|
+
#
|
|
271
|
+
# *scalar* is the configured one, or the ladder's current (loosened) one, so a
|
|
272
|
+
# retry scales the same vector instead of re-deriving it. The cap is relative
|
|
273
|
+
# to the scalar for the same reason: an absolute cap would undo the ladder's
|
|
274
|
+
# loosening. cap_ratio is optional; without it pinning only restores the
|
|
275
|
+
# initial-derived vector.
|
|
276
|
+
#
|
|
277
|
+
# Opt in per replicate through the solver settings:
|
|
278
|
+
#
|
|
279
|
+
# "tolerance_pinning": True, # default TOLERANCE_PINNING (False)
|
|
280
|
+
# "tolerance_cap_ratio": 1e-3, # default TOLERANCE_CAP_RATIO (None)
|
|
281
|
+
#
|
|
282
|
+
# Both are plain values in the settings dict, so Profile_checkpoint's solver
|
|
283
|
+
# fingerprint already covers them: switching them on starts a new checkpoint
|
|
284
|
+
# directory rather than mixing two objectives. Left off, apply_tolerance_vector
|
|
285
|
+
# is floor_tolerance_vector and nothing changes.
|
|
286
|
+
TOLERANCE_PINNING = False
|
|
287
|
+
TOLERANCE_CAP_RATIO = None
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
def tolerance_policy(solver_settings):
|
|
291
|
+
"""``{"pin": bool, "cap_ratio": float | None}`` from *solver_settings*."""
|
|
292
|
+
pin = bool(solver_settings.get("tolerance_pinning", TOLERANCE_PINNING))
|
|
293
|
+
cap = solver_settings.get("tolerance_cap_ratio", TOLERANCE_CAP_RATIO)
|
|
294
|
+
if cap is not None:
|
|
295
|
+
cap = float(cap)
|
|
296
|
+
if not cap > 0:
|
|
297
|
+
raise ValueError(f"tolerance_cap_ratio must be positive, got {cap}")
|
|
298
|
+
if not pin:
|
|
299
|
+
raise ValueError("tolerance_cap_ratio needs tolerance_pinning: a cap is "
|
|
300
|
+
"applied to the pinned vector")
|
|
301
|
+
return {"pin": pin, "cap_ratio": cap}
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
def _remember_tolerance_policy(r, solver_settings, scalar):
|
|
305
|
+
"""Cache the policy on *r* and, at the model's initial time, the reference scale.
|
|
306
|
+
|
|
307
|
+
Called by configure_integrator right after the scalar is set, when RoadRunner
|
|
308
|
+
has just derived the vector. The scale is only taken at time 0: a second
|
|
309
|
+
configure on a state restored from the pre-dose cache would derive it from
|
|
310
|
+
amounts that have already decayed, which is the drift this exists to undo.
|
|
311
|
+
"""
|
|
312
|
+
policy = tolerance_policy(solver_settings)
|
|
313
|
+
if not policy["pin"]:
|
|
314
|
+
_model_cache.put(r, "_tolerance_policy", None)
|
|
315
|
+
return
|
|
316
|
+
try:
|
|
317
|
+
at_start = float(r.getCurrentTime()) == 0.0
|
|
318
|
+
except Exception:
|
|
319
|
+
at_start = False
|
|
320
|
+
if at_start:
|
|
321
|
+
try:
|
|
322
|
+
vec = np.asarray(r.integrator.getAbsoluteToleranceVector(), float)
|
|
323
|
+
ids = state_vector_ids(r)
|
|
324
|
+
n = min(len(ids), len(vec))
|
|
325
|
+
if float(scalar) > 0 and n:
|
|
326
|
+
_model_cache.put(r, "_atol_reference",
|
|
327
|
+
(list(ids[:n]), vec[:n] / float(scalar)))
|
|
328
|
+
except Exception:
|
|
329
|
+
pass
|
|
330
|
+
if _model_cache.get(r, "_atol_reference") is None:
|
|
331
|
+
# Never saw the initial state, so there is no initial scale to pin to.
|
|
332
|
+
import warnings
|
|
333
|
+
warnings.warn("tolerance_pinning requested but the model was first configured "
|
|
334
|
+
"away from its initial time; falling back to the floor",
|
|
335
|
+
RuntimeWarning, stacklevel=3)
|
|
336
|
+
_model_cache.put(r, "_tolerance_policy", None)
|
|
337
|
+
return
|
|
338
|
+
_model_cache.put(r, "_tolerance_policy", policy)
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
def apply_tolerance_vector(r, scalar=None, floor=_MIN_ABSOLUTE_TOLERANCE):
|
|
342
|
+
"""Set the per-state absolute tolerances for the next integration.
|
|
343
|
+
|
|
344
|
+
Pinning off (the default): exactly :func:`floor_tolerance_vector`. Pinning
|
|
345
|
+
on: every entry is set to ``clip(scalar * scale_i, floor, cap_ratio * scalar)``
|
|
346
|
+
from the reference scale. *scalar* defaults to the configured absolute
|
|
347
|
+
tolerance; the retry ladder passes its current, loosened one. Returns the
|
|
348
|
+
number of entries set.
|
|
349
|
+
"""
|
|
350
|
+
policy = _model_cache.get(r, "_tolerance_policy")
|
|
351
|
+
ref = _model_cache.get(r, "_atol_reference") if policy else None
|
|
352
|
+
if ref is None:
|
|
353
|
+
return floor_tolerance_vector(r, floor)
|
|
354
|
+
if scalar is None:
|
|
355
|
+
scalar = _model_cache.get(r, "_scalar_abs_tol")
|
|
356
|
+
if scalar is None:
|
|
357
|
+
return floor_tolerance_vector(r, floor)
|
|
358
|
+
ids, scale = ref
|
|
359
|
+
try:
|
|
360
|
+
current = np.asarray(r.integrator.getAbsoluteToleranceVector(), float)
|
|
361
|
+
except Exception:
|
|
362
|
+
return 0
|
|
363
|
+
if len(current) < len(ids):
|
|
364
|
+
return floor_tolerance_vector(r, floor)
|
|
365
|
+
scalar = float(scalar)
|
|
366
|
+
want = scalar * scale
|
|
367
|
+
if policy["cap_ratio"] is not None:
|
|
368
|
+
want = np.minimum(want, policy["cap_ratio"] * scalar)
|
|
369
|
+
want = np.maximum(want, floor)
|
|
370
|
+
n = 0
|
|
371
|
+
for i in np.flatnonzero(~np.isclose(current[:len(ids)], want, rtol=1e-9, atol=0.0)):
|
|
372
|
+
try:
|
|
373
|
+
r.integrator.setIndividualTolerance(ids[i], float(want[i]))
|
|
374
|
+
n += 1
|
|
375
|
+
except Exception:
|
|
376
|
+
pass
|
|
377
|
+
return n
|
|
378
|
+
|
|
379
|
+
|
|
236
380
|
class StackedResult(np.ndarray):
|
|
237
381
|
def __new__(cls, input_array, colnames):
|
|
238
382
|
obj = np.asarray(input_array).view(cls)
|
|
@@ -653,7 +797,7 @@ def safe_simulate(r, solver_settings, observed_species, depth=0, label=None):
|
|
|
653
797
|
# state and discards any individual settings, so the floor has to be
|
|
654
798
|
# re-applied here or every retry would run without it -- including the
|
|
655
799
|
# retries provoked by its absence.
|
|
656
|
-
|
|
800
|
+
apply_tolerance_vector(r, scalar=cur_abs_tol)
|
|
657
801
|
if cur_initial_step is not None:
|
|
658
802
|
try:
|
|
659
803
|
r.integrator.initial_time_step = cur_initial_step
|
|
@@ -822,6 +966,7 @@ def configure_integrator(r, solver_settings):
|
|
|
822
966
|
r.integrator.variable_step_size = solver_settings["variable_step_size"]
|
|
823
967
|
# r.integrator.setValue('initial_time_step', solver_settings["initial_time_step"])
|
|
824
968
|
r.integrator.setValue('maximum_num_steps', solver_settings["maximum_num_steps"])
|
|
969
|
+
_remember_tolerance_policy(r, solver_settings, atol)
|
|
825
970
|
return solver_settings
|
|
826
971
|
|
|
827
972
|
|
|
@@ -891,7 +1036,7 @@ def _run_blocks(r, block_list, last_tracked_idx, observed_species, label, runner
|
|
|
891
1036
|
# tolerance vector is derived from the current state, so it is healthy
|
|
892
1037
|
# here at t=0 and underflows as the trajectory decays. See
|
|
893
1038
|
# _MIN_ABSOLUTE_TOLERANCE.
|
|
894
|
-
|
|
1039
|
+
apply_tolerance_vector(r)
|
|
895
1040
|
|
|
896
1041
|
try:
|
|
897
1042
|
if runner is not None:
|
|
@@ -23,7 +23,8 @@ What this module does to the model
|
|
|
23
23
|
from per-segment constants ``sched_a0/a1`` and ``sched_c0_<m>/c1_<m>`` (the
|
|
24
24
|
anchors and coefficients of the segment's two end points). The solver sees
|
|
25
25
|
``D`` as a function of the parameters.
|
|
26
|
-
* Every reaction rate and every rate rule is multiplied by ``sched_Delta
|
|
26
|
+
* Every reaction rate and every rate rule is multiplied by ``sched_Delta``; a formula that reads a reaction id
|
|
27
|
+
(a rule that sums fluxes) reads ``id/sched_Delta``, the flux of the unscaled model.
|
|
27
28
|
* A measured curve ``c`` (an assignment rule in the model) becomes
|
|
28
29
|
``sched_ci_c + sched_cs_c*sched_clock``: on one segment it is a straight line.
|
|
29
30
|
* Each parameter the schedule assigns (``Q_Leak``, ...) becomes a state with rate
|
|
@@ -133,6 +134,32 @@ def _with_clock(node):
|
|
|
133
134
|
return _parse(_TIME_TOKEN.sub(CLOCK, libsbml.formulaToL3String(node)))
|
|
134
135
|
|
|
135
136
|
|
|
137
|
+
_TOKEN = re.compile(r"(?<![A-Za-z0-9_.])[A-Za-z_][A-Za-z0-9_]*")
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def _reads_any(node, names):
|
|
141
|
+
if node.isName() and node.getName() in names:
|
|
142
|
+
return True
|
|
143
|
+
return any(_reads_any(node.getChild(i), names) for i in range(node.getNumChildren()))
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def _with_true_fluxes(node, reaction_ids):
|
|
147
|
+
"""*node*'s mathematics with each reaction id read as the flux of the original time, as a new tree; None if it reads none.
|
|
148
|
+
|
|
149
|
+
A reaction id in a formula means the reaction's rate. Step 3 of :func:`scale_model` multiplies every rate law by
|
|
150
|
+
``sched_Delta``, so a rule that sums fluxes (``BBB_transport := BBBTransport_AB42 + ...``) would read
|
|
151
|
+
``Delta`` times the flux, and an output or a rate rule built on it would be wrong by the length of the segment.
|
|
152
|
+
Reading ``(R/sched_Delta)`` instead gives the rule the flux it has in the unscaled model, and the solver
|
|
153
|
+
differentiates the quotient, ``dDelta/dp`` included. Done on the formula text, for the reason given in
|
|
154
|
+
:func:`_with_clock`.
|
|
155
|
+
"""
|
|
156
|
+
if not _reads_any(node, reaction_ids):
|
|
157
|
+
return None
|
|
158
|
+
text = _TOKEN.sub(lambda m: f"({m.group(0)}/{DELTA})" if m.group(0) in reaction_ids else m.group(0),
|
|
159
|
+
libsbml.formulaToL3String(node))
|
|
160
|
+
return _parse(text)
|
|
161
|
+
|
|
162
|
+
|
|
136
163
|
def _expand_initial(model, text, depth=0):
|
|
137
164
|
"""*text* with every parameter that has an initial assignment replaced by that assignment.
|
|
138
165
|
|
|
@@ -221,6 +248,25 @@ def scale_model(sbml, schedule):
|
|
|
221
248
|
_add_parameter(model, f"{PREFIX}cs_{c}")
|
|
222
249
|
rule.setMath(_parse(f"{PREFIX}ci_{c} + {PREFIX}cs_{c}*{CLOCK}"))
|
|
223
250
|
|
|
251
|
+
# 3a. a formula that reads a reaction id reads that reaction's rate: keep it the unscaled one
|
|
252
|
+
reaction_ids = {model.getReaction(i).getId() for i in range(model.getNumReactions())}
|
|
253
|
+
if reaction_ids:
|
|
254
|
+
for i in range(model.getNumRules()):
|
|
255
|
+
rule = model.getRule(i)
|
|
256
|
+
new = _with_true_fluxes(rule.getMath(), reaction_ids)
|
|
257
|
+
if new is not None:
|
|
258
|
+
rule.setMath(new)
|
|
259
|
+
for i in range(model.getNumReactions()):
|
|
260
|
+
kl = model.getReaction(i).getKineticLaw()
|
|
261
|
+
new = _with_true_fluxes(kl.getMath(), reaction_ids) if kl is not None and kl.isSetMath() else None
|
|
262
|
+
if new is not None:
|
|
263
|
+
kl.setMath(new)
|
|
264
|
+
for i in range(model.getNumInitialAssignments()):
|
|
265
|
+
assignment = model.getInitialAssignment(i)
|
|
266
|
+
new = _with_true_fluxes(assignment.getMath(), reaction_ids)
|
|
267
|
+
if new is not None:
|
|
268
|
+
assignment.setMath(new)
|
|
269
|
+
|
|
224
270
|
# 3. rates times Delta (before the new rules, which must not be scaled)
|
|
225
271
|
delta = lambda: _name_node(DELTA) # noqa: E731
|
|
226
272
|
for i in range(model.getNumReactions()):
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pyantigen-2.1.3 → pyantigen-2.1.5}/pyantigen/.agents/skills/ode_conversion_antimony/SKILL.md
RENAMED
|
File without changes
|
{pyantigen-2.1.3 → pyantigen-2.1.5}/pyantigen/.agents/skills/ode_extraction_pipeline/SKILL.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pyantigen-2.1.3 → pyantigen-2.1.5}/pyantigen/template/Example/Modules/Optimizer_settings.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pyantigen-2.1.3 → pyantigen-2.1.5}/pyantigen/template/Example/Modules/Update_opt_parameters.py
RENAMED
|
File without changes
|
|
File without changes
|
{pyantigen-2.1.3 → pyantigen-2.1.5}/pyantigen/template/Example/optimizations/Example1_ADneg.json
RENAMED
|
File without changes
|
{pyantigen-2.1.3 → pyantigen-2.1.5}/pyantigen/template/Example/optimizations/Example1_ADpos.json
RENAMED
|
File without changes
|
{pyantigen-2.1.3 → pyantigen-2.1.5}/pyantigen/template/Example/optimizations/Example3_joint.json
RENAMED
|
File without changes
|
{pyantigen-2.1.3 → pyantigen-2.1.5}/pyantigen/template/Example/optimizations/Example4_flipflop.json
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|