Skip to content

feat(state-space): exact discrete NB-branching latent with particle Gibbs (issue #48)#116

Draft
seabbs-bot wants to merge 3 commits into
mainfrom
arch-state-space
Draft

feat(state-space): exact discrete NB-branching latent with particle Gibbs (issue #48)#116
seabbs-bot wants to merge 3 commits into
mainfrom
arch-state-space

Conversation

@seabbs-bot
Copy link
Copy Markdown
Collaborator

Note

Draft — agent-generated. Not for merge without human review.

Candidate redesign for issue #48: a state-space architecture in which the latent daily infection counts follow the exact discrete-time NB-overdispersed branching renewal step, with particle Gibbs as the inference route for the integer latent path and Mooncake-differentiable continuous nuisances for NUTS inside Gibbs.
Sibling to the LNA route in #101 and the deterministic renewal route in #103.

Architecture

  • Latent state: a daily grid t = 1, …, n. Day 1 seeded from a small-cluster prior. Each subsequent day I_t | I_{<t}, R_t ~ NegativeBinomial(mean = R_t · Σ_s I_{t-s} g_s, dispersion = φ), the exact NB-overdispersed branching renewal step. φ → ∞ recovers the Poisson branching limit.
  • Continuous nuisances: weekly piecewise-linear log-R_t with non-centred Gaussian-walk knots; Gamma generation interval (mean and SD priors); Gamma incubation, onset-to-death, onset-to-report and onset-to-detection delays (each with priors on parameters); CFR; surveillance dispersion (NB 1/√k prior); pooled DRC/Uganda ascertainment (non-centred, same as baseline); traveller volume; seed size; offspring dispersion φ.
  • Onset staging: infections are convolved with the incubation PMF to get the daily onset incidence O_t. The four observation streams (deaths, reports, exports, deaths-among-exports) are built by convolving O_t with onset-to-X delays once and shared across streams.
  • Observation likelihoods: Poisson for the small exports / export-deaths streams, NegBinomial(mean, k) for the large DRC streams, conditioned on cumulative totals to the cut-off. Any stream can be missing so the composer doubles as a prior-predictive generator. Genetic TMRCA enters as the same censored upper-tail soft lower bound the baseline uses.

Design rules

  • Every prior is a submodel injected via a keyword argument. The composer accepts rt, gi, incubation, onset_to_death, onset_to_report, onset_to_detect, cfr, dispersion, ascertainment, traveller, seed, offspring, all overridable.
  • All delays carry uncertainty, including the generation interval (Gamma mean and SD priors). No fixed delay constant in the model body.
  • Observation distributions are swappable through the dispersion submodel.
  • No inline using / import in src/state_space.jl; all imports stay on the module page.
  • Non-centred parameterisation for the weekly R_t walk and the pooled ascertainment offsets, mirroring the baseline.
  • The R_t walk uses a typed cumulative-sum allocation so Libtask's IR transformation handles it under PG; the abstract Vector{Real} allocation pattern that the renewal track uses is rejected by the PG transform.
  • Delay discretisation by hand-rolled trapezoidal bins with density-at-zero forced to zero, matching the renewal track's AD-safe trick (CensoredDistributions' analytical primary-censored Gamma CDF is not Mooncake-differentiable; double-interval-censored PMF approximation bias is documented in the proposal).

Inference and runtime

The natural sampler is Gibbs(:I => PG(N), :nuisances => NUTS()) — particle Gibbs over the integer latent path, NUTS over the continuous nuisances with Mooncake gradients.

Demonstrated:

  • Compiles end-to-end.
  • Prior-predictive draw gives finite, integer latent path and finite expected counts on all four streams.
  • Continuous-only Mooncake gradient (latent path conditioned) is finite and non-zero on every dimension.
  • A PG(50) smoke test runs to completion in ~80s on the n = 60 day grid.

Honest runtime estimate for a full fit: roughly an order of magnitude or more above the LNA route, because per-Gibbs-sweep cost scales as O(N · n) likelihood evaluations against a single evaluation per NUTS leapfrog on the LNA. For n ≈ 130 d, N ≈ 200 particles, expect ~5–25 h per chain for 1000 post-warmup samples versus ~15–25 min for the LNA route.

PF-vs-LNA verdict

What the exact discrete latent buys in principle: heavier-than-Gaussian early-phase tails, integer realism at small counts, and one extra mass term in the posterior for minimum outbreak age consistent with an early-cluster fluctuation.

What it does not buy on the available data: the four primary observation streams are all cumulative aggregates, which by CLT are nearly Gaussian regardless of the latent's discreteness. The earliest-onset bound and the TMRCA bound only depend on T, not on path discreteness, so the LNA captures both faithfully. The PG path-mixing rate is also weakest at the start of the path — exactly where the architecture is meant to be most informative — because the observational anchor is weakest there.

Recommendation: do not pursue this as the default architecture for the current data. Keep #101 (LNA) as the production route while streams remain aggregate. The state-space route becomes worthwhile when a future data drop adds time-resolved early-phase records (e.g. dated first-onset trail, or sequenced cases with sampling dates that anchor the early branching) — at which point the path's discreteness starts to matter.

Identifiability risks

  • Offspring φ, process noise, and surveillance dispersion k all add to observed-total spread and are weakly separated under cumulative-only data; priors carry the inference.
  • Seed size I_1 and R_1 near-degenerate, same as T / r in the baseline.
  • Particle ancestry collapse at the start of long latent paths under mild observation noise.

Files

  • src/state_space.jl, test/test_state_space.jl, scripts/state_space.jl, docs/src/proposals/state-space-particle.md.
  • docs/make.jl wires the proposal under a new "Redesign proposals" page group.

This was opened by a bot. Please ping @seabbs for any questions.

Tests for the candidate state-space architecture for issue #48: the
exact discrete-time NB-branching infection process with particle Gibbs
inference. They cover the delay discretisation, onset-staging
convolution, the NB renewal step mean, the weekly knot helpers, and
both a prior-predictive draw and a gradient check on the continuous
nuisance block (latent path conditioned). All red until the
implementation lands.

Also adds the proposal: docs/proposals/state-space-particle.md.
…ibbs

Implements the state-space candidate architecture for issue #48: the
exact discrete-time NB-overdispersed branching renewal as the latent
infection process, with particle Gibbs as the inference route for the
latent integer path and Mooncake-differentiable continuous nuisances
for NUTS inside Gibbs. Sibling to the LNA route in PR #101 and the
deterministic renewal route in PR #103.

- src/state_space.jl: building-block submodels (Rt weekly walk,
  generation interval, incubation, onset-to-death, onset-to-report,
  onset-to-detection, CFR, surveillance dispersion, pooled
  ascertainment, traveller volume, seed size, offspring dispersion),
  NB-branching renewal kernel, onset staging via shared convolution,
  the `state_space_joint` composer wiring four streams plus the
  genetic TMRCA soft bound. Submodels injected as keyword arguments
  so every prior including the generation interval carries
  uncertainty into the fit; no inline `using` / `import`. The Rt
  walk uses a typed cumulative-sum non-centred parameterisation so
  Libtask's IR transformation handles it under PG (the abstract
  `Vector{Real}` allocation pattern is rejected).
- test/test_state_space.jl: unit tests on the delay discretisation,
  onset-staging convolution, NB renewal mean, weekly knot helpers,
  prior-predictive draw, and a continuous-only Mooncake gradient
  check (latent path conditioned).
- scripts/state_space.jl: end-to-end demo. Prior-predictive draw,
  continuous-nuisance gradient check, and a particle-Gibbs smoke test
  (50 particles, 10 iterations, ~80s on the n = 60 day grid).
- docs/src/proposals/state-space-particle.md: full design, inference
  plan, runtime calculation, comparison vs the LNA route (PR #101),
  identifiability and honest verdict.
- docs/make.jl: adds a "Redesign proposals" group with the page wired
  in so it renders in the docs build.

PF-vs-LNA verdict: the architecture is mechanically operational
(compiles, draws from the prior, PG completes). On the available
data — four aggregate cumulative streams plus the genetic TMRCA bound
— the exact discrete latent buys little over the LNA at roughly an
order of magnitude higher cost, because cumulatives are nearly
Gaussian regardless of latent discreteness. The state-space route
becomes worthwhile when time-resolved early-phase data lands.
A one-knot grid would have indexed `knot_days[b + 1]` out of bounds.
The single-knot path returns the constant value early; the multi-knot
loop is unchanged. Regression test added (coderabbit review).
@github-actions
Copy link
Copy Markdown

📖 Documentation preview is ready!

View the docs for this PR at: http://epiforecasts.io/BVDOutbreakSize/previews/PR116/

This preview will be updated automatically when you push new commits.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant