Skip to content

feat: search capability declarations, fail-fast gate, lazy registry and run(ctx) design note (search-extensibility A1) - #1675

Merged
Jammy2211 merged 14 commits into
mainfrom
feature/search-ext-a1-declare-gate
Oct 8, 2026
Merged

Jammy2211 merged 14 commits into
mainfrom
feature/search-ext-a1-declare-gate

Conversation

@Jammy2211

@Jammy2211 Jammy2211 commented Oct 8, 2026 •

Copy link
Copy Markdown
Collaborator

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_jax replaces the nine scattered _use_jax probes and factor graphs derive their backend from their factors (D12); a JAX-required search given a numpy analysis raises one shared SearchException after 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 --check job (D18); and docs/design/run_ctx.md freezes the run(ctx) signature and FitContext members for A2 (D7). Eight commits in that order. Shipped under the human's 2026-10-08 --auto launch at effective level supervised; tier judge → human /prm. Merge this before the three downstream docs PRs (their intersphinx label resolves once RTD latest rebuilds).

Golden identifiers unchanged: no capability attribute is in any __identifier_fields__ (tested).

Decision taken (decide-and-flag, one)

Factor-graph backend rules. FactorGraphModel and ModelAnalysis now default use_jax=None and derive the flag from their factors (the plan's "a FactorGraphModel inherits use_jax"); an explicit use_jax=True with disagreeing factors raises; factors declaring different gradient_modes fall back to "reverse"; the agreement check sits next to the REQUIRED gate, after the test-mode bypass, so a mixed graph under PYAUTO_TEST_MODE>=2 is not caught. Rejected alternative: check before the bypass (stricter, riskier for smoke runs) and keep use_jax=False as the default (would not inherit). One-command revert of the placement: move the isinstance(analysis, FactorGraphModel) block in abstract_search.py above mode = test_mode_level(); the default change reverts with git revert 196abf39a (which also removes is_jax).

Judgement values for the reviewer (one line each in the class and registry.py): status experimental for NSS, SMC, ADABelief, Lion; warm_start provider for BFGS/LBFGS/MultiStart (the Brain samplers faculty's "mode-finders provide a point"); resumable False for Emcee (report §3.1 literally); batched True for NUTS (vmaps over chains). The §3.1 checkpointer attribute 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; modules autofit.non_linear.search.capabilities and autofit.non_linear.search.registry; the python -m autofit search-manifest CLI. Changed: FactorGraphModel(use_jax=None)/ModelAnalysis(use_jax=None) derive the flag (was False); HierarchicalFactor honours PYAUTO_DISABLE_JAX; JAX-required searches raise SearchException instead of ValueError; search.summary/model.info start with a capability header; search_summary_to_file(..., search_capabilities=None); FactorGraphModel.tree_flatten aux carries use_jax.
See full details below.

Test Plan

  • pytest test_autofit -x: 3409 passed, 2 skipped, 9 xfailed (the A0 strict xfails only, none added)
  • nojax emulation over non_linear, analysis and the generated-docs test: 1192 passed, 132 skipped, 7 xfailed; import autofit imports no optional backend (checked via sys.modules)
  • Sphinx full env and emulated minimal env: 30 warnings, equal to docs/sphinx_warning_baseline.txt; matrix renders 15 rows; docs/_generate_searches.py --check up to date
  • PyAutoLens 832 passed / 1 xfailed; PyAutoGalaxy 1357 passed
  • afW searches/{mcmc,nest,mle} and afT Nautilus, NSS exit 0; afT BlackJAXNUTS/MultiStartAdam fail their own accuracy asserts identically on main (control run)
  • REQUIRED + PYAUTO_DISABLE_JAX=1 PYAUTO_TEST_MODE=2 completes for NUTS, MultiStartAdam, NSS
  • CI green on unittest 3.12 / 3.13 / nojax, docs, and the new generated-search-docs job
Full API Changes (for automation & release notes)

Removed

  • none (the _use_jax probes are replaced internally; Analysis.use_jax is unchanged)

Added

  • Analysis.is_jax — read-only, the single JAX probe
  • AnalysisFactor.is_jax, AnalysisFactor.gradient_mode
  • FactorGraphModel.factors_disagreeing_on_backend(), FactorGraphModel.check_backend_agreement(), FactorGraphModel.gradient_mode
  • ModelAnalysis.gradient_mode
  • Class attributes on every search: 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, invalid_value
  • autofit.non_linear.search.capabilities: JaxUse, Gradient, PosteriorKind, WarmStart, Status, ObjectiveTarget, CAPABILITY_ATTRIBUTES, capabilities_from, JAX_REQUIRED_MESSAGE, check_jax_required, capability_summary_from
  • autofit.non_linear.search.registry: SEARCHES, entries(), entry(), manifest(), MANIFEST_SCHEMA
  • python -m autofit search-manifest [--json]
  • docs/_generate_searches.py --check; docs/searches/{index,citations}.rst; docs/design/run_ctx.md

Migration

  • Before: getattr(analysis, "_use_jax", False) → After: analysis.is_jax
  • Before: a JAX-required search with a numpy analysis raised ValueError → After: autofit.exc.SearchException (message JAX_REQUIRED_MESSAGE)
  • Before: FactorGraphModel(use_jax=False) default → After: use_jax=None derives from the factors. Explicit FactorGraphModel(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 raises SearchException. To fit the whole graph on numpy, build every factor's analysis (and any HierarchicalFactor) with use_jax=False and leave FactorGraphModel(use_jax=...) unset

Validation checklist (--auto run — plan was not pre-approved)

  • Effective level: supervised (header: supervised, cap: feature@large → supervised); ship checkpoint resolved by decide-and-flag (one decision, above)
  • Plan: on the issue (feat: search capability declarations, fail-fast gate, lazy registry and run(ctx) design note (search-extensibility A1) #1674), written at start, unmodified since
  • Gate: tests 3409 pass / 2 skip / 9 xfail + nojax 1192 pass + downstream PyAutoLens 832 / PyAutoGalaxy 1357 · smoke afW searches ×3 + afT Nautilus/NSS exit 0 (NUTS/MultiStartAdam pre-existing accuracy failures on main) · review CLEAN (Fable session over the Opus-written branch; witness basis-cited: manifest 15 rows, sys.modules free of optional backends, SearchException raised for NUTS+numpy, generator --check current, design note present, 54 registry/docs tests pass) · Heart STALE release validation incomplete: no rehearsal for current source
  • Human: plan sound in hindsight?
  • Human: diff matches plan (no scope creep)? Check the flagged decision and the judgement values
  • Human: merge, amend, or reject — then log the outcome

Generated 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; shared SearchException for all seven REQUIRED searches; import free of optional backends; design note present). Verdict FINDINGS (6); all six reproduced and enacted in follow-up commits 2c07ebea6..53e39c9c0:

  1. P1 legacy FactorGraphModel pickles with _use_jax=False came back is_jax=True and could not flatten → __setstate__ migration + test · 2. P1 a graph over a HierarchicalFactor(use_jax=True) inferred is_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 unwraps ModelAnalysis/AnalysisFactor first + test · 4. NSS objective_target.space is physical (draws are transformed before algo.init) · 5. BFGS/LBFGS/MultiStart invalid_value is what the backend sees on NaN, +inf (×−2 of the −inf replacement), with the conditional −inf on FitException documented and pinned by a test for A3's sentinel normalisation · 6. Emcee resumable=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=None inheritance intent, NUTS batched=True, the experimental statuses and mode-finder warm_start=provider. After the fixes: 3417 passed / 2 skipped / 9 xfailed; golden table untouched; generator --check current; Sphinx 30 warnings = baseline; import autofit loads no optional backend. Nojax emulation: 1466 passed with 4 pre-existing order-dependent failures in graphical/ that reproduce identically on origin/main.

Jammy2211 and others added 8 commits October 8, 2026 10:35
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>
Jammy2211 and others added 6 commits October 8, 2026 13:46
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>
@Jammy2211
Jammy2211 merged commit 0dbf258 into main Oct 8, 2026
5 checks passed
@Jammy2211
Jammy2211 deleted the feature/search-ext-a1-declare-gate branch October 8, 2026 13:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

decision-taken pending-release PR queued for the next release build

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant