Repository navigation
feat: search capability declarations, fail-fast gate, lazy registry and run(ctx) design note (search-extensibility A1) - #1675
Merged
Conversation
Every search now declares jax_use, gradient, batched, honours_gradient_mode, posterior_kind, produces_evidence, resumable, warm_start, install_extra, upstream_url, citation_keys, status, test_mode_budget, objective_target and invalid_value as plain class attributes (str enums in the new autofit.non_linear.search.capabilities module), with defaults on NonLinearSearch. None is an identifier field; the A0 golden identifier table is unchanged. Search-extensibility phase A1, PyAutoFit#1674. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Analysis.is_jax replaces every getattr(analysis, "_use_jax", False) / analysis._use_jax probe in the searches, Fitness and the latent machinery. ModelAnalysis and AnalysisFactor forward is_jax and gradient_mode from the analysis they wrap. FactorGraphModel(use_jax=None) derives its backend from its factors and its gradient_mode from their common mode (reverse when mixed); whole-graph fitting raises SearchException naming any factor that disagrees, while per-factor EP still allows mixed factors. HierarchicalFactor honours PYAUTO_DISABLE_JAX and the graph flag survives pytree flattening and pickling. Search-extensibility phase A1, PyAutoFit#1674. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
NonLinearSearch.start_resume_fit raises one shared SearchException (capabilities.JAX_REQUIRED_MESSAGE) when a JAX-required search gets a numpy analysis, before any backend state exists. The gate sits after the test-mode bypass return, so PYAUTO_DISABLE_JAX=1 + PYAUTO_TEST_MODE=2 smoke runs still complete. BlackJAXNUTS, SMC and the MultiStart searches replace their own ValueError checks with the same gate (error type changes to SearchException); NSS gains it. Search-extensibility phase A1, PyAutoFit#1674. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
fit_x1_cpu built its Fitness with use_jax_vmap=self.use_jax_vmap (default True) whatever the analysis, so a numpy likelihood was traced through jax.vmap and raised TracerArrayConversionError. The vectorised path is now requested only when analysis.is_jax. Search-extensibility phase A1, PyAutoFit#1674. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
autofit/non_linear/search/registry.py lists the 15 public searches as data (class_path string, family, lazy, requires, the mirrored capability attributes, example and integration_test anchors) and never imports a search module. python -m autofit search-manifest --json publishes it as the versioned search-manifest@1, the only cross-repo format. Tests: completeness against the autofit exports (deleting an entry fails), entry == class attributes (skipped per entry when its backend is absent), manifest round trip, and no search module or optional backend imported. The conformance roster now derives jax_use/jax_modules from the registry; its golden table is unchanged. Search-extensibility phase A1, PyAutoFit#1674. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Both files now open with the search's class path and its static capabilities (JAX use, gradient, batched, posterior kind, evidence, resumable, objective, invalid value, status), from capabilities.capability_summary_from. Search-extensibility phase A1, PyAutoFit#1674. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
docs/_generate_searches.py writes docs/api/searches.rst, the new docs/searches/index.rst capability matrix (15 rows) and the canonical docs/searches/citations.rst from python -m autofit search-manifest --json and files/citations.bib; --check fails on a stale page and runs in a new Docs workflow job and in test_autofit. docs/conf.py mocks the optional sampler backends that are not installed (autodoc_mock_imports), so the minimal [docs] environment builds. Search-extensibility phase A1, PyAutoFit#1674. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
docs/design/run_ctx.md (RTD-visible, under Searches) freezes run(self, ctx) -> internal and the FitContext members (objective factory, model, paths, test-mode level, pool factory, start points, RNG, resume, checkpointer slot, schedule, update callback), its lifecycle rules and what the context does not hold, for phase A2 to implement. Search-extensibility phase A1, PyAutoFit#1674. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Graphs pickled before the backend was derived from the factors stored a plain `_use_jax` attribute, which the new `_use_jax` property shadows, so a legacy graph built with use_jax=False and JAX children re-derived is_jax=True and `tree_flatten()` raised AttributeError on the missing `_explicit_use_jax`. `__setstate__` now moves the old key over. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
`FactorGraphModel(h)` with `h = HierarchicalFactor(..., use_jax=True)` reported is_jax=False and then rejected its own JAX children: inference read the unflattened `_model_factors` (the HierarchicalFactor container, which has no `is_jax`) while the agreement check read the flattened ones. Both now read `_flat_factors()`. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
`search.fit(analysis=ModelAnalysis(graph, ...))` on a mixed numpy/JAX graph skipped `check_backend_agreement` and reached the backend, because the check only recognised a bare `FactorGraphModel`. The fit gate now unwraps `ModelAnalysis` and `AnalysisFactor` before the check. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
NSS draws its initial live points in the unit cube but transforms them before `algo.init`; the sampler then proposes physical vectors evaluated through `instance_from_vector` against a physical-space prior density. Declare `physical` in the class, the registry and the generated matrix. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ndition BFGS, LBFGS and the MultiStart family declared invalid_value=-inf, but a NaN likelihood reaches their backend as +inf: Fitness replaces it with -inf and the chi-squared conversion multiplies by -2. A FitException early return (and a failed traced assertion) still returns -inf. Declare the NaN path's +inf, record the conditional behaviour in the capability docstring and a footnote under the generated objectives table, and pin both observed values in a test. Normalising the sentinel is left to A3's adapter. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Emcee's `_fit` reopens its HDF backend, loads the last sample and iteration count and samples only the remaining steps, so it resumes from its own checkpoint under the published definition. The report's section 3.1 table listed it as not resumable; corrected in the class (with a note), the registry and the generated matrix. 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
Phase A1 of the search-extensibility epic (PyAutoFit#1674). Every search now declares its capabilities as class attributes; a declarative, lazy registry mirrors them with a completeness test and a versioned
python -m autofit search-manifest --json(search-manifest@1);Analysis.is_jaxreplaces the nine scattered_use_jaxprobes and factor graphs derive their backend from their factors (D12); a JAX-required search given a numpy analysis raises one sharedSearchExceptionafter the test-mode bypass (D2);Nautilus(force_x1_cpu=True)works with numpy; the search API page, an RTD capability matrix and a canonical citations page are generated from the manifest with a--checkjob (D18); anddocs/design/run_ctx.mdfreezes therun(ctx)signature andFitContextmembers for A2 (D7). Eight commits in that order. Shipped under the human's 2026-10-08--autolaunch at effective level supervised; tier judge → human/prm. Merge this before the three downstream docs PRs (their intersphinx label resolves once RTDlatestrebuilds).Golden identifiers unchanged: no capability attribute is in any
__identifier_fields__(tested).Decision taken (decide-and-flag, one)
Factor-graph backend rules.
FactorGraphModelandModelAnalysisnow defaultuse_jax=Noneand derive the flag from their factors (the plan's "a FactorGraphModel inherits use_jax"); an explicituse_jax=Truewith disagreeing factors raises; factors declaring differentgradient_modes fall back to"reverse"; the agreement check sits next to the REQUIRED gate, after the test-mode bypass, so a mixed graph underPYAUTO_TEST_MODE>=2is not caught. Rejected alternative: check before the bypass (stricter, riskier for smoke runs) and keepuse_jax=Falseas the default (would not inherit). One-command revert of the placement: move theisinstance(analysis, FactorGraphModel)block inabstract_search.pyabovemode = test_mode_level(); the default change reverts withgit revert 196abf39a(which also removesis_jax).Judgement values for the reviewer (one line each in the class and
registry.py):statusexperimental for NSS, SMC, ADABelief, Lion;warm_startprovider for BFGS/LBFGS/MultiStart (the Brain samplers faculty's "mode-finders provide a point");resumableFalse for Emcee (report §3.1 literally);batchedTrue for NUTS (vmaps over chains). The §3.1checkpointerattribute is deferred to A3.API Changes
Added
Analysis.is_jax;AnalysisFactor.is_jax/.gradient_mode;FactorGraphModel.factors_disagreeing_on_backend(),.check_backend_agreement(),.gradient_mode;ModelAnalysis.gradient_mode; the 15 capability class attributes on every search; modulesautofit.non_linear.search.capabilitiesandautofit.non_linear.search.registry; thepython -m autofit search-manifestCLI. Changed:FactorGraphModel(use_jax=None)/ModelAnalysis(use_jax=None)derive the flag (wasFalse);HierarchicalFactorhonoursPYAUTO_DISABLE_JAX; JAX-required searches raiseSearchExceptioninstead ofValueError;search.summary/model.infostart with a capability header;search_summary_to_file(..., search_capabilities=None);FactorGraphModel.tree_flattenaux carriesuse_jax.See full details below.
Test Plan
pytest test_autofit -x: 3409 passed, 2 skipped, 9 xfailed (the A0 strict xfails only, none added)non_linear,analysisand the generated-docs test: 1192 passed, 132 skipped, 7 xfailed;import autofitimports no optional backend (checked viasys.modules)docs/sphinx_warning_baseline.txt; matrix renders 15 rows;docs/_generate_searches.py --checkup to datesearches/{mcmc,nest,mle}and afTNautilus,NSSexit 0; afTBlackJAXNUTS/MultiStartAdamfail their own accuracy asserts identically on main (control run)PYAUTO_DISABLE_JAX=1 PYAUTO_TEST_MODE=2completes for NUTS, MultiStartAdam, NSSgenerated-search-docsjobFull API Changes (for automation & release notes)
Removed
_use_jaxprobes are replaced internally;Analysis.use_jaxis unchanged)Added
Analysis.is_jax— read-only, the single JAX probeAnalysisFactor.is_jax,AnalysisFactor.gradient_modeFactorGraphModel.factors_disagreeing_on_backend(),FactorGraphModel.check_backend_agreement(),FactorGraphModel.gradient_modeModelAnalysis.gradient_modejax_use,gradient,batched,honours_gradient_mode,posterior_kind,produces_evidence,resumable,warm_start,install_extra,upstream_url,citation_keys,status,test_mode_budget,objective_target,invalid_valueautofit.non_linear.search.capabilities:JaxUse,Gradient,PosteriorKind,WarmStart,Status,ObjectiveTarget,CAPABILITY_ATTRIBUTES,capabilities_from,JAX_REQUIRED_MESSAGE,check_jax_required,capability_summary_fromautofit.non_linear.search.registry:SEARCHES,entries(),entry(),manifest(),MANIFEST_SCHEMApython -m autofit search-manifest [--json]docs/_generate_searches.py --check;docs/searches/{index,citations}.rst;docs/design/run_ctx.mdMigration
getattr(analysis, "_use_jax", False)→ After:analysis.is_jaxValueError→ After:autofit.exc.SearchException(messageJAX_REQUIRED_MESSAGE)FactorGraphModel(use_jax=False)default → After:use_jax=Nonederives from the factors. ExplicitFactorGraphModel(use_jax=False)does not force JAX factors onto numpy: when it is fitted as one analysis, the agreement check rejects any factor that disagrees and raisesSearchException. To fit the whole graph on numpy, build every factor's analysis (and anyHierarchicalFactor) withuse_jax=Falseand leaveFactorGraphModel(use_jax=...)unsetValidation checklist (--auto run — plan was not pre-approved)
sys.modulesfree of optional backends,SearchExceptionraised for NUTS+numpy, generator--checkcurrent, design note present, 54 registry/docs tests pass) · Heart STALErelease validation incomplete: no rehearsal for current sourceGenerated by the PyAutoLabs agent workflow.
🤖 Generated with Claude Code
Independent adversary review (Codex gpt-6-astra), enacted 2026-10-08
Review archived at PyAutoMind
draft/research/autofit/search_extensibility_epic_reviews/04_codex_astra_a1_pr1675.md. Witness: all five clauses held (each of the 15 registry deletions detected; manifest 15 rows; sharedSearchExceptionfor all seven REQUIRED searches; import free of optional backends; design note present). Verdict FINDINGS (6); all six reproduced and enacted in follow-up commits2c07ebea6..53e39c9c0:FactorGraphModelpickles with_use_jax=Falsecame backis_jax=Trueand could not flatten →__setstate__migration + test · 2. P1 a graph over aHierarchicalFactor(use_jax=True)inferredis_jax=False→ backend derived from the flattened factors (the same set the agreement check reads) + test · 3.ModelAnalysis(graph)bypassed the whole-graph check → the gate unwrapsModelAnalysis/AnalysisFactorfirst + test · 4. NSSobjective_target.spaceisphysical(draws are transformed beforealgo.init) · 5. BFGS/LBFGS/MultiStartinvalid_valueis what the backend sees on NaN,+inf(×−2 of the −inf replacement), with the conditional −inf onFitExceptiondocumented and pinned by a test for A3's sentinel normalisation · 6. Emceeresumable=True(HDF backend resume), correcting the report §3.1 table.Kept on the adversary's recommendation: gates after the test-mode bypass, the
use_jax=Noneinheritance intent, NUTSbatched=True, the experimental statuses and mode-finderwarm_start=provider. After the fixes: 3417 passed / 2 skipped / 9 xfailed; golden table untouched; generator--checkcurrent; Sphinx 30 warnings = baseline;import autofitloads no optional backend. Nojax emulation: 1466 passed with 4 pre-existing order-dependent failures ingraphical/that reproduce identically onorigin/main.