fix: report the real quick-update cadence and JAX compile status - #1436
Merged
Conversation
The CLI told users two untrue things while a search ran.
The workspace scripts printed the *name* of the cadence knob
("On-the-fly updates every iterations_per_quick_update"), so the
number never reached the terminal. The cadence now comes from the
library instead of 23 copies of a hand-written sentence:
`AbstractSearch.quick_update_message`, logged once at search start.
The packaged default is the inf-like 1e99 sentinel, so the message
has two branches -- a real integer cadence, or a plain statement
that updates are disabled and which config key enables them.
Announcing that cadence exposed the reason it had never been
noticed: only Nautilus forwarded `iterations_per_quick_update` when
building its Fitness, so `manage_quick_update` returned at its
`is None` guard for every other search. Setting a cadence under
Dynesty, Emcee, Zeus, BlackJAX NUTS, BFGS or Drawer did nothing at
all. Those six now forward it, which turns the new message from a
false claim into a true one. MultiStartGradient is deliberately
excluded -- it differentiates `fitness.call` inside its own
jit/vmap step loop, where the Python-side counter would run once at
trace time (see #1433) -- and a new AST test guards every call site
so the next search cannot omit it silently.
Wiring those searches up in turn surfaced a latent crash:
`manage_quick_update` called `.tolist()` on the parameter vector,
which held only because Nautilus passes an ndarray. Dynesty's
initializer passes a plain list, and the call raised mid-fit.
Separately, `jax.jit`/`vmap`/`grad` return instantly -- compilation
happens on the first call to what they return. The old logging sat
at wrapper-construction time, announcing a wait that had not
started and reporting ~0 seconds immediately before an unexplained
pause that can run to minutes. `log_on_first_compile` moves the
message to where the wait is, and reports the true elapsed time.
`analysis/latent.py` had the identical bug and now shares the
helper rather than duplicating it.
Closes #1434
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015qjakpNaigkwpGzbW2Qc9S
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
Two things the CLI told a user during a search were untrue, and fixing the second one exposed the reason nobody had noticed the first.
The cadence was never printed. The workspace scripts printed the literal token
iterations_per_quick_updaterather than its value. The message now comes from the library —AbstractSearch.quick_update_message, logged once at search start — so 23 workspace scripts can drop their hand-written copy (follow-up issue). It has two branches because the packaged default is the inf-like1e99sentinel: a real integer cadence, or a plain statement that updates are disabled naming the config key that enables them. Reporting the default as "every 1e+99 iterations" would have been true and useless.Only Nautilus ever honoured the cadence. Announcing it surfaced why: Dynesty, Emcee, Zeus, BlackJAX NUTS, BFGS and Drawer all built their
Fitnesswithout forwardingiterations_per_quick_update, somanage_quick_updatereturned at itsis Noneguard and the search silently never updated. Same model, same cadence of 25, fresh output dir: Nautilus produced updates, Dynesty produced none. Those six now forward it — otherwise the new message would be an active lie rather than a cosmetic bug.multi_start_gradientis deliberately exempt (it differentiatesfitness.callinside its own jit/vmap step loop, where the Python-side counter runs once at trace time — that path is #1433's), and an AST test now guards every construction site so the next search cannot omit it silently.Wiring those searches up exposed a latent crash.
manage_quick_updatecalled.tolist()on the parameter vector — fine for Nautilus's ndarray,AttributeErrormid-fit for the plain list Dynesty's initializer passes. Normalized withnp.asarray(...).The JAX compile message fired before the compile.
jax.jit/vmap/gradreturn instantly; tracing, lowering and XLA compilation happen on the first call to what they return. The old logging sat at wrapper-construction time, announcing a wait that had not started and reporting ~0 seconds immediately before an unexplained pause that can run to minutes.log_on_first_compilemoves the message to where the wait actually is and reports the true elapsed time.analysis/latent.pyhad the identical bug and now shares the helper instead of duplicating it.API Changes
Adds
AbstractSearch.quick_update_messageand a newautofit.non_linear.jax_compilemodule. No signatures changed and nothing was removed.The behavioural change worth flagging is that six searches now actually perform on-the-fly quick updates when
iterations_per_quick_updateis set — previously only Nautilus did. Users on the packaged1e99default see no change beyond one new startup log line. The JAX compile log lines are reworded and moved to first-call.See full details below.
Test Plan
1615 passed, 2 skipped(was 1610 before; the skip is the documentedmulti_start_gradientexemption)listandtuplecases. A regression test that cannot fail proves nothing.DynestyStaticfit, identical input and fresh output directory each run: 0 quick updates → 1DynestyStatic+ JAX fit end-to-end:On-the-fly updates ... are disabled. Set 'updates: iterations_per_quick_update' in config/general.yaml ...jax.jitfunction: nothing logged at wrap time; reported 0.7s matched the real 0.7s wall-clock; second call silent and 64x fasterFull API Changes (for automation & release notes)
Added
autofit.non_linear.search.abstract_search.AbstractSearch.quick_update_message— property returning the one-line startup message describing the on-the-fly update cadence, or a statement that updates are disabledautofit.non_linear.search.abstract_search.ITERATIONS_NEVER— module constant (1e90); cadences at or above it mean "never", covering the packaged1e99sentinel and any larger hand-set valueautofit.non_linear.jax_compile.log_on_first_compile(func, description)— wraps a JAX-transformed callable so the compile message is emitted on its first invocationChanged Behaviour
DynestyStatic/DynestyDynamic,Emcee,Zeus,NUTS,BFGS,Drawer— now forwarditerations_per_quick_update,background_quick_updateandlive_visual_updateto theirFitness. Setting a finite cadence on these searches previously did nothing; it now produces quick updates at that cadence. No change on the packaged1e99default.AbstractSearch.fit— logs one additional line at search start (quick_update_message)Fitness.manage_quick_update— accepts any parameter container (list, tuple, ndarray, JAX array); previously raisedAttributeError: 'list' object has no attribute 'tolist'for anything without.tolist()Fitness._jit/._vmap/._grad— the JAX compile log now fires on first call rather than at construction, with wordingJAX jit compiling <what>, could take seconds or minutes...and an honest elapsed time on completionautofit.non_linear.analysis.latent.latent_samples_from— same log relocation for the latent-variable compileNot changed (deliberate)
multi_start_gradient— not wired to quick updates; its step loop differentiatesfitness.callunder jit/vmap, so the Python-side counter would run once at trace time. Progress reporting for those searches is PyAutoFit#1433. Recorded as an explicit exemption with its reason intest_quick_update_wiring.py.Migration
Closes #1434
Generated by the PyAutoLabs agent workflow.