PyAntiGen 2.0.0__tar.gz → 2.0.1__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 (101) hide show
  1. {pyantigen-2.0.0 → pyantigen-2.0.1}/PKG-INFO +1 -1
  2. {pyantigen-2.0.0 → pyantigen-2.0.1}/PyAntiGen.egg-info/PKG-INFO +1 -1
  3. {pyantigen-2.0.0 → pyantigen-2.0.1}/PyAntiGen.egg-info/scm_file_list.json +6 -0
  4. pyantigen-2.0.1/PyAntiGen.egg-info/scm_version.json +8 -0
  5. {pyantigen-2.0.0 → pyantigen-2.0.1}/pyantigen/_version.py +3 -3
  6. {pyantigen-2.0.0 → pyantigen-2.0.1}/pyantigen/engine/Model_optimize.py +7 -1
  7. {pyantigen-2.0.0 → pyantigen-2.0.1}/pyantigen/engine/Optimize.py +94 -5
  8. {pyantigen-2.0.0 → pyantigen-2.0.1}/pyantigen/generate/rate_laws.py +2 -2
  9. pyantigen-2.0.0/PyAntiGen.egg-info/scm_version.json +0 -8
  10. {pyantigen-2.0.0 → pyantigen-2.0.1}/.github/workflows/publish.yml +0 -0
  11. {pyantigen-2.0.0 → pyantigen-2.0.1}/.github/workflows/tests.yml +0 -0
  12. {pyantigen-2.0.0 → pyantigen-2.0.1}/.gitignore +0 -0
  13. {pyantigen-2.0.0 → pyantigen-2.0.1}/.vscode/launch.json +0 -0
  14. {pyantigen-2.0.0 → pyantigen-2.0.1}/LICENSE +0 -0
  15. {pyantigen-2.0.0 → pyantigen-2.0.1}/MANIFEST.in +0 -0
  16. {pyantigen-2.0.0 → pyantigen-2.0.1}/PyAntiGen.egg-info/SOURCES.txt +0 -0
  17. {pyantigen-2.0.0 → pyantigen-2.0.1}/PyAntiGen.egg-info/dependency_links.txt +0 -0
  18. {pyantigen-2.0.0 → pyantigen-2.0.1}/PyAntiGen.egg-info/entry_points.txt +0 -0
  19. {pyantigen-2.0.0 → pyantigen-2.0.1}/PyAntiGen.egg-info/requires.txt +0 -0
  20. {pyantigen-2.0.0 → pyantigen-2.0.1}/PyAntiGen.egg-info/top_level.txt +0 -0
  21. {pyantigen-2.0.0 → pyantigen-2.0.1}/README.md +0 -0
  22. {pyantigen-2.0.0 → pyantigen-2.0.1}/docs/PROTOCOL_LAYER.md +0 -0
  23. {pyantigen-2.0.0 → pyantigen-2.0.1}/docs/V2_DESIGN.md +0 -0
  24. {pyantigen-2.0.0 → pyantigen-2.0.1}/pyantigen/.agents/skills/module_generation/SKILL.md +0 -0
  25. {pyantigen-2.0.0 → pyantigen-2.0.1}/pyantigen/.agents/skills/ode_conversion_antimony/SKILL.md +0 -0
  26. {pyantigen-2.0.0 → pyantigen-2.0.1}/pyantigen/.agents/skills/ode_extraction_pipeline/SKILL.md +0 -0
  27. {pyantigen-2.0.0 → pyantigen-2.0.1}/pyantigen/.gitignore +0 -0
  28. {pyantigen-2.0.0 → pyantigen-2.0.1}/pyantigen/__init__.py +0 -0
  29. {pyantigen-2.0.0 → pyantigen-2.0.1}/pyantigen/cli.py +0 -0
  30. {pyantigen-2.0.0 → pyantigen-2.0.1}/pyantigen/engine/Anchor_cache.py +0 -0
  31. {pyantigen-2.0.0 → pyantigen-2.0.1}/pyantigen/engine/Deadline.py +0 -0
  32. {pyantigen-2.0.0 → pyantigen-2.0.1}/pyantigen/engine/Differential_evolution.py +0 -0
  33. {pyantigen-2.0.0 → pyantigen-2.0.1}/pyantigen/engine/Evaluator.py +0 -0
  34. {pyantigen-2.0.0 → pyantigen-2.0.1}/pyantigen/engine/Event_times.py +0 -0
  35. {pyantigen-2.0.0 → pyantigen-2.0.1}/pyantigen/engine/Fast_profile.py +0 -0
  36. {pyantigen-2.0.0 → pyantigen-2.0.1}/pyantigen/engine/Fit_cache.py +0 -0
  37. {pyantigen-2.0.0 → pyantigen-2.0.1}/pyantigen/engine/Identifiability.py +0 -0
  38. {pyantigen-2.0.0 → pyantigen-2.0.1}/pyantigen/engine/Model_simulate.py +0 -0
  39. {pyantigen-2.0.0 → pyantigen-2.0.1}/pyantigen/engine/Nelder_mead.py +0 -0
  40. {pyantigen-2.0.0 → pyantigen-2.0.1}/pyantigen/engine/Noise_floor.py +0 -0
  41. {pyantigen-2.0.0 → pyantigen-2.0.1}/pyantigen/engine/Petab_export.py +0 -0
  42. {pyantigen-2.0.0 → pyantigen-2.0.1}/pyantigen/engine/Preequil_cache.py +0 -0
  43. {pyantigen-2.0.0 → pyantigen-2.0.1}/pyantigen/engine/Profile_checkpoint.py +0 -0
  44. {pyantigen-2.0.0 → pyantigen-2.0.1}/pyantigen/engine/README.md +0 -0
  45. {pyantigen-2.0.0 → pyantigen-2.0.1}/pyantigen/engine/Results.py +0 -0
  46. {pyantigen-2.0.0 → pyantigen-2.0.1}/pyantigen/engine/Sensitivity_analysis.py +0 -0
  47. {pyantigen-2.0.0 → pyantigen-2.0.1}/pyantigen/engine/Simulate.py +0 -0
  48. {pyantigen-2.0.0 → pyantigen-2.0.1}/pyantigen/engine/__init__.py +0 -0
  49. {pyantigen-2.0.0 → pyantigen-2.0.1}/pyantigen/generate/AntimonyGen.py +0 -0
  50. {pyantigen-2.0.0 → pyantigen-2.0.1}/pyantigen/generate/RxnDict_to_antimony.py +0 -0
  51. {pyantigen-2.0.0 → pyantigen-2.0.1}/pyantigen/generate/TelluriumGen.py +0 -0
  52. {pyantigen-2.0.0 → pyantigen-2.0.1}/pyantigen/generate/__init__.py +0 -0
  53. {pyantigen-2.0.0 → pyantigen-2.0.1}/pyantigen/generate/antimony_utils.py +0 -0
  54. {pyantigen-2.0.0 → pyantigen-2.0.1}/pyantigen/generate/data_interpolation.py +0 -0
  55. {pyantigen-2.0.0 → pyantigen-2.0.1}/pyantigen/generate/isotopomer_tools.py +0 -0
  56. {pyantigen-2.0.0 → pyantigen-2.0.1}/pyantigen/generate/model_generation.py +0 -0
  57. {pyantigen-2.0.0 → pyantigen-2.0.1}/pyantigen/generate/models.py +0 -0
  58. {pyantigen-2.0.0 → pyantigen-2.0.1}/pyantigen/generate/module_base.py +0 -0
  59. {pyantigen-2.0.0 → pyantigen-2.0.1}/pyantigen/generate/pyantigen.py +0 -0
  60. {pyantigen-2.0.0 → pyantigen-2.0.1}/pyantigen/generate/reaction_creation.py +0 -0
  61. {pyantigen-2.0.0 → pyantigen-2.0.1}/pyantigen/study/__init__.py +0 -0
  62. {pyantigen-2.0.0 → pyantigen-2.0.1}/pyantigen/study/assay.py +0 -0
  63. {pyantigen-2.0.0 → pyantigen-2.0.1}/pyantigen/study/describe.py +0 -0
  64. {pyantigen-2.0.0 → pyantigen-2.0.1}/pyantigen/study/design.py +0 -0
  65. {pyantigen-2.0.0 → pyantigen-2.0.1}/pyantigen/study/lower_v1.py +0 -0
  66. {pyantigen-2.0.0 → pyantigen-2.0.1}/pyantigen/study/optimization.py +0 -0
  67. {pyantigen-2.0.0 → pyantigen-2.0.1}/pyantigen/study/params.py +0 -0
  68. {pyantigen-2.0.0 → pyantigen-2.0.1}/pyantigen/study/quantity.py +0 -0
  69. {pyantigen-2.0.0 → pyantigen-2.0.1}/pyantigen/study/reagent.py +0 -0
  70. {pyantigen-2.0.0 → pyantigen-2.0.1}/pyantigen/study/refs.py +0 -0
  71. {pyantigen-2.0.0 → pyantigen-2.0.1}/pyantigen/study/remarks.py +0 -0
  72. {pyantigen-2.0.0 → pyantigen-2.0.1}/pyantigen/study/serialize.py +0 -0
  73. {pyantigen-2.0.0 → pyantigen-2.0.1}/pyantigen/study/validate.py +0 -0
  74. {pyantigen-2.0.0 → pyantigen-2.0.1}/pyantigen/template/Example/AntiGen_paths.py +0 -0
  75. {pyantigen-2.0.0 → pyantigen-2.0.1}/pyantigen/template/Example/Flipflop_reference.py +0 -0
  76. {pyantigen-2.0.0 → pyantigen-2.0.1}/pyantigen/template/Example/Model_generate.py +0 -0
  77. {pyantigen-2.0.0 → pyantigen-2.0.1}/pyantigen/template/Example/Model_run.py +0 -0
  78. {pyantigen-2.0.0 → pyantigen-2.0.1}/pyantigen/template/Example/Modules/Data.py +0 -0
  79. {pyantigen-2.0.0 → pyantigen-2.0.1}/pyantigen/template/Example/Modules/Events.py +0 -0
  80. {pyantigen-2.0.0 → pyantigen-2.0.1}/pyantigen/template/Example/Modules/Experiment.py +0 -0
  81. {pyantigen-2.0.0 → pyantigen-2.0.1}/pyantigen/template/Example/Modules/Loss_config.py +0 -0
  82. {pyantigen-2.0.0 → pyantigen-2.0.1}/pyantigen/template/Example/Modules/Observed_species.py +0 -0
  83. {pyantigen-2.0.0 → pyantigen-2.0.1}/pyantigen/template/Example/Modules/Optimizer_settings.py +0 -0
  84. {pyantigen-2.0.0 → pyantigen-2.0.1}/pyantigen/template/Example/Modules/Plots.py +0 -0
  85. {pyantigen-2.0.0 → pyantigen-2.0.1}/pyantigen/template/Example/Modules/Solver_settings.py +0 -0
  86. {pyantigen-2.0.0 → pyantigen-2.0.1}/pyantigen/template/Example/Modules/Update_opt_parameters.py +0 -0
  87. {pyantigen-2.0.0 → pyantigen-2.0.1}/pyantigen/template/Example/Modules/Update_parameters.py +0 -0
  88. {pyantigen-2.0.0 → pyantigen-2.0.1}/pyantigen/template/Example/optimizations/Example1_ADneg.json +0 -0
  89. {pyantigen-2.0.0 → pyantigen-2.0.1}/pyantigen/template/Example/optimizations/Example1_ADpos.json +0 -0
  90. {pyantigen-2.0.0 → pyantigen-2.0.1}/pyantigen/template/Example/optimizations/Example3_joint.json +0 -0
  91. {pyantigen-2.0.0 → pyantigen-2.0.1}/pyantigen/template/Example/optimizations/Example4_flipflop.json +0 -0
  92. {pyantigen-2.0.0 → pyantigen-2.0.1}/pyantigen/template/Example/optimizations/example.py +0 -0
  93. {pyantigen-2.0.0 → pyantigen-2.0.1}/pyantigen/template/Example/studies/example.json +0 -0
  94. {pyantigen-2.0.0 → pyantigen-2.0.1}/pyantigen/template/Example/studies/example.py +0 -0
  95. {pyantigen-2.0.0 → pyantigen-2.0.1}/pyantigen/template/Example/studies/flipflop.json +0 -0
  96. {pyantigen-2.0.0 → pyantigen-2.0.1}/pyantigen/template/data/ADneg.csv +0 -0
  97. {pyantigen-2.0.0 → pyantigen-2.0.1}/pyantigen/template/data/ADpos.csv +0 -0
  98. {pyantigen-2.0.0 → pyantigen-2.0.1}/pyantigen/template/data/Flipflop.csv +0 -0
  99. {pyantigen-2.0.0 → pyantigen-2.0.1}/pyantigen/template/data/make_flipflop_data.py +0 -0
  100. {pyantigen-2.0.0 → pyantigen-2.0.1}/pyproject.toml +0 -0
  101. {pyantigen-2.0.0 → pyantigen-2.0.1}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PyAntiGen
3
- Version: 2.0.0
3
+ Version: 2.0.1
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.0.0
3
+ Version: 2.0.1
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
@@ -104,6 +104,7 @@
104
104
  "tests/engine/test_hessian_steps.py",
105
105
  "tests/engine/test_nelder_mead.py",
106
106
  "tests/engine/test_point_identity.py",
107
+ "tests/engine/test_profile_grid_open.py",
107
108
  "tests/engine/test_profile_quadratic.py",
108
109
  "tests/engine/test_search_mode.py",
109
110
  "tests/engine/test_sigma_floor.py",
@@ -111,6 +112,11 @@
111
112
  "tests/engine/test_solver_fingerprint.py",
112
113
  "tests/engine/test_tolerance_floor.py",
113
114
  "tests/engine/test_wald_units.py",
115
+ "tests/generate/test_gen_antimony_utils.py",
116
+ "tests/generate/test_gen_data_interpolation.py",
117
+ "tests/generate/test_gen_models.py",
118
+ "tests/generate/test_gen_pyantigen_api.py",
119
+ "tests/generate/test_gen_rxndict_to_antimony.py",
114
120
  "tests/silk_fixtures/antimony_models/Antimony_Bloomingdale_2021_1a_all_reactions.txt",
115
121
  "tests/silk_fixtures/antimony_models/Antimony_Bloomingdale_2021_1a_rules.txt",
116
122
  "tests/silk_fixtures/antimony_models/Antimony_Elbert_2022_1a_all_reactions.txt",
@@ -0,0 +1,8 @@
1
+ {
2
+ "tag": "2.0.1",
3
+ "distance": 0,
4
+ "node": "g886e26777feffc0c1b974558dbc2d9965c65b1bc",
5
+ "dirty": false,
6
+ "branch": "HEAD",
7
+ "node_date": "2026-10-03"
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.0.0'
22
- __version_tuple__ = version_tuple = (2, 0, 0)
21
+ __version__ = version = '2.0.1'
22
+ __version_tuple__ = version_tuple = (2, 0, 1)
23
23
 
24
- __commit_id__ = commit_id = 'g67741a856'
24
+ __commit_id__ = commit_id = 'g886e26777'
@@ -219,6 +219,12 @@ def _shutdown_evaluator(opt):
219
219
  # Grid controls for the parallel profile, and the settings key that reaches each.
220
220
  # The engine holds the defaults; only keys a run actually sets are forwarded, so
221
221
  # there is one place to change a default rather than two.
222
+ # Opening-grid placement, settable from a spec's profile_grid only. All default
223
+ # to today's behaviour. open_from_screen ends each side's grid at the slice
224
+ # screen's first crossing; open_decades caps the Wald/range_factor opening span
225
+ # for sides with no crossing; grid_spacing is "linear" or "geometric".
226
+ _PROFILE_GRID_SPEC_ONLY = {"open_from_screen", "open_decades", "grid_spacing"}
227
+
222
228
  _PROFILE_GRID_SETTINGS = {
223
229
  "profile_se_span": "se_span",
224
230
  "profile_n_grid": "n_grid",
@@ -265,7 +271,7 @@ def _profile_kwargs(settings, optimization_spec=None):
265
271
  out[arg] = value
266
272
 
267
273
  spec_kwargs = getattr(optimization_spec, "optimizer_kwargs", None) or {}
268
- known = set(_PROFILE_GRID_SETTINGS.values())
274
+ known = set(_PROFILE_GRID_SETTINGS.values()) | _PROFILE_GRID_SPEC_ONLY
269
275
  for arg, value in (spec_kwargs.get("profile_grid") or {}).items():
270
276
  if arg not in known:
271
277
  raise ValueError(
@@ -3762,8 +3762,53 @@ def _param_bounds(bounds, param_idx):
3762
3762
  return -np.inf, np.inf
3763
3763
 
3764
3764
 
3765
+ def _screen_crossing(screen, name, side, p_opt, sign, threshold=None):
3766
+ """Opt-space value of the first slice point above the threshold, or None.
3767
+
3768
+ The slice screen walks each side outward and stops the round it first
3769
+ crosses, so the crossing is the outermost point it holds. The slice is an
3770
+ upper bound on the profile, so the profile crosses at or beyond this value:
3771
+ it is where the opening grid should end, not a prediction of where the
3772
+ profile threshold is. Only a "crossed" side has one; an open, short-reach,
3773
+ blocked or empty side returns None and the caller falls back.
3774
+ """
3775
+ try:
3776
+ rec = screen["parameters"][name][side]
3777
+ except (KeyError, TypeError):
3778
+ return None
3779
+ if rec.get("state") != "crossed":
3780
+ return None
3781
+ thr = float(threshold if threshold is not None
3782
+ else screen.get("threshold", _PROFILE_THRESHOLD))
3783
+ for pt in rec.get("points") or []:
3784
+ x, d = pt.get("x"), pt.get("dnll")
3785
+ if x is None or d is None or not np.isfinite(d) or d <= thr:
3786
+ continue
3787
+ x = float(x)
3788
+ if (x < p_opt) if sign < 0 else (x > p_opt):
3789
+ return x
3790
+ return None
3791
+
3792
+
3793
+ def _grid_between(p_opt, target, n_grid, spacing):
3794
+ """*n_grid* values from the optimum out to *target*, the last one on it.
3795
+
3796
+ "linear" is the historical spacing. "geometric" halves the distance each
3797
+ step inward (target, target/2, target/4, ...), which spends the points near
3798
+ the optimum where a better minimum would show up as a negative dNLL, and
3799
+ keeps the outermost one where the threshold is expected.
3800
+ """
3801
+ if spacing == "geometric":
3802
+ fracs = [0.5 ** k for k in range(n_grid - 1, -1, -1)]
3803
+ return [p_opt + f * (target - p_opt) for f in fracs]
3804
+ # np.linspace itself, not an equivalent formula: the default grid must stay
3805
+ # bit-identical so checkpointed points from earlier launches still match.
3806
+ return list(np.linspace(p_opt, target, n_grid + 1)[1:])
3807
+
3808
+
3765
3809
  def _profile_grid_for(param_idx, res_x, bounds, scales, wald_se, n_grid,
3766
- range_factor, se_span):
3810
+ range_factor, se_span, param_name=None, screen=None,
3811
+ open_decades=None, grid_spacing="linear"):
3767
3812
  """Grid of fixed values for one parameter, both directions, in opt space.
3768
3813
 
3769
3814
  Seeded from the Wald standard error when one is available: the profile
@@ -3779,6 +3824,20 @@ def _profile_grid_for(param_idx, res_x, bounds, scales, wald_se, n_grid,
3779
3824
  wide span here and waste points in the flat middle of every well-determined
3780
3825
  parameter, ``_build_extension_jobs`` walks whichever side has not reached
3781
3826
  the threshold outward from this grid until it does.
3827
+
3828
+ Three opt-in controls, all inert at their defaults so existing specs place
3829
+ exactly the grid they always did:
3830
+
3831
+ ``screen`` (with ``param_name``)
3832
+ The slice screen's report. A side whose slice crossed the threshold
3833
+ ends its grid at that crossing, whatever the Wald SE says. The SE is
3834
+ the unreliable input -- a Hessian taken away from a true optimum can be
3835
+ enormous -- and the slice crossing is measured, not extrapolated.
3836
+ ``open_decades``
3837
+ Cap, in decades, on the half-width of a side that has no slice
3838
+ crossing and so falls back to ``se_span * SE`` or ``range_factor``.
3839
+ ``grid_spacing``
3840
+ "linear" (default) or "geometric"; see ``_grid_between``.
3782
3841
  """
3783
3842
  p_opt = res_x[param_idx]
3784
3843
  is_log = scales[param_idx] == "log10"
@@ -3803,11 +3862,30 @@ def _profile_grid_for(param_idx, res_x, bounds, scales, wald_se, n_grid,
3803
3862
  lo_target = min(p_opt / range_factor, p_opt * range_factor)
3804
3863
  hi_target = max(p_opt / range_factor, p_opt * range_factor)
3805
3864
 
3865
+ if open_decades is not None:
3866
+ cap = float(open_decades)
3867
+ if is_log:
3868
+ lo_target = max(lo_target, p_opt - cap)
3869
+ hi_target = min(hi_target, p_opt + cap)
3870
+ elif p_opt > 0:
3871
+ lo_target = max(lo_target, p_opt * 10.0 ** -cap)
3872
+ hi_target = min(hi_target, p_opt * 10.0 ** cap)
3873
+
3874
+ if screen is not None and param_name is not None:
3875
+ lo_x = _screen_crossing(screen, param_name, "lower", p_opt, -1)
3876
+ hi_x = _screen_crossing(screen, param_name, "upper", p_opt, 1)
3877
+ if lo_x is not None:
3878
+ lo_target = lo_x
3879
+ if hi_x is not None:
3880
+ hi_target = hi_x
3881
+
3806
3882
  lo = max(lo_target, lb)
3807
3883
  hi = min(hi_target, ub)
3808
3884
 
3809
- left = [v for v in np.linspace(p_opt, lo, n_grid + 1)[1:] if v < p_opt]
3810
- right = [v for v in np.linspace(p_opt, hi, n_grid + 1)[1:] if v > p_opt]
3885
+ left = [v for v in _grid_between(p_opt, lo, n_grid, grid_spacing)
3886
+ if v < p_opt]
3887
+ right = [v for v in _grid_between(p_opt, hi, n_grid, grid_spacing)
3888
+ if v > p_opt]
3811
3889
  return left, right, (lb, ub), is_log
3812
3890
 
3813
3891
 
@@ -4400,6 +4478,7 @@ def run_parallel_profile(
4400
4478
  n_grid=5, range_factor=2.0, se_span=4.0, n_refine=4,
4401
4479
  checkpoint=None, threshold=_PROFILE_THRESHOLD, warm_passes=1,
4402
4480
  max_extend=8, extend_growth=2.0, bracket_rtol=0.05,
4481
+ screen=None, open_decades=None, grid_spacing="linear",
4403
4482
  ):
4404
4483
  """Profile likelihood for every parameter as parallel batches.
4405
4484
 
@@ -4597,7 +4676,9 @@ def run_parallel_profile(
4597
4676
  meta = {}
4598
4677
  for i, name in enumerate(param_names):
4599
4678
  left, right, _b, is_log = _profile_grid_for(
4600
- i, res_x, bounds, scales, wald_se, n_grid, range_factor, se_span
4679
+ i, res_x, bounds, scales, wald_se, n_grid, range_factor, se_span,
4680
+ param_name=name, screen=screen, open_decades=open_decades,
4681
+ grid_spacing=grid_spacing,
4601
4682
  )
4602
4683
  meta[i] = {"is_log": is_log}
4603
4684
  nb = nuisance_bounds_for(i)
@@ -5455,6 +5536,7 @@ def _run_parallel_profile_with_checkpoint(
5455
5536
  fixed_sigmas=None, warm_passes=1, max_extend=8, extend_growth=2.0,
5456
5537
  bracket_rtol=0.05, screen_span_decades=None, screen_min_reach_decades=None,
5457
5538
  replicates=None, sigma_by_block=None,
5539
+ open_from_screen=False, open_decades=None, grid_spacing="linear",
5458
5540
  ):
5459
5541
  """Wire the pool, the checkpoint store, the wall budget and the profile."""
5460
5542
  from pyantigen.engine.Profile_checkpoint import (
@@ -5565,6 +5647,8 @@ def _run_parallel_profile_with_checkpoint(
5565
5647
  se_span=se_span, n_refine=n_refine, checkpoint=ckpt,
5566
5648
  warm_passes=warm_passes, max_extend=max_extend,
5567
5649
  extend_growth=extend_growth, bracket_rtol=bracket_rtol,
5650
+ screen=screen if open_from_screen else None,
5651
+ open_decades=open_decades, grid_spacing=grid_spacing,
5568
5652
  )
5569
5653
  if ckpt.n_skipped_stale:
5570
5654
  print(f"[profile] ignored {ckpt.n_skipped_stale} checkpoint record(s) "
@@ -6951,7 +7035,9 @@ def run_optimization_from_groups(
6951
7035
  n_refine=4, run_id=None, warm_passes=1,
6952
7036
  max_extend=8, extend_growth=2.0,
6953
7037
  bracket_rtol=0.05, screen_span_decades=None,
6954
- screen_min_reach_decades=None:
7038
+ screen_min_reach_decades=None,
7039
+ open_from_screen=False, open_decades=None,
7040
+ grid_spacing="linear":
6955
7041
  _run_parallel_profile_with_checkpoint(
6956
7042
  _pool_state["evaluator"], res.x, nll_at_optimum,
6957
7043
  param_names, bounds, scales,
@@ -6974,6 +7060,9 @@ def run_optimization_from_groups(
6974
7060
  screen_min_reach_decades),
6975
7061
  replicates=active_replicates,
6976
7062
  sigma_by_block=sigma_by_block,
7063
+ open_from_screen=open_from_screen,
7064
+ open_decades=open_decades,
7065
+ grid_spacing=grid_spacing,
6977
7066
  )
6978
7067
  )
6979
7068
 
@@ -93,9 +93,9 @@ class CustomLaw(RateLaw):
93
93
  def get_rate_law_info(rate_type: str) -> str:
94
94
  """Return a short docstring for the given rate type (units and scaling)."""
95
95
  if rate_type in ("MA", "RMA"):
96
- return MassActionLaw(rate_type).user_units_description
96
+ return MassActionLaw().user_units_description
97
97
  if rate_type in ("UDF", "BDF"):
98
- return VolumeTransportLaw(rate_type).user_units_description
98
+ return VolumeTransportLaw().user_units_description
99
99
  if rate_type in ("custom_conc_per_time", "custom_amt_per_time", "custom"):
100
100
  return CustomLaw(rate_type).user_units_description
101
101
  return "Unknown rate type"
@@ -1,8 +0,0 @@
1
- {
2
- "tag": "2.0.0",
3
- "distance": 0,
4
- "node": "g67741a856a2e34ea018990f634e0c15fc0a42e9c",
5
- "dirty": false,
6
- "branch": "HEAD",
7
- "node_date": "2026-10-01"
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