PyAntiGen 2.1.4__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.
Files changed (112) hide show
  1. {pyantigen-2.1.4 → pyantigen-2.1.5}/PKG-INFO +1 -1
  2. {pyantigen-2.1.4 → pyantigen-2.1.5}/PyAntiGen.egg-info/PKG-INFO +1 -1
  3. {pyantigen-2.1.4 → pyantigen-2.1.5}/PyAntiGen.egg-info/scm_file_list.json +1 -0
  4. pyantigen-2.1.5/PyAntiGen.egg-info/scm_version.json +8 -0
  5. {pyantigen-2.1.4 → pyantigen-2.1.5}/pyantigen/_version.py +3 -3
  6. {pyantigen-2.1.4 → pyantigen-2.1.5}/pyantigen/engine/Segmented.py +2 -2
  7. {pyantigen-2.1.4 → pyantigen-2.1.5}/pyantigen/engine/Simulate.py +147 -2
  8. pyantigen-2.1.4/PyAntiGen.egg-info/scm_version.json +0 -8
  9. {pyantigen-2.1.4 → pyantigen-2.1.5}/.github/workflows/publish.yml +0 -0
  10. {pyantigen-2.1.4 → pyantigen-2.1.5}/.github/workflows/tests.yml +0 -0
  11. {pyantigen-2.1.4 → pyantigen-2.1.5}/.gitignore +0 -0
  12. {pyantigen-2.1.4 → pyantigen-2.1.5}/.vscode/launch.json +0 -0
  13. {pyantigen-2.1.4 → pyantigen-2.1.5}/LICENSE +0 -0
  14. {pyantigen-2.1.4 → pyantigen-2.1.5}/MANIFEST.in +0 -0
  15. {pyantigen-2.1.4 → pyantigen-2.1.5}/PyAntiGen.egg-info/SOURCES.txt +0 -0
  16. {pyantigen-2.1.4 → pyantigen-2.1.5}/PyAntiGen.egg-info/dependency_links.txt +0 -0
  17. {pyantigen-2.1.4 → pyantigen-2.1.5}/PyAntiGen.egg-info/entry_points.txt +0 -0
  18. {pyantigen-2.1.4 → pyantigen-2.1.5}/PyAntiGen.egg-info/requires.txt +0 -0
  19. {pyantigen-2.1.4 → pyantigen-2.1.5}/PyAntiGen.egg-info/top_level.txt +0 -0
  20. {pyantigen-2.1.4 → pyantigen-2.1.5}/README.md +0 -0
  21. {pyantigen-2.1.4 → pyantigen-2.1.5}/docs/ANALYTIC_GRADIENT.md +0 -0
  22. {pyantigen-2.1.4 → pyantigen-2.1.5}/docs/MULTISTART.md +0 -0
  23. {pyantigen-2.1.4 → pyantigen-2.1.5}/docs/PROTOCOL_LAYER.md +0 -0
  24. {pyantigen-2.1.4 → pyantigen-2.1.5}/docs/V2_DESIGN.md +0 -0
  25. {pyantigen-2.1.4 → pyantigen-2.1.5}/pyantigen/.agents/skills/module_generation/SKILL.md +0 -0
  26. {pyantigen-2.1.4 → pyantigen-2.1.5}/pyantigen/.agents/skills/ode_conversion_antimony/SKILL.md +0 -0
  27. {pyantigen-2.1.4 → pyantigen-2.1.5}/pyantigen/.agents/skills/ode_extraction_pipeline/SKILL.md +0 -0
  28. {pyantigen-2.1.4 → pyantigen-2.1.5}/pyantigen/.gitignore +0 -0
  29. {pyantigen-2.1.4 → pyantigen-2.1.5}/pyantigen/__init__.py +0 -0
  30. {pyantigen-2.1.4 → pyantigen-2.1.5}/pyantigen/cli.py +0 -0
  31. {pyantigen-2.1.4 → pyantigen-2.1.5}/pyantigen/engine/Analytic_gradient.py +0 -0
  32. {pyantigen-2.1.4 → pyantigen-2.1.5}/pyantigen/engine/Anchor_cache.py +0 -0
  33. {pyantigen-2.1.4 → pyantigen-2.1.5}/pyantigen/engine/Deadline.py +0 -0
  34. {pyantigen-2.1.4 → pyantigen-2.1.5}/pyantigen/engine/Differential_evolution.py +0 -0
  35. {pyantigen-2.1.4 → pyantigen-2.1.5}/pyantigen/engine/Evaluator.py +0 -0
  36. {pyantigen-2.1.4 → pyantigen-2.1.5}/pyantigen/engine/Event_times.py +0 -0
  37. {pyantigen-2.1.4 → pyantigen-2.1.5}/pyantigen/engine/Fast_profile.py +0 -0
  38. {pyantigen-2.1.4 → pyantigen-2.1.5}/pyantigen/engine/Fit_cache.py +0 -0
  39. {pyantigen-2.1.4 → pyantigen-2.1.5}/pyantigen/engine/Identifiability.py +0 -0
  40. {pyantigen-2.1.4 → pyantigen-2.1.5}/pyantigen/engine/Model_cache.py +0 -0
  41. {pyantigen-2.1.4 → pyantigen-2.1.5}/pyantigen/engine/Model_optimize.py +0 -0
  42. {pyantigen-2.1.4 → pyantigen-2.1.5}/pyantigen/engine/Model_simulate.py +0 -0
  43. {pyantigen-2.1.4 → pyantigen-2.1.5}/pyantigen/engine/Multistart.py +0 -0
  44. {pyantigen-2.1.4 → pyantigen-2.1.5}/pyantigen/engine/Nelder_mead.py +0 -0
  45. {pyantigen-2.1.4 → pyantigen-2.1.5}/pyantigen/engine/Noise_floor.py +0 -0
  46. {pyantigen-2.1.4 → pyantigen-2.1.5}/pyantigen/engine/Optimize.py +0 -0
  47. {pyantigen-2.1.4 → pyantigen-2.1.5}/pyantigen/engine/Petab_export.py +0 -0
  48. {pyantigen-2.1.4 → pyantigen-2.1.5}/pyantigen/engine/Preequil_cache.py +0 -0
  49. {pyantigen-2.1.4 → pyantigen-2.1.5}/pyantigen/engine/Profile_checkpoint.py +0 -0
  50. {pyantigen-2.1.4 → pyantigen-2.1.5}/pyantigen/engine/Profile_plan.py +0 -0
  51. {pyantigen-2.1.4 → pyantigen-2.1.5}/pyantigen/engine/README.md +0 -0
  52. {pyantigen-2.1.4 → pyantigen-2.1.5}/pyantigen/engine/Results.py +0 -0
  53. {pyantigen-2.1.4 → pyantigen-2.1.5}/pyantigen/engine/Scaled_amici.py +0 -0
  54. {pyantigen-2.1.4 → pyantigen-2.1.5}/pyantigen/engine/Sensitivity_analysis.py +0 -0
  55. {pyantigen-2.1.4 → pyantigen-2.1.5}/pyantigen/engine/Stop_rules.py +0 -0
  56. {pyantigen-2.1.4 → pyantigen-2.1.5}/pyantigen/engine/__init__.py +0 -0
  57. {pyantigen-2.1.4 → pyantigen-2.1.5}/pyantigen/generate/AntimonyGen.py +0 -0
  58. {pyantigen-2.1.4 → pyantigen-2.1.5}/pyantigen/generate/RxnDict_to_antimony.py +0 -0
  59. {pyantigen-2.1.4 → pyantigen-2.1.5}/pyantigen/generate/TelluriumGen.py +0 -0
  60. {pyantigen-2.1.4 → pyantigen-2.1.5}/pyantigen/generate/__init__.py +0 -0
  61. {pyantigen-2.1.4 → pyantigen-2.1.5}/pyantigen/generate/antimony_utils.py +0 -0
  62. {pyantigen-2.1.4 → pyantigen-2.1.5}/pyantigen/generate/data_interpolation.py +0 -0
  63. {pyantigen-2.1.4 → pyantigen-2.1.5}/pyantigen/generate/input_schedule.py +0 -0
  64. {pyantigen-2.1.4 → pyantigen-2.1.5}/pyantigen/generate/isotopomer_tools.py +0 -0
  65. {pyantigen-2.1.4 → pyantigen-2.1.5}/pyantigen/generate/model_generation.py +0 -0
  66. {pyantigen-2.1.4 → pyantigen-2.1.5}/pyantigen/generate/models.py +0 -0
  67. {pyantigen-2.1.4 → pyantigen-2.1.5}/pyantigen/generate/module_base.py +0 -0
  68. {pyantigen-2.1.4 → pyantigen-2.1.5}/pyantigen/generate/pyantigen.py +0 -0
  69. {pyantigen-2.1.4 → pyantigen-2.1.5}/pyantigen/generate/rate_laws.py +0 -0
  70. {pyantigen-2.1.4 → pyantigen-2.1.5}/pyantigen/generate/reaction_creation.py +0 -0
  71. {pyantigen-2.1.4 → pyantigen-2.1.5}/pyantigen/generate/scaled_time.py +0 -0
  72. {pyantigen-2.1.4 → pyantigen-2.1.5}/pyantigen/study/__init__.py +0 -0
  73. {pyantigen-2.1.4 → pyantigen-2.1.5}/pyantigen/study/assay.py +0 -0
  74. {pyantigen-2.1.4 → pyantigen-2.1.5}/pyantigen/study/describe.py +0 -0
  75. {pyantigen-2.1.4 → pyantigen-2.1.5}/pyantigen/study/design.py +0 -0
  76. {pyantigen-2.1.4 → pyantigen-2.1.5}/pyantigen/study/lower_v1.py +0 -0
  77. {pyantigen-2.1.4 → pyantigen-2.1.5}/pyantigen/study/optimization.py +0 -0
  78. {pyantigen-2.1.4 → pyantigen-2.1.5}/pyantigen/study/params.py +0 -0
  79. {pyantigen-2.1.4 → pyantigen-2.1.5}/pyantigen/study/quantity.py +0 -0
  80. {pyantigen-2.1.4 → pyantigen-2.1.5}/pyantigen/study/reagent.py +0 -0
  81. {pyantigen-2.1.4 → pyantigen-2.1.5}/pyantigen/study/refs.py +0 -0
  82. {pyantigen-2.1.4 → pyantigen-2.1.5}/pyantigen/study/remarks.py +0 -0
  83. {pyantigen-2.1.4 → pyantigen-2.1.5}/pyantigen/study/serialize.py +0 -0
  84. {pyantigen-2.1.4 → pyantigen-2.1.5}/pyantigen/study/validate.py +0 -0
  85. {pyantigen-2.1.4 → pyantigen-2.1.5}/pyantigen/template/Example/AntiGen_paths.py +0 -0
  86. {pyantigen-2.1.4 → pyantigen-2.1.5}/pyantigen/template/Example/Flipflop_reference.py +0 -0
  87. {pyantigen-2.1.4 → pyantigen-2.1.5}/pyantigen/template/Example/Model_generate.py +0 -0
  88. {pyantigen-2.1.4 → pyantigen-2.1.5}/pyantigen/template/Example/Model_run.py +0 -0
  89. {pyantigen-2.1.4 → pyantigen-2.1.5}/pyantigen/template/Example/Modules/Data.py +0 -0
  90. {pyantigen-2.1.4 → pyantigen-2.1.5}/pyantigen/template/Example/Modules/Events.py +0 -0
  91. {pyantigen-2.1.4 → pyantigen-2.1.5}/pyantigen/template/Example/Modules/Experiment.py +0 -0
  92. {pyantigen-2.1.4 → pyantigen-2.1.5}/pyantigen/template/Example/Modules/Loss_config.py +0 -0
  93. {pyantigen-2.1.4 → pyantigen-2.1.5}/pyantigen/template/Example/Modules/Observed_species.py +0 -0
  94. {pyantigen-2.1.4 → pyantigen-2.1.5}/pyantigen/template/Example/Modules/Optimizer_settings.py +0 -0
  95. {pyantigen-2.1.4 → pyantigen-2.1.5}/pyantigen/template/Example/Modules/Plots.py +0 -0
  96. {pyantigen-2.1.4 → pyantigen-2.1.5}/pyantigen/template/Example/Modules/Solver_settings.py +0 -0
  97. {pyantigen-2.1.4 → pyantigen-2.1.5}/pyantigen/template/Example/Modules/Update_opt_parameters.py +0 -0
  98. {pyantigen-2.1.4 → pyantigen-2.1.5}/pyantigen/template/Example/Modules/Update_parameters.py +0 -0
  99. {pyantigen-2.1.4 → pyantigen-2.1.5}/pyantigen/template/Example/optimizations/Example1_ADneg.json +0 -0
  100. {pyantigen-2.1.4 → pyantigen-2.1.5}/pyantigen/template/Example/optimizations/Example1_ADpos.json +0 -0
  101. {pyantigen-2.1.4 → pyantigen-2.1.5}/pyantigen/template/Example/optimizations/Example3_joint.json +0 -0
  102. {pyantigen-2.1.4 → pyantigen-2.1.5}/pyantigen/template/Example/optimizations/Example4_flipflop.json +0 -0
  103. {pyantigen-2.1.4 → pyantigen-2.1.5}/pyantigen/template/Example/optimizations/example.py +0 -0
  104. {pyantigen-2.1.4 → pyantigen-2.1.5}/pyantigen/template/Example/studies/example.json +0 -0
  105. {pyantigen-2.1.4 → pyantigen-2.1.5}/pyantigen/template/Example/studies/example.py +0 -0
  106. {pyantigen-2.1.4 → pyantigen-2.1.5}/pyantigen/template/Example/studies/flipflop.json +0 -0
  107. {pyantigen-2.1.4 → pyantigen-2.1.5}/pyantigen/template/data/ADneg.csv +0 -0
  108. {pyantigen-2.1.4 → pyantigen-2.1.5}/pyantigen/template/data/ADpos.csv +0 -0
  109. {pyantigen-2.1.4 → pyantigen-2.1.5}/pyantigen/template/data/Flipflop.csv +0 -0
  110. {pyantigen-2.1.4 → pyantigen-2.1.5}/pyantigen/template/data/make_flipflop_data.py +0 -0
  111. {pyantigen-2.1.4 → pyantigen-2.1.5}/pyproject.toml +0 -0
  112. {pyantigen-2.1.4 → pyantigen-2.1.5}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PyAntiGen
3
- Version: 2.1.4
3
+ Version: 2.1.5
4
4
  Summary: Declarative generation, simulation and identifiability analysis of compartmental Antimony/SBML models
5
5
  Author-email: Don Elbert <elbert5770@gmail.com>
6
6
  License-Expression: MIT
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PyAntiGen
3
- Version: 2.1.4
3
+ Version: 2.1.5
4
4
  Summary: Declarative generation, simulation and identifiability analysis of compartmental Antimony/SBML models
5
5
  Author-email: Don Elbert <elbert5770@gmail.com>
6
6
  License-Expression: MIT
@@ -143,6 +143,7 @@
143
143
  "tests/engine/test_solver_fingerprint.py",
144
144
  "tests/engine/test_stop_rules.py",
145
145
  "tests/engine/test_tolerance_floor.py",
146
+ "tests/engine/test_tolerance_pinning.py",
146
147
  "tests/engine/test_wald_units.py",
147
148
  "tests/generate/test_gen_antimony_utils.py",
148
149
  "tests/generate/test_gen_data_interpolation.py",
@@ -0,0 +1,8 @@
1
+ {
2
+ "tag": "2.1.5",
3
+ "distance": 0,
4
+ "node": "g244c235d347fa7cf25440401efc2e02f1a6955e2",
5
+ "dirty": false,
6
+ "branch": "HEAD",
7
+ "node_date": "2026-10-15"
8
+ }
@@ -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.4'
22
- __version_tuple__ = version_tuple = (2, 1, 4)
21
+ __version__ = version = '2.1.5'
22
+ __version_tuple__ = version_tuple = (2, 1, 5)
23
23
 
24
- __commit_id__ = commit_id = 'g2e65abb87'
24
+ __commit_id__ = commit_id = 'g244c235d3'
@@ -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, floor_tolerance_vector, safe_simulate
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
- floor_tolerance_vector(self.r)
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
- floor_tolerance_vector(r)
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
- floor_tolerance_vector(r)
1039
+ apply_tolerance_vector(r)
895
1040
 
896
1041
  try:
897
1042
  if runner is not None:
@@ -1,8 +0,0 @@
1
- {
2
- "tag": "2.1.4",
3
- "distance": 0,
4
- "node": "g2e65abb870fe7cb1136e01bcd8c30b6d0f712dd6",
5
- "dirty": false,
6
- "branch": "HEAD",
7
- "node_date": "2026-10-14"
8
- }
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