Repository navigation
fix: A2/A3/A3b post-merge fixes from astra review 05 (search-extensibility) - #1683
Merged
Merged
Conversation
A Fitness pickled before the objective factory carries use_jax_vmap / use_jax_jit. __setstate__ discarded use_jax_jit and defaulted compile=True, so an eager (use_jax_jit=False) checkpoint resumed jitted and broke likelihoods valid only eagerly. Map the legacy flags onto compile exactly. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…runs Preflight traced the class-declared objective kind through a throwaway Fitness that ignored runtime options, so it rejected an eager use_jax_jit=False likelihood and a MultiStartAdam gradient_mode='reverse' override. NonLinearSearch.execution_settings(analysis) now resolves batching, compile, gradient use and gradient mode once (preflight.ExecutionSettings); preflight traces exactly that and the run(ctx) bridge, Dynesty and MultiStartGradient build their Fitness from the same object. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…eckpoint start_resume_fit wrote .completed straight after save_results, before post_fit_output archived search_internal, so an archive write that failed (disk full) left a 'completed' fit the rerun loaded without ever retrying the archive; NSS also deleted its resume checkpoint inside run(). The fit now finalizes its retained state (archive, then resume-state discard) in NonLinearSearch._complete_fit before writing .completed, so a failure leaves the fit resumable and the rerun finalizes it. NSS no longer discards its checkpoint in run(). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Outside fit() there is no fit-scoped PoolFactory, and _pools() fell back to PoolFactory(self) without the analysis, so Nautilus(number_of_cores=2) ._fit(model, jax_analysis) saw is_jax=False and took the forking multiprocessing path. _pools(analysis) now applies the JAX fork rule for direct callers; the run(ctx) bridge, Dynesty, the initializer and the sneaky pool pass the analysis they hold. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…checkpoint The frozen run(ctx) contract promises cleanup on every exit and a checkpoint save on each update. ctx.close(failed=False) returned at once, leaking context-built pools after a successful run, and ctx.update only called perform_update, so a resumable run(ctx) search lost its progress on interruption. PoolFactory.close(failed) now closes+joins on success and terminates+joins on failure (idempotent); ctx.update saves the search's resume_state (never finalize) before the update. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ership test_samples_info_is_unchanged compared ESS / R-hat by shape only, so scalar diagnostics had no protection and a shape-preserving -999 passed. They are now compared by value to the fixture (rtol 0.25, finite, positive), exactly against the installed blackjax estimators on the stored chains, and via deterministic estimator doubles that must be forwarded unchanged; a parametrized sentinel test proves every key fails on -999. The samples.csv tolerance (rtol 1e-12) is unchanged. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes all seven findings of the post-merge astra review 05 of the search-extensibility A2/A3/A3b PRs (#1679/#1680/#1681), which shipped in 2026.10.11.1 / 2026.10.12.1. One commit per item; every fix has a test that was confirmed failing on untouched
main(911fe05) first.1. Old
Fitnesspickle silently enabled JIT (F1, P1) —5402c7619Finding.
Fitness.__setstate__popped the legacyuse_jax_jitflag and defaultedcompile=True, so an A1-era eager checkpoint resumed jitted (a likelihood valid only eagerly then raisedTracerArrayConversionError).Fix. The legacy flags map onto
compileexactly: vmap -> compiled batched, jit -> compiled scalar, neither -> eager scalar. A pickle predating both flags also dispatched eagerly, so it stays eager.Tests.
test_fitness_objective.py::test_legacy_eager_pickle_stays_eager(hand-built A1__dict__,use_jax_jit=False, eager-only likelihood evaluates to 50.0 and the restored_callhas no__wrapped__),test_legacy_jit_and_vmap_pickles_stay_compiled.2. Preflight validated the declared config, not the resolved one (F3) —
1f33755f5Finding. Reproduced both false rejections on main:
DynestyStatic(use_jax_jit=False)+ eager likelihood ->TracerArrayConversionError;MultiStartAdam(gradient_mode="reverse")+ forward-declared analysis +custom_vjp-> "can't apply forward-mode autodiff".Fix. New
preflight.ExecutionSettings(batched, compile, uses_gradient, gradient_mode, fitness_overrides), resolved once byNonLinearSearch.execution_settings(analysis)from the search'sfitness_overridesand itsgradient_modeoverride (whenhonours_gradient_mode). The preflight traces exactly those kinds/modes (an eager scalar execution is not traced; grad kinds always are), and therun(ctx)bridge, Dynesty (use_jax_jitnow flows throughAbstractDynesty.fitness_overrides) andMultiStartGradientbuild theirFitnessfrom the same object.Tests.
test_preflight.py: eager Dynesty passes preflight andfitreaches the backend; the reverse override passes while the un-overridden search still fails;Nautilus(use_jax_vmap=False)traces'scalar'; a jitted search still rejects an untraceable likelihood.3.
.completedwritten before the archive finalizes; NSS discarded its checkpoint early (F2, P1) —edcf35116Finding.
start_resume_fitwrote.completedright aftersave_results; the archive (output_search_internal) ran later inpost_fit_output. A failed archive write left a "completed" fit whose rerun never retried. NSS also deletednss_checkpoint.pklinsiderun().Fix. New
NonLinearSearch._complete_fit: finalize retained state (archive, then the resume-state discard it supersedes; or removesearch_internal) and only then write.completed. Used by the real fit and the test-mode bypass;post_fit_outputno longer re-finalizes a fit that just ran (it still zips, and still tidies an already-completed folder). NSS no longer discards inrun(); the generic discard runs after the archive..completedis still inside the zip (zip happens after the marker).Tests.
test_checkpoint.py::test_a_failed_archive_write_leaves_the_fit_incomplete_and_a_rerun_finishes_it(realDirectoryPathson disk, Drawer whose archive raisesOSError("simulated disk full")once: no marker after the failure; the rerun finalizes, archive and.completedare in the zip);nss/test_checkpoint.py::test__run_keeps_its_checkpoint_until_the_result_is_written.4. Direct
_fitbuiltPoolFactory(self)without the analysis (F4) —86299f6e8Finding. Reproduced:
Nautilus(number_of_cores=2)._fit(model, jax_analysis)chosefit_multiprocessingwithis_jax=False, cores 2.Fix.
_pools(analysis=None)buildsPoolFactory(self, analysis)when no fit-scoped factory exists; the bridge passes the analysis. The same latent gap was closed where the analysis is in hand: Dynesty's core count, the initializer'sn_cores(fitness.analysis) andmake_sneaky_pool.Test.
test_fork_rule.py::test_direct_fit_entry_applies_the_fork_rule_to_a_jax_analysis(fork_contextpatched to raise; asserts the single-process path is taken).5.
FitContextleaked pools on success;ctx.updatenever saved the checkpoint (F5, F6) —8f35343deFinding. Reproduced both: a toy
run(ctx)search that callsctx.pool()got 0 close/join on success; a resumable toy search interrupted afterctx.updatehad 0 checkpoint saves.Fix.
PoolFactory.close(failed)closes+joins on success and terminates+joins on failure (idempotent, tolerant of pools a backend already shut down);ctx.closecalls it on both exits.ctx.updatecallsresume_state.save(paths, internal)(neverfinalize) beforeperform_update; native resume states keep their no-op save; a search without a resume state saves nothing mid-run.docs/design/run_ctx.mdmember notes updated to match.Tests.
test_fit_context.py: success ->created, close, join; failure ->created, terminate, join; idempotent close; interrupted resumable toy search -> onesave, nofinalize, file on disk; non-resumable search saves nothing mid-run.6. Shape-only golden diagnostics (F7) —
2090ca9ddFinding. Reproduced: replacing any
BACKEND_DIAGNOSTIC_KEYSentry with a shape-preserving -999 passedtest_samples_info_is_unchanged("nuts")(8/8 mutations passed).Fix. Diagnostics are compared by value to the fixture (
DIAGNOSTIC_RTOL = 0.25, finite, positive), exactly against the installed blackjax estimators on the stored chains, and through deterministic estimator doubles whose outputs must be forwarded unchanged (ownership). The CSVassert_allclose(rtol=1e-12, atol=0)is unchanged.Tests.
test_sentinel_diagnostics_fail_the_golden_comparison[<key>]x8,test_diagnostics_are_the_installed_blackjax_reference,test_diagnostics_forward_the_backend_outputs.7. Witness: disk-backed checks in a writable environment
Old-era folders were produced by checking out tag
2026.10.7.1(710f4b3) in a detached scratch worktree (git worktree add <scratchpad>/old_wt 2026.10.7.1) and running a tiny Gaussian fit with that PyAutoFit first onPYTHONPATH(its source__version__prints the stale2026.8.17.1stamp; the code is the tag's). The worktree was removed afterwards. Script:fit.py <dynesty|emcee> <name>,DynestyStatic(nlive=50, iterations_per_full_update=1500)/Emcee(nwalkers=20, nsteps=400);CRASH_AFTER=Nhard-kills the process withos._exit(9)after N likelihood calls (no cleanup runs).(a) Pre-release output folders loaded with the new code
(The old run reported 929 Dynesty samples in-process;
samples.csvdrops weights < 1e-10, hence 433 on load. Same value on old and new.)(b) Interrupt and resume (Dynesty and Emcee), new code, and old-era interrupted runs resumed by the new code
At
CRASH_AFTER=4000the 2026.10.7.1 Dynesty run happened to finish in 3620 calls, so it was repeated withCRASH_AFTER=2500:(c) Zipped restore (unzipped output folder deleted, leaving only
<identifier>.zip; rerun with the new code)The archive-failure path itself (finding 2) is witnessed on a real on-disk
DirectoryPathsby the item-3 test.Findings that did not reproduce
None: all seven reproduced on 911fe05 (each new test failed on untouched code; item 2's behaviour was also reproduced directly through
trace_preflightbefore the API existed).Decisions taken (not settled by the plan)
ExecutionSettingslives inautofit/non_linear/search/preflight.py, exposed asNonLinearSearch.execution_settings(analysis); an eager (compile=False) scalar kind is skipped by the trace, grad and batched kinds are still traced (vmap/grad trace even eagerly).ctx.updatesaves the resume state only (the file a run resumes from), beforeperform_update, so the checkpoint is never behind the written samples; it does not write the archive mid-run.skip_fit_output()aspost_fit_outputwas. UnderPYAUTO_SKIP_FIT_OUTPUT=1NSS now leaves its checkpoint beside.completed(harmless: a completed folder never resumes)._pools(analysis)fix was extended to Dynesty, the initializer andmake_sneaky_pool(same latent gap); Zeus'smake_pool()is unchanged (no analysis in that signature).pytest -n autodoes not work on this suite (xdist reports different collections between workers from object-address parametrize ids intest_prior_properties.py, and parametrized fits share output dirs), so both legs ran serially.Test results
python -m pytest test_autofit -q -p no:xdistunittest-nojaxemulationsitecustomizeonPYTHONPATHwhose meta-path finder raisesModuleNotFoundErrorforjax, jaxlib, jaxnnls, optax, chex, jax_zero_contour, blackjax, nufftax, tensorflow_probability(andimportlib.util.find_specreturnsNonefor them, as with the packages uninstalled)New jax/blackjax tests use
pytest.importorskip(or the golden_skip_missingguard).Sphinx:
python -m sphinx -b html docs <out> -w warnings.log-> 30 warnings (baselinedocs/sphinx_warning_baseline.txt= 30, not exceeded).python docs/_generate_searches.py --check: up to date.Closes #1682
🤖 Generated with Claude Code