PyAntiGen 2.1.0__tar.gz → 2.1.2__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.0 → pyantigen-2.1.2}/PKG-INFO +1 -1
- {pyantigen-2.1.0 → pyantigen-2.1.2}/PyAntiGen.egg-info/PKG-INFO +1 -1
- {pyantigen-2.1.0 → pyantigen-2.1.2}/PyAntiGen.egg-info/SOURCES.txt +3 -0
- {pyantigen-2.1.0 → pyantigen-2.1.2}/PyAntiGen.egg-info/scm_file_list.json +7 -0
- pyantigen-2.1.2/PyAntiGen.egg-info/scm_version.json +8 -0
- {pyantigen-2.1.0 → pyantigen-2.1.2}/pyantigen/_version.py +3 -3
- {pyantigen-2.1.0 → pyantigen-2.1.2}/pyantigen/engine/Evaluator.py +54 -1
- {pyantigen-2.1.0 → pyantigen-2.1.2}/pyantigen/engine/Optimize.py +55 -4
- {pyantigen-2.1.0 → pyantigen-2.1.2}/pyantigen/engine/Preequil_cache.py +9 -0
- {pyantigen-2.1.0 → pyantigen-2.1.2}/pyantigen/engine/Profile_plan.py +187 -58
- pyantigen-2.1.2/pyantigen/engine/Segmented.py +157 -0
- {pyantigen-2.1.0 → pyantigen-2.1.2}/pyantigen/engine/Simulate.py +58 -7
- pyantigen-2.1.2/pyantigen/engine/Stop_rules.py +205 -0
- pyantigen-2.1.2/pyantigen/generate/input_schedule.py +624 -0
- pyantigen-2.1.0/PyAntiGen.egg-info/scm_version.json +0 -8
- {pyantigen-2.1.0 → pyantigen-2.1.2}/.github/workflows/publish.yml +0 -0
- {pyantigen-2.1.0 → pyantigen-2.1.2}/.github/workflows/tests.yml +0 -0
- {pyantigen-2.1.0 → pyantigen-2.1.2}/.gitignore +0 -0
- {pyantigen-2.1.0 → pyantigen-2.1.2}/.vscode/launch.json +0 -0
- {pyantigen-2.1.0 → pyantigen-2.1.2}/LICENSE +0 -0
- {pyantigen-2.1.0 → pyantigen-2.1.2}/MANIFEST.in +0 -0
- {pyantigen-2.1.0 → pyantigen-2.1.2}/PyAntiGen.egg-info/dependency_links.txt +0 -0
- {pyantigen-2.1.0 → pyantigen-2.1.2}/PyAntiGen.egg-info/entry_points.txt +0 -0
- {pyantigen-2.1.0 → pyantigen-2.1.2}/PyAntiGen.egg-info/requires.txt +0 -0
- {pyantigen-2.1.0 → pyantigen-2.1.2}/PyAntiGen.egg-info/top_level.txt +0 -0
- {pyantigen-2.1.0 → pyantigen-2.1.2}/README.md +0 -0
- {pyantigen-2.1.0 → pyantigen-2.1.2}/docs/MULTISTART.md +0 -0
- {pyantigen-2.1.0 → pyantigen-2.1.2}/docs/PROTOCOL_LAYER.md +0 -0
- {pyantigen-2.1.0 → pyantigen-2.1.2}/docs/V2_DESIGN.md +0 -0
- {pyantigen-2.1.0 → pyantigen-2.1.2}/pyantigen/.agents/skills/module_generation/SKILL.md +0 -0
- {pyantigen-2.1.0 → pyantigen-2.1.2}/pyantigen/.agents/skills/ode_conversion_antimony/SKILL.md +0 -0
- {pyantigen-2.1.0 → pyantigen-2.1.2}/pyantigen/.agents/skills/ode_extraction_pipeline/SKILL.md +0 -0
- {pyantigen-2.1.0 → pyantigen-2.1.2}/pyantigen/.gitignore +0 -0
- {pyantigen-2.1.0 → pyantigen-2.1.2}/pyantigen/__init__.py +0 -0
- {pyantigen-2.1.0 → pyantigen-2.1.2}/pyantigen/cli.py +0 -0
- {pyantigen-2.1.0 → pyantigen-2.1.2}/pyantigen/engine/Anchor_cache.py +0 -0
- {pyantigen-2.1.0 → pyantigen-2.1.2}/pyantigen/engine/Deadline.py +0 -0
- {pyantigen-2.1.0 → pyantigen-2.1.2}/pyantigen/engine/Differential_evolution.py +0 -0
- {pyantigen-2.1.0 → pyantigen-2.1.2}/pyantigen/engine/Event_times.py +0 -0
- {pyantigen-2.1.0 → pyantigen-2.1.2}/pyantigen/engine/Fast_profile.py +0 -0
- {pyantigen-2.1.0 → pyantigen-2.1.2}/pyantigen/engine/Fit_cache.py +0 -0
- {pyantigen-2.1.0 → pyantigen-2.1.2}/pyantigen/engine/Identifiability.py +0 -0
- {pyantigen-2.1.0 → pyantigen-2.1.2}/pyantigen/engine/Model_cache.py +0 -0
- {pyantigen-2.1.0 → pyantigen-2.1.2}/pyantigen/engine/Model_optimize.py +0 -0
- {pyantigen-2.1.0 → pyantigen-2.1.2}/pyantigen/engine/Model_simulate.py +0 -0
- {pyantigen-2.1.0 → pyantigen-2.1.2}/pyantigen/engine/Multistart.py +0 -0
- {pyantigen-2.1.0 → pyantigen-2.1.2}/pyantigen/engine/Nelder_mead.py +0 -0
- {pyantigen-2.1.0 → pyantigen-2.1.2}/pyantigen/engine/Noise_floor.py +0 -0
- {pyantigen-2.1.0 → pyantigen-2.1.2}/pyantigen/engine/Petab_export.py +0 -0
- {pyantigen-2.1.0 → pyantigen-2.1.2}/pyantigen/engine/Profile_checkpoint.py +0 -0
- {pyantigen-2.1.0 → pyantigen-2.1.2}/pyantigen/engine/README.md +0 -0
- {pyantigen-2.1.0 → pyantigen-2.1.2}/pyantigen/engine/Results.py +0 -0
- {pyantigen-2.1.0 → pyantigen-2.1.2}/pyantigen/engine/Sensitivity_analysis.py +0 -0
- {pyantigen-2.1.0 → pyantigen-2.1.2}/pyantigen/engine/__init__.py +0 -0
- {pyantigen-2.1.0 → pyantigen-2.1.2}/pyantigen/generate/AntimonyGen.py +0 -0
- {pyantigen-2.1.0 → pyantigen-2.1.2}/pyantigen/generate/RxnDict_to_antimony.py +0 -0
- {pyantigen-2.1.0 → pyantigen-2.1.2}/pyantigen/generate/TelluriumGen.py +0 -0
- {pyantigen-2.1.0 → pyantigen-2.1.2}/pyantigen/generate/__init__.py +0 -0
- {pyantigen-2.1.0 → pyantigen-2.1.2}/pyantigen/generate/antimony_utils.py +0 -0
- {pyantigen-2.1.0 → pyantigen-2.1.2}/pyantigen/generate/data_interpolation.py +0 -0
- {pyantigen-2.1.0 → pyantigen-2.1.2}/pyantigen/generate/isotopomer_tools.py +0 -0
- {pyantigen-2.1.0 → pyantigen-2.1.2}/pyantigen/generate/model_generation.py +0 -0
- {pyantigen-2.1.0 → pyantigen-2.1.2}/pyantigen/generate/models.py +0 -0
- {pyantigen-2.1.0 → pyantigen-2.1.2}/pyantigen/generate/module_base.py +0 -0
- {pyantigen-2.1.0 → pyantigen-2.1.2}/pyantigen/generate/pyantigen.py +0 -0
- {pyantigen-2.1.0 → pyantigen-2.1.2}/pyantigen/generate/rate_laws.py +0 -0
- {pyantigen-2.1.0 → pyantigen-2.1.2}/pyantigen/generate/reaction_creation.py +0 -0
- {pyantigen-2.1.0 → pyantigen-2.1.2}/pyantigen/study/__init__.py +0 -0
- {pyantigen-2.1.0 → pyantigen-2.1.2}/pyantigen/study/assay.py +0 -0
- {pyantigen-2.1.0 → pyantigen-2.1.2}/pyantigen/study/describe.py +0 -0
- {pyantigen-2.1.0 → pyantigen-2.1.2}/pyantigen/study/design.py +0 -0
- {pyantigen-2.1.0 → pyantigen-2.1.2}/pyantigen/study/lower_v1.py +0 -0
- {pyantigen-2.1.0 → pyantigen-2.1.2}/pyantigen/study/optimization.py +0 -0
- {pyantigen-2.1.0 → pyantigen-2.1.2}/pyantigen/study/params.py +0 -0
- {pyantigen-2.1.0 → pyantigen-2.1.2}/pyantigen/study/quantity.py +0 -0
- {pyantigen-2.1.0 → pyantigen-2.1.2}/pyantigen/study/reagent.py +0 -0
- {pyantigen-2.1.0 → pyantigen-2.1.2}/pyantigen/study/refs.py +0 -0
- {pyantigen-2.1.0 → pyantigen-2.1.2}/pyantigen/study/remarks.py +0 -0
- {pyantigen-2.1.0 → pyantigen-2.1.2}/pyantigen/study/serialize.py +0 -0
- {pyantigen-2.1.0 → pyantigen-2.1.2}/pyantigen/study/validate.py +0 -0
- {pyantigen-2.1.0 → pyantigen-2.1.2}/pyantigen/template/Example/AntiGen_paths.py +0 -0
- {pyantigen-2.1.0 → pyantigen-2.1.2}/pyantigen/template/Example/Flipflop_reference.py +0 -0
- {pyantigen-2.1.0 → pyantigen-2.1.2}/pyantigen/template/Example/Model_generate.py +0 -0
- {pyantigen-2.1.0 → pyantigen-2.1.2}/pyantigen/template/Example/Model_run.py +0 -0
- {pyantigen-2.1.0 → pyantigen-2.1.2}/pyantigen/template/Example/Modules/Data.py +0 -0
- {pyantigen-2.1.0 → pyantigen-2.1.2}/pyantigen/template/Example/Modules/Events.py +0 -0
- {pyantigen-2.1.0 → pyantigen-2.1.2}/pyantigen/template/Example/Modules/Experiment.py +0 -0
- {pyantigen-2.1.0 → pyantigen-2.1.2}/pyantigen/template/Example/Modules/Loss_config.py +0 -0
- {pyantigen-2.1.0 → pyantigen-2.1.2}/pyantigen/template/Example/Modules/Observed_species.py +0 -0
- {pyantigen-2.1.0 → pyantigen-2.1.2}/pyantigen/template/Example/Modules/Optimizer_settings.py +0 -0
- {pyantigen-2.1.0 → pyantigen-2.1.2}/pyantigen/template/Example/Modules/Plots.py +0 -0
- {pyantigen-2.1.0 → pyantigen-2.1.2}/pyantigen/template/Example/Modules/Solver_settings.py +0 -0
- {pyantigen-2.1.0 → pyantigen-2.1.2}/pyantigen/template/Example/Modules/Update_opt_parameters.py +0 -0
- {pyantigen-2.1.0 → pyantigen-2.1.2}/pyantigen/template/Example/Modules/Update_parameters.py +0 -0
- {pyantigen-2.1.0 → pyantigen-2.1.2}/pyantigen/template/Example/optimizations/Example1_ADneg.json +0 -0
- {pyantigen-2.1.0 → pyantigen-2.1.2}/pyantigen/template/Example/optimizations/Example1_ADpos.json +0 -0
- {pyantigen-2.1.0 → pyantigen-2.1.2}/pyantigen/template/Example/optimizations/Example3_joint.json +0 -0
- {pyantigen-2.1.0 → pyantigen-2.1.2}/pyantigen/template/Example/optimizations/Example4_flipflop.json +0 -0
- {pyantigen-2.1.0 → pyantigen-2.1.2}/pyantigen/template/Example/optimizations/example.py +0 -0
- {pyantigen-2.1.0 → pyantigen-2.1.2}/pyantigen/template/Example/studies/example.json +0 -0
- {pyantigen-2.1.0 → pyantigen-2.1.2}/pyantigen/template/Example/studies/example.py +0 -0
- {pyantigen-2.1.0 → pyantigen-2.1.2}/pyantigen/template/Example/studies/flipflop.json +0 -0
- {pyantigen-2.1.0 → pyantigen-2.1.2}/pyantigen/template/data/ADneg.csv +0 -0
- {pyantigen-2.1.0 → pyantigen-2.1.2}/pyantigen/template/data/ADpos.csv +0 -0
- {pyantigen-2.1.0 → pyantigen-2.1.2}/pyantigen/template/data/Flipflop.csv +0 -0
- {pyantigen-2.1.0 → pyantigen-2.1.2}/pyantigen/template/data/make_flipflop_data.py +0 -0
- {pyantigen-2.1.0 → pyantigen-2.1.2}/pyproject.toml +0 -0
- {pyantigen-2.1.0 → pyantigen-2.1.2}/setup.cfg +0 -0
|
@@ -45,8 +45,10 @@ pyantigen/engine/Profile_checkpoint.py
|
|
|
45
45
|
pyantigen/engine/Profile_plan.py
|
|
46
46
|
pyantigen/engine/README.md
|
|
47
47
|
pyantigen/engine/Results.py
|
|
48
|
+
pyantigen/engine/Segmented.py
|
|
48
49
|
pyantigen/engine/Sensitivity_analysis.py
|
|
49
50
|
pyantigen/engine/Simulate.py
|
|
51
|
+
pyantigen/engine/Stop_rules.py
|
|
50
52
|
pyantigen/engine/__init__.py
|
|
51
53
|
pyantigen/generate/AntimonyGen.py
|
|
52
54
|
pyantigen/generate/RxnDict_to_antimony.py
|
|
@@ -54,6 +56,7 @@ pyantigen/generate/TelluriumGen.py
|
|
|
54
56
|
pyantigen/generate/__init__.py
|
|
55
57
|
pyantigen/generate/antimony_utils.py
|
|
56
58
|
pyantigen/generate/data_interpolation.py
|
|
59
|
+
pyantigen/generate/input_schedule.py
|
|
57
60
|
pyantigen/generate/isotopomer_tools.py
|
|
58
61
|
pyantigen/generate/model_generation.py
|
|
59
62
|
pyantigen/generate/models.py
|
|
@@ -37,8 +37,10 @@
|
|
|
37
37
|
"pyantigen/engine/Profile_plan.py",
|
|
38
38
|
"pyantigen/engine/README.md",
|
|
39
39
|
"pyantigen/engine/Results.py",
|
|
40
|
+
"pyantigen/engine/Segmented.py",
|
|
40
41
|
"pyantigen/engine/Sensitivity_analysis.py",
|
|
41
42
|
"pyantigen/engine/Simulate.py",
|
|
43
|
+
"pyantigen/engine/Stop_rules.py",
|
|
42
44
|
"pyantigen/engine/__init__.py",
|
|
43
45
|
"pyantigen/generate/AntimonyGen.py",
|
|
44
46
|
"pyantigen/generate/RxnDict_to_antimony.py",
|
|
@@ -46,6 +48,7 @@
|
|
|
46
48
|
"pyantigen/generate/__init__.py",
|
|
47
49
|
"pyantigen/generate/antimony_utils.py",
|
|
48
50
|
"pyantigen/generate/data_interpolation.py",
|
|
51
|
+
"pyantigen/generate/input_schedule.py",
|
|
49
52
|
"pyantigen/generate/isotopomer_tools.py",
|
|
50
53
|
"pyantigen/generate/model_generation.py",
|
|
51
54
|
"pyantigen/generate/models.py",
|
|
@@ -121,14 +124,18 @@
|
|
|
121
124
|
"tests/engine/test_profile_stream.py",
|
|
122
125
|
"tests/engine/test_results_csv_policy.py",
|
|
123
126
|
"tests/engine/test_search_mode.py",
|
|
127
|
+
"tests/engine/test_segment_sensitivity.py",
|
|
128
|
+
"tests/engine/test_segmented.py",
|
|
124
129
|
"tests/engine/test_sigma_floor.py",
|
|
125
130
|
"tests/engine/test_slice_screen.py",
|
|
126
131
|
"tests/engine/test_slice_window.py",
|
|
127
132
|
"tests/engine/test_solver_fingerprint.py",
|
|
133
|
+
"tests/engine/test_stop_rules.py",
|
|
128
134
|
"tests/engine/test_tolerance_floor.py",
|
|
129
135
|
"tests/engine/test_wald_units.py",
|
|
130
136
|
"tests/generate/test_gen_antimony_utils.py",
|
|
131
137
|
"tests/generate/test_gen_data_interpolation.py",
|
|
138
|
+
"tests/generate/test_gen_input_schedule.py",
|
|
132
139
|
"tests/generate/test_gen_models.py",
|
|
133
140
|
"tests/generate/test_gen_pyantigen_api.py",
|
|
134
141
|
"tests/generate/test_gen_rxndict_to_antimony.py",
|
|
@@ -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.2'
|
|
22
|
+
__version_tuple__ = version_tuple = (2, 1, 2)
|
|
23
23
|
|
|
24
|
-
__commit_id__ = commit_id = '
|
|
24
|
+
__commit_id__ = commit_id = 'g6b3300aa1'
|
|
@@ -353,6 +353,11 @@ def _profile_task(job):
|
|
|
353
353
|
out.pop("frozen_sigmas", None)
|
|
354
354
|
# A location on this machine's disk, meaningless in a stored record.
|
|
355
355
|
out.pop("state_path", None)
|
|
356
|
+
# An input as well: the settings for the shadow stop rules. What they
|
|
357
|
+
# observed comes back as `trace`, `shadow_stops` and `stop_reason` below;
|
|
358
|
+
# `trace` and `shadow_stops` are also inputs when a point is resumed, and
|
|
359
|
+
# are overwritten with the continued history.
|
|
360
|
+
stop_rules = out.pop("stop_rules", None)
|
|
356
361
|
out.update({"nll": None, "status": "ok", "n_evals": 0, "wall_s": 0.0,
|
|
357
362
|
"worker": os.getpid(), "converged": True, "nit": -1,
|
|
358
363
|
"nfev": -1, "opt_message": "", "interrupted": False})
|
|
@@ -384,6 +389,12 @@ def _profile_task(job):
|
|
|
384
389
|
# because a point stopped before its first simplex is complete has no
|
|
385
390
|
# sorted vertex to read it from.
|
|
386
391
|
best = {"f": float("inf"), "x": x_start}
|
|
392
|
+
# The shadow stop rules' watcher (pyantigen.engine.Stop_rules). It only
|
|
393
|
+
# observes -- nothing it computes feeds back into the optimizer -- and
|
|
394
|
+
# is None unless the job asked for it. `base` is the point's evaluation
|
|
395
|
+
# count when this job began, so a resumed point numbers on from where
|
|
396
|
+
# it stopped.
|
|
397
|
+
shadow = {"mon": None, "base": 0}
|
|
387
398
|
|
|
388
399
|
# Every profile point pins each floored block at its own sigma_used
|
|
389
400
|
# from the fit rather than letting it re-concentrate (see
|
|
@@ -404,6 +415,12 @@ def _profile_task(job):
|
|
|
404
415
|
if np.isfinite(v) and v < best["f"]:
|
|
405
416
|
best["f"] = float(v)
|
|
406
417
|
best["x"] = x_arr.copy()
|
|
418
|
+
if shadow["mon"] is not None:
|
|
419
|
+
try:
|
|
420
|
+
shadow["mon"].update(shadow["base"] + eval_count["n"],
|
|
421
|
+
best["f"])
|
|
422
|
+
except Exception: # a watcher must never fail a point
|
|
423
|
+
shadow["mon"] = None
|
|
407
424
|
return v
|
|
408
425
|
|
|
409
426
|
def time_is_up():
|
|
@@ -419,7 +436,8 @@ def _profile_task(job):
|
|
|
419
436
|
# is just the objective at the fixed value -- exact by definition.
|
|
420
437
|
nll = raw_objective(x_start, x_fixed)
|
|
421
438
|
x_opt = x_start
|
|
422
|
-
out.update({"converged": True, "nit": 0, "nfev": 1
|
|
439
|
+
out.update({"converged": True, "nit": 0, "nfev": 1,
|
|
440
|
+
"stop_reason": "exact"})
|
|
423
441
|
eval_count["n"] = 1
|
|
424
442
|
else:
|
|
425
443
|
options, extra_kwargs = nuisance_options(
|
|
@@ -429,6 +447,19 @@ def _profile_task(job):
|
|
|
429
447
|
resumable = str(method).lower() == "nelder-mead" and can_run(
|
|
430
448
|
options, extra_kwargs)
|
|
431
449
|
|
|
450
|
+
def start_shadow(base):
|
|
451
|
+
if not stop_rules or stop_rules.get("anchor") is None:
|
|
452
|
+
return
|
|
453
|
+
try:
|
|
454
|
+
from pyantigen.engine.Stop_rules import PointMonitor
|
|
455
|
+
shadow["mon"] = PointMonitor(
|
|
456
|
+
stop_rules["anchor"], max_fev, x_start.size,
|
|
457
|
+
cfg=stop_rules, trace=job.get("trace"),
|
|
458
|
+
fired=job.get("shadow_stops"))
|
|
459
|
+
shadow["base"] = int(base)
|
|
460
|
+
except Exception: # a watcher must never fail a point
|
|
461
|
+
shadow["mon"] = None
|
|
462
|
+
|
|
432
463
|
res = None
|
|
433
464
|
outcome = "done"
|
|
434
465
|
if resumable:
|
|
@@ -457,6 +488,7 @@ def _profile_task(job):
|
|
|
457
488
|
# run, and nothing to save either.
|
|
458
489
|
outcome = "capped"
|
|
459
490
|
else:
|
|
491
|
+
start_shadow(nm_state["nfev"])
|
|
460
492
|
interval = float(job.get("state_interval_s",
|
|
461
493
|
POINT_STATE_INTERVAL_S))
|
|
462
494
|
last_save = [0.0]
|
|
@@ -490,6 +522,8 @@ def _profile_task(job):
|
|
|
490
522
|
if nfev_used >= max_fev or nit_used >= max_it:
|
|
491
523
|
outcome = "capped"
|
|
492
524
|
else:
|
|
525
|
+
start_shadow(nfev_used)
|
|
526
|
+
|
|
493
527
|
def guarded(x_nuisance, fixed_val):
|
|
494
528
|
v = nuisance_objective(x_nuisance, fixed_val)
|
|
495
529
|
if time_is_up():
|
|
@@ -549,6 +583,20 @@ def _profile_task(job):
|
|
|
549
583
|
out["nm_simplex"] = np.asarray(nm_state["sim"],
|
|
550
584
|
dtype=float).tolist()
|
|
551
585
|
|
|
586
|
+
# Why this job's optimization ended, for reading a finished run:
|
|
587
|
+
# `converged` alone cannot tell a point that met its tolerance from
|
|
588
|
+
# one that spent its cap and happened to be at the minimum, and at
|
|
589
|
+
# a 1500 cap most points are the second kind.
|
|
590
|
+
if outcome == "interrupted":
|
|
591
|
+
out["stop_reason"] = "interrupted"
|
|
592
|
+
elif res is None or not np.isfinite(getattr(res, "fun", np.inf)):
|
|
593
|
+
out["stop_reason"] = "budget-spent"
|
|
594
|
+
elif outcome == "capped" or not getattr(res, "success", True):
|
|
595
|
+
out["stop_reason"] = ("maxiter" if getattr(res, "status", None) == 2
|
|
596
|
+
else "maxfev")
|
|
597
|
+
else:
|
|
598
|
+
out["stop_reason"] = "tolerance"
|
|
599
|
+
|
|
552
600
|
if nm_state is not None:
|
|
553
601
|
nfev_total, nit_total = nm_state["nfev"], nm_state["nit"]
|
|
554
602
|
else:
|
|
@@ -565,6 +613,11 @@ def _profile_task(job):
|
|
|
565
613
|
"nit_total": nit_total,
|
|
566
614
|
"status": "ok" if np.isfinite(nll) and nll < FAILURE_VALUE else "sentinel",
|
|
567
615
|
})
|
|
616
|
+
if shadow["mon"] is not None:
|
|
617
|
+
try:
|
|
618
|
+
out.update(shadow["mon"].finish(nfev_total, best["f"]))
|
|
619
|
+
except Exception:
|
|
620
|
+
pass
|
|
568
621
|
except Exception as exc:
|
|
569
622
|
out.update({"status": f"error: {type(exc).__name__}: {exc}",
|
|
570
623
|
"nll": FAILURE_VALUE, "interrupted": False})
|
|
@@ -5166,7 +5166,8 @@ def _carry_resume_state(keep, res):
|
|
|
5166
5166
|
allowance never advancing and its optimizer state never moving.
|
|
5167
5167
|
"""
|
|
5168
5168
|
for field in ("interrupted", "converged", "nfev_total", "nit_total",
|
|
5169
|
-
"nm_simplex", "opt_message"
|
|
5169
|
+
"nm_simplex", "opt_message", "stop_reason", "trace",
|
|
5170
|
+
"shadow_stops"):
|
|
5170
5171
|
if field in res:
|
|
5171
5172
|
keep[field] = res[field]
|
|
5172
5173
|
return keep
|
|
@@ -5233,10 +5234,17 @@ def run_parallel_profile(
|
|
|
5233
5234
|
max_extend=8, extend_growth=2.0, bracket_rtol=0.05,
|
|
5234
5235
|
screen=None, open_decades=None, grid_spacing="linear",
|
|
5235
5236
|
adaptive_pass1=False, max_step_decades=0.7, n_workers=None,
|
|
5236
|
-
first_probe="max",
|
|
5237
|
+
first_probe="max", shadow_stop_rules=True,
|
|
5237
5238
|
):
|
|
5238
5239
|
"""Profile likelihood for every parameter as parallel batches.
|
|
5239
5240
|
|
|
5241
|
+
*shadow_stop_rules* attaches a watcher to every point (see
|
|
5242
|
+
:mod:`pyantigen.engine.Stop_rules`): a sparse trace of the best value reached
|
|
5243
|
+
and the evaluation at which each candidate stop rule would first have
|
|
5244
|
+
fired, written into the record, and summarised at the end of the run. It
|
|
5245
|
+
never stops anything. ``False`` turns it off; a dict overrides the rule
|
|
5246
|
+
constants.
|
|
5247
|
+
|
|
5240
5248
|
Five passes:
|
|
5241
5249
|
1. coarse grid, cold-started from the optimum, fully parallel;
|
|
5242
5250
|
2. outward extension: any side whose points all sit below the threshold
|
|
@@ -5369,6 +5377,15 @@ def run_parallel_profile(
|
|
|
5369
5377
|
# this one's travel into a rate. See _predicted_travel.
|
|
5370
5378
|
"x_step": x_step,
|
|
5371
5379
|
}
|
|
5380
|
+
if shadow_stop_rules:
|
|
5381
|
+
cfg = dict(shadow_stop_rules) if isinstance(shadow_stop_rules, dict) else {}
|
|
5382
|
+
cfg.setdefault("threshold", float(threshold))
|
|
5383
|
+
cfg["anchor"] = float(nll_at_optimum)
|
|
5384
|
+
job["stop_rules"] = cfg
|
|
5385
|
+
if resume_from is not None:
|
|
5386
|
+
# One history across launches of the same point.
|
|
5387
|
+
job["trace"] = resume_from.get("trace")
|
|
5388
|
+
job["shadow_stops"] = resume_from.get("shadow_stops")
|
|
5372
5389
|
# Every job carries an explicit starting simplex, from its neighbour
|
|
5373
5390
|
# where there is one and from the Wald SE otherwise. Leaving it to
|
|
5374
5391
|
# scipy is what the two constructors exist to avoid: its 5%-of-value
|
|
@@ -5466,6 +5483,14 @@ def run_parallel_profile(
|
|
|
5466
5483
|
key = ProfileCheckpointKey(res["x_fixed"])
|
|
5467
5484
|
prev = completed.get(name, {}).get(key)
|
|
5468
5485
|
keep = res
|
|
5486
|
+
# A planner restart (see Profile_plan) that ran to its end is counted on
|
|
5487
|
+
# the point, so a relaunch does not restart it again: the planner reads
|
|
5488
|
+
# the count to decide whether a value past the threshold can be trusted.
|
|
5489
|
+
finished_rerun = (str(res.get("pass_label", "")).endswith("rerun")
|
|
5490
|
+
and res.get("status") == "ok"
|
|
5491
|
+
and not res.get("interrupted"))
|
|
5492
|
+
if prev is not None and finished_rerun:
|
|
5493
|
+
res["reruns_tried"] = int(prev.get("reruns_tried") or 0) + 1
|
|
5469
5494
|
# Two evaluations at the same fixed value are both upper bounds on the
|
|
5470
5495
|
# profile, so the lower one is the better estimate. Nothing in passes 1,
|
|
5471
5496
|
# 2 or 4 re-visits a point, but keeping the rule here means the store
|
|
@@ -5477,8 +5502,17 @@ def run_parallel_profile(
|
|
|
5477
5502
|
# Dropping it would resume the point forever against a budget that
|
|
5478
5503
|
# never moves.
|
|
5479
5504
|
if not (prev.get("interrupted") and res.get("status") == "ok"):
|
|
5480
|
-
|
|
5481
|
-
|
|
5505
|
+
if not finished_rerun:
|
|
5506
|
+
return
|
|
5507
|
+
# A restart that did not lower the point: the value stands, but
|
|
5508
|
+
# the point has had its restart. Written as a tie, which the
|
|
5509
|
+
# store resolves to the later record.
|
|
5510
|
+
keep = dict(prev)
|
|
5511
|
+
keep["reruns_tried"] = res["reruns_tried"]
|
|
5512
|
+
else:
|
|
5513
|
+
keep = _carry_resume_state(dict(prev), res)
|
|
5514
|
+
if finished_rerun:
|
|
5515
|
+
keep["reruns_tried"] = res["reruns_tried"]
|
|
5482
5516
|
if checkpoint is not None and keep.get("status") == "ok":
|
|
5483
5517
|
checkpoint.append(keep)
|
|
5484
5518
|
completed.setdefault(name, {})[key] = keep
|
|
@@ -5765,6 +5799,21 @@ def run_parallel_profile(
|
|
|
5765
5799
|
param_names, res_x, meta, anchor, where, nll_at_optimum)
|
|
5766
5800
|
_print_profile_convergence(convergence)
|
|
5767
5801
|
_print_profile_reach(convergence["reach"])
|
|
5802
|
+
if shadow_stop_rules:
|
|
5803
|
+
try:
|
|
5804
|
+
from pyantigen.engine.Stop_rules import (
|
|
5805
|
+
format_shadow_summary, shadow_summary)
|
|
5806
|
+
cfg = (dict(shadow_stop_rules)
|
|
5807
|
+
if isinstance(shadow_stop_rules, dict) else {})
|
|
5808
|
+
cfg.setdefault("threshold", float(threshold))
|
|
5809
|
+
text = format_shadow_summary(shadow_summary(
|
|
5810
|
+
[r for pts in completed.values() for r in pts.values()],
|
|
5811
|
+
nll_at_optimum, cfg))
|
|
5812
|
+
if text:
|
|
5813
|
+
print(f"\n{text}", flush=True)
|
|
5814
|
+
except Exception as exc: # reporting must never end a run
|
|
5815
|
+
print(f"[profile] shadow stop rules: summary failed ({exc})",
|
|
5816
|
+
flush=True)
|
|
5768
5817
|
if convergence["incomplete"]:
|
|
5769
5818
|
parts = []
|
|
5770
5819
|
if still_unfinished:
|
|
@@ -5940,6 +5989,8 @@ def _run_warm_passes(nll_batch_profile, completed, param_names, res_x, meta,
|
|
|
5940
5989
|
# probe) and is counted by the refinement budget, so it must
|
|
5941
5990
|
# survive a warm re-run regardless of which value won.
|
|
5942
5991
|
keep["phase"] = prev.get("phase", res.get("phase", 1))
|
|
5992
|
+
if prev.get("reruns_tried"):
|
|
5993
|
+
keep["reruns_tried"] = prev["reruns_tried"]
|
|
5943
5994
|
keep["warm_refined"] = int(
|
|
5944
5995
|
(prev or {}).get("warm_refined", 0)) + 1
|
|
5945
5996
|
# How much re-running this point warm actually lowered it. This is
|
|
@@ -128,6 +128,15 @@ class PreequilCache:
|
|
|
128
128
|
if block is None:
|
|
129
129
|
return solver_settings
|
|
130
130
|
|
|
131
|
+
# The cached block is integrated on its own, without the schedule's
|
|
132
|
+
# steps (see Segmented). A schedule that switches inside it cannot be
|
|
133
|
+
# replayed from a cached end state.
|
|
134
|
+
schedule = solver_settings.get("schedule")
|
|
135
|
+
if schedule is not None:
|
|
136
|
+
from pyantigen.engine.Segmented import switches_before
|
|
137
|
+
if switches_before(r, schedule, float(block["end"])):
|
|
138
|
+
return solver_settings
|
|
139
|
+
|
|
131
140
|
configure_integrator(r, solver_settings)
|
|
132
141
|
key = _digest(save_model_state(r), block, solver_settings)
|
|
133
142
|
|