feat: periodic-equilibrium warm-start for nuts-on-ode (gh#396)#399
Draft
vsbuffalo wants to merge 5 commits into
Draft
feat: periodic-equilibrium warm-start for nuts-on-ode (gh#396)#399vsbuffalo wants to merge 5 commits into
vsbuffalo wants to merge 5 commits into
Conversation
Revise against the 3-reviewer review + garki grounding. Key changes from v1: - Conservation-aware bordered/KKT solve. Closed models make (I-M) singular (cᵀM=cᵀ); garki is conserved k=4 (per-village, dynamical birth=δ·Nvil balance). Detect the conserved directions numerically via SVD — robust because genuine conservation sits at machine-precision singular values, ~10 orders from the slowest real mode. Solve on the conservation manifold; the sensitivity reuses the same LU and the ic_grad seed for the conserved-direction part. - T_eq is a single declared anchor, decoupled from the per-stream cond_from (which stays per-stream over [T_eq, t_end]). - Seed trap pinned: the ∂Φ_P/∂θ read is a SensSeed::Zero (S(0)=0) one-period solve — the partial, not a total derivative. - Monodromy mode is a real SensSeed branch (not a param_model_idx trick), threaded through sensitivity_derivs/rk4_step. - mh-on-ode wired too (compute_ode_loglik injection), not just nuts. - Pre-obs periodicity validity gate: refuse events/interventions in [origin, T_eq]; numerical C(t)≈C(t+P) check (interpolated forcings carry no declared period — the garki case). - CLI + TOML surface via the run input system; warm-start re-keys the run. Rust-only (fit-time option, no IR/OCaml/golden change). Measured motivation carried forward: relaxation ~56 yr ≫ 5-yr window (frozen ICs biased, don't wash out); global coarse dt hard-blocked by coprime survey dates; per-phase dt ~5-8x but 1%-biased.
…h#396)
The core of the ODE warm-start: solve the seasonal limit cycle as the
fixed point of the one-period stroboscopic map instead of integrating the
60-year transient, with the exact parameter-sensitivity the NUTS/mh
gradient needs. Not yet wired into a fit path (next commit).
Integrator (ode.rs):
- Phase A: `integrate_obs_sensitivity` takes `x0: Option<&[f64]>` to begin
from an injected continuous state (overrides initial_state_continuous).
Every existing caller passes None → byte-identical.
- Monodromy mode: a `forcing: bool` on `Sens`, threaded to
`sensitivity_derivs`. When false the ∂rate/∂θ summand is dropped, so the
identity-seeded run integrates Ṡ = J_x·S → M = ∂x(T)/∂x₀. Carried in the
Sens struct so rk4_step's call sites don't change.
- `period_flow`: augmented (x,S) over one period, the solver's building
block (monodromy with forcing off / ∂Φ/∂θ partial with a zero seed).
Solver (ode_equilibrium.rs):
- Newton on X* = Φ_P(X*;θ) via the monodromy; ∂X*/∂θ = (I−M)⁻¹∂Φ_P/∂θ (IFT).
- Conservation-aware: closed models make (I−M) singular (cᵀM=cᵀ). Detect
the conserved directions numerically via SVD — genuine conservation sits
at machine-precision singular values (RK preserves linear invariants
exactly), ~10 orders below the slowest real mode, so the threshold is
robust. Solve on the manifold {CᵀX=CᵀX_init} with a bordered/KKT system;
the sensitivity reuses the factorization and the ic_grad seed for the
conserved part. Endemic gate refuses a non-hyperbolic transverse cycle.
Oracles (all green): fixed-point residual + conservation (k, N preserved);
monodromy vs central FD; ∂X*/∂θ vs FD of the equilibrium re-solve (the
seed-trap catcher); X* vs a long burn-in (the attracting cycle). Fixture
is a freshly-compiled closed seasonal SEIRS — the ir/golden copies predate
the gh#275 rate_state_grad emission (a separate staleness, noted).
nalgebra added (SVD + bordered LU). Rust-only; no IR/schema change.
Proposal: docs/dev/proposals/2026-07-07-ode-periodic-equilibrium-warmstart.md
…396)
Turn the solver on from a fit. det_grad takes an optional WarmStart
{t_eq, period}: when set, it solves the equilibrium at T_eq and seeds the
[T_eq, t_end] integration with (X*, ∂X*/∂θ) instead of integrating the
transient from origin. Threaded through OdeNutsConfig → run_ode_nuts →
both det_grad call sites (value + gradient).
Surface (fit.toml, on the nuts stage — consistent with the sibling
stage knobs dense_mass/warmup/target_accept, which are TOML-only):
[stages.posterior]
algorithm = "nuts"
backend = "ode"
warm_start = "equilibrium" # default "none"
warm_start_period = 365.25 # forcing period P (required)
warm_start_at = 1970.0 # T_eq; default = earliest first_obs
warm_start_* are identity-defining (they change the scored trajectory),
so they enter the stage's run-identity hash and re-key the run.
WarmStartKind enum + NutsStageOpts fields + resolution in run_stage
(require period > 0; default T_eq = min first_obs). ode_equilibrium is
now `pub` so the cli crate can name WarmStart.
v1 scope: nuts only. mh does not expose the option (an honest absence, not
a silently-ignored flag) — a named follow-up. Existing ode tests green
(31 + 3 det_grad FD), no regression from the det_grad signature change.
Refuse a non-P-periodic forcing over the burn-in window before any gradient is taken. An `interpolated`/table forcing (the garki case) carries no declared period, so periodicity is checked numerically: `derivs_at` evaluates dx/dt at a fixed reference state, and the gate (run once at fit setup) asserts the vector field repeats with period P across [T_eq-P, T_eq]. A mismatch → hard error naming the failure, so a misconfigured warm_start_period/warm_start_at cannot silently seed a spurious equilibrium. Test: the sinusoidal seasonal fixture passes at its true period 365.25 and is refused at a wrong period (100) and a non-positive period.
fit-toml.md: a "Periodic-equilibrium warm-start" section (the warm_start / warm_start_period / warm_start_at options, when it applies, the refusal conditions) + warm_start in the nuts stage-knob table. inference.md: a pointer from the ODE Bayesian-fit section for the long-seasonal-spin-up case.
vsbuffalo
marked this pull request as draft
July 8, 2026 15:23
Owner
Author
|
Converting to draft — this is a correct-but-not-yet-viable foundation, not for merge. Status: correct math, validated on small models (5 oracle tests green), but does not scale to densely-coupled models. Findings from real-model testing (garki mv_age, 196 comp) + two expert reviews:
Where this method belongs: sparsely-coupled seasonal models that are genuinely at their annual cycle. If we invest, build it the reviewers' way: symbolic projection/elimination for conservation (not SVD/KKT-first), Anderson-accelerated period map, matrix-free Newton–Krylov fallback, stiff-solver option, period-2/-k diagnostics. The immediate garki unlock is being pursued separately via calendar-aware coarse-dt burn-in (simpler, faithful, no dense-coupling penalty). |
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.
Periodic-equilibrium warm-start for
nutsonode(gh#396)Skips the seasonal spin-up transient in gradient ODE fits by solving the limit
cycle directly, instead of re-integrating decades of burn-in on every gradient.
The measured motivation (garki
mv_age,origin = 1910): one augmented-ODEgradient ≈ 1 min, a single NUTS warm-up iteration > 7 min ⇒ a full fit = days,
zero samples. The cost is the ~63-year transient, re-integrated per eval.
Approach
The seasonal equilibrium at the data-window start
T_eqis the fixed point ofthe one-period stroboscopic map,
X* = Φ_P(X*; θ). We solve it by Newton usingthe monodromy
M = ∂Φ_P/∂X₀, and seed the data-window integration with theexact sensitivity the sampler needs,
∂X*/∂θ = (I − M)⁻¹ ∂Φ_P/∂θ(implicitfunction theorem). The 63-year transient collapses to a handful of one-year
period-solves plus dense linear solves.
Reuses the gh#275 augmented forward-sensitivity RK4 verbatim —
Ṡ = J_x·Sisalready the monodromy variational equation; a
forcingtoggle on the sensitivityseed turns off the
∂rate/∂θterm to integrateM.Conservation (the load-bearing correction)
A closed model conserves a linear quantity (
cᵀM = cᵀ), so(I − M)is singularendemic-or-not — and garki is conserved (per-village population, k = 4). We
detect the conserved directions numerically via SVD: because any RK method
preserves linear invariants exactly, genuine conservation sits at machine-
precision singular values (~1e-12), ~10 orders below the slowest real mode, so
the threshold is robust. The fixed point is then solved on the conservation
manifold with a bordered/KKT system; the sensitivity reuses the factorization and
the
ic_gradseed for the conserved directions. An endemic gate refuses anon-hyperbolic transverse cycle.
Validity
interpolated/table forcing carries no declaredperiod, so
warm_start_periodis required and the forcing is checkednumerically — the vector field must repeat with period
Pover the burn-inwindow, else a hard error (a misconfigured anchor can't silently seed a spurious
equilibrium).
rk45,dt-in-rate, real compartments — already refused bythe existing
det_gradcapability gate.Surface (
fit.toml, on thenutsstage)Consistent with the sibling stage knobs (
dense_mass,warmup, …), which areTOML-only.
warm_start_*are identity-defining and re-key the run.nuts-onlyin v1 —
mhdoes not expose the option (an honest absence, a named follow-up).Tests
Five solver oracles (all green): fixed-point residual + conservation (k, N
preserved); monodromy vs central FD; ∂X*/∂θ vs FD of the equilibrium re-solve
(the partial-vs-total seed-trap catcher); X* vs a long burn-in (confirms it's
the attracting cycle); the periodicity gate accepts the true period and refuses a
wrong one. No regression in the existing ODE gradient suite (31 + 3 det_grad FD).
Fixture is a freshly-compiled closed seasonal SEIRS. Note: the committed
ir/golden/*.ir.jsonpredate the gh#275rate_state_grademission (they carry anempty
rate_state_grad,skip_serializing_if_empty) — a separate staleness wortha
make update-golden, filed as a follow-up; real fits compile fresh and areunaffected.
Rust-only (a fit-time runtime option) — no IR/schema/OCaml/golden change.
Follow-ups
mh-on-odewarm-start (needs onlyX*, no sensitivity).dx/dtmodels.P; block-sparse bordered solve for national scale.mv_age(garki agent).Proposal:
docs/dev/proposals/2026-07-07-ode-periodic-equilibrium-warmstart.md.