Skip to content

Convert course code into MFIIDD package (draft for #22)#39

Merged
sbfnk merged 25 commits into
mainfrom
spike/package-mfiidd
Jun 29, 2026
Merged

Convert course code into MFIIDD package (draft for #22)#39
sbfnk merged 25 commits into
mainfrom
spike/package-mfiidd

Conversation

@sbfnk-bot

@sbfnk-bot sbfnk-bot commented Apr 22, 2026

Copy link
Copy Markdown
Collaborator

Draft spike for #22.

Turns the project into a Julia package called MFIIDD. Splits src/seit4l_particle_filter.jl into focused files. Sessions and chain-generation scripts now do using MFIIDD instead of path-juggling include(...). Adds the quarto-ext/include-code-files extension so source-display blocks pull from the actual src files (which incidentally fixes a bug where the previous #| eval: false #| file: callouts in mcmc.qmd, mcmc_diagnostics.qmd, pmcmc.qmd, and particle_filters.qmd had been silently rendering empty for months).

What this addresses:

  • Native-code caching across sessions via a small PrecompileTools workload in MFIIDD.jl — the main argument from the Make code a package #22 thread
  • No more include(joinpath(@__DIR__, "..", "src", ...)) path juggling in sessions and scripts
  • Single source of truth — the docstring text rendered into a session is the same string as the actual src/*.jl file, no copy-paste drift
  • GitHub source links next to every src/...jl mention in the session prose

What this leaves alone:

  • The pedagogical pattern is unchanged: sessions still show source inline as the student reads, just sourced from src/ rather than pasted in
  • No registry release or package publishing — MFIIDD is consumed in-tree via Pkg.activate(".")
  • [compat] julia = "1.12" matches the CI pin

Notes for review:

  • Reviewer (eager-epi-student agent simulating an MFIIDD course participant from the MTM background) flagged the formal docstrings as slightly more "manual-like" than the previous one-line teaching comments — addressed with a conversational gloss before the SSM-interface block in particle_filters.qmd
  • Same reviewer asked for the using MFIIDD line to be framed as the course package — done
  • Stale #= ... =# file-header comments (some referencing the deleted seit4l_particle_filter.jl or the obsolete include pattern) were leaking into the rendered output; stripped
  • juliaup override set 1.12.6 in the project directory pins the Julia version locally; [compat] julia enforces it at instantiate time

Please review with a focus on whether you'd actually want this merged, or whether the student-shortcut concern still outweighs the package benefits in your view.

Summary by CodeRabbit

  • New Features

    • Added a reusable Julia package (MFIIDD) providing epidemiological simulators and particle-filter APIs.
    • Introduced a Quarto extension to include external code files in documentation with optional line ranges and snippets.
  • Improvements

    • Enhanced numerical stability and autodiff behaviour (ForwardDiff-safe parameter handling, typed zeros, guarded ODE solves, clearer inference result usage).
    • Updated course notebooks/scripts to load models via MFIIDD for consistency.
  • Documentation

    • Added Documenter-based API site generation for MFIIDD and extended Quarto configuration.

@sbfnk-bot sbfnk-bot changed the title Spike: convert course code into MFIIDD package Convert course code into MFIIDD package (draft for #22) Apr 22, 2026
sbfnk-bot and others added 5 commits April 22, 2026 22:56
Co-authored-by: sbfnk <sebastian.funk@lshtm.ac.uk>
…lay the real source

Co-authored-by: sbfnk <sebastian.funk@lshtm.ac.uk>
…GitHub

Co-authored-by: sbfnk <sebastian.funk@lshtm.ac.uk>
…, gloss the SSM interface block

Co-authored-by: sbfnk <sebastian.funk@lshtm.ac.uk>
Co-authored-by: sbfnk <sebastian.funk@lshtm.ac.uk>
@sbfnk sbfnk force-pushed the spike/package-mfiidd branch from 1fc41a2 to cf7e429 Compare April 22, 2026 20:57
sbfnk-bot and others added 10 commits April 27, 2026 11:53
Co-authored-by: sbfnk <sebastian.funk@lshtm.ac.uk>
Co-authored-by: sbfnk <sebastian.funk@lshtm.ac.uk>
The 2.x release changed the __initial_guess_on_mesh signature and is
incompatible with BoundaryValueDiffEqFIRK 1.13.

Co-authored-by: sbfnk <sebastian.funk@lshtm.ac.uk>
NUTS uses ForwardDiff Dual numbers for the parameter vector. Without
promoting u0, the solver tried to write Duals into a Float64 vector and
crashed with a Float64 conversion error.

Co-authored-by: sbfnk <sebastian.funk@lshtm.ac.uk>
Turing's externalsampler init probe wraps parameters in ForwardDiff
Duals to check the gradient is finite, which fails inside
GeneralisedFilters because the bootstrap filter is non-differentiable.
Apply ForwardDiff.value on each prior draw before building the SSM, so
the init probe and the actual MH steps both see Float64 parameters.

Co-authored-by: sbfnk <sebastian.funk@lshtm.ac.uk>
NUTS occasionally probes parameter combinations where Tsit5 only saves
the initial timepoint, leaving inc_daily one element long and tripping
a BoundsError in the lambdas comprehension. Check the retcode and
saved-points length, addlogprob! -Inf to discard the draw if either
fails.

Co-authored-by: sbfnk <sebastian.funk@lshtm.ac.uk>
Turing 0.44 wraps rand(model) results in a VarNamedTuple; the inner
NamedTuple lives at .data, so direct field access on the wrapper now
errors.

Co-authored-by: sbfnk <sebastian.funk@lshtm.ac.uk>
Spike's wider compat bounds let the resolver pick newer Lux/OptimizationBase
combinations whose chunked ForwardDiff path errors with PartialsFn(::Float64)
on ComponentArray. Match main's working pin until upstream sorts the
interaction out.

Co-authored-by: sbfnk <sebastian.funk@lshtm.ac.uk>
Without the promotion, the ODE solve drops ForwardDiff Duals, the loss
returns Float64, and ForwardDiff's chunked gradient extraction trips a
Lux/ComponentArrays edge case (PartialsFn called on a Float64). This
matches the fix used for the deterministic SEIT4L model. Drops the
earlier Lux/OptimizationBase pins now that the underlying issue is
addressed.

Co-authored-by: sbfnk <sebastian.funk@lshtm.ac.uk>
q_meanfield_gaussian/q_fullrank_gaussian now act as families passed to
vi, rather than being called on the model up front. vi returns a
VIResult; the optimised distribution is res.q and the training stats
res.info.

Co-authored-by: sbfnk <sebastian.funk@lshtm.ac.uk>
@sbfnk sbfnk marked this pull request as ready for review May 4, 2026 12:06
@coderabbitai

coderabbitai Bot commented May 4, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 4e91a671-7dd1-475b-8ccb-fa7a88964f4d

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • ✅ Review completed - (🔄 Check again to review again)

Walkthrough

Refactors the codebase into a Julia package module MFIIDD (new src/MFIIDD.jl and split SEIT4L components), updates dependency metadata and compatibility bounds, adds a Quarto include-code-files extension, and converts scripts and Quarto sessions to import MFIIDD while applying autodiff/type fixes.

Changes

Module refactor & runtime/autodiff changes

Layer / File(s) Summary
Package Configuration
Project.toml
Adds dependencies BoundaryValueDiffEqCore, PrecompileTools, Random and introduces a new [compat] section (pins versions, julia = "1.12").
Module API & Exports / Implementation split
src/MFIIDD.jl, src/seit4l_gillespie.jl, src/seit4l_bootstrap_filter.jl, src/seit4l_ssm_interface.jl, src/seit4l_runner.jl
Adds module MFIIDD that includes and exports simulation, Gillespie, particle-filter and SEIT4L SSM API; introduces standalone gillespie_step, gillespie_step_seit4l!, particle_filter_seit4l, SSM types/methods, and run_particle_filter.
Removal / Cleanup
src/seit4l_particle_filter.jl
Removes monolithic seit4l_particle_filter.jl (its functionality has been split into the new files).
Precompilation workload
src/MFIIDD.jl
Adds @compile_workload block calling representative simulate/filter routines to drive precompilation.
Wiring: scripts
scripts/generate_pmcmc_chains.jl, scripts/generate_pmcmc_seit4l.jl, scripts/generate_pmcmc_seitl.jl
Replaces local include(...) of particle-filter files with using MFIIDD.
Wiring: sessions / notebooks
sessions/mcmc.qmd, sessions/mcmc_diagnostics.qmd, sessions/particle_filters.qmd
Switches notebooks to using MFIIDD; replaces in-document SSM/filter definitions with include("../src/seit4l_ssm_interface.jl") and include("../src/seit4l_runner.jl") where applicable.
Autodiff / type-safety fixes
sessions/pmcmc.qmd, sessions/universal_differential_equations.qmd
Applies ForwardDiff.value() to strip Duals before particle-filter calls; promotes initial states to parameter element type for ODE solves, guards on SciMLBase.successful_retcode(sol), and uses typed zeros for incidence to avoid type-mismatch under autodiff.
Minor session updates
sessions/seitl.qmd, sessions/variational_inference.qmd
Adjusts parameter extraction (.data) in prior-predictive sampling and updates VI examples to pass distribution constructors directly to vi and read results from VIResult fields.

Quarto include-code-files extension

Layer / File(s) Summary
Extension manifest
_extensions/quarto-ext/include-code-files/_extension.yml
Adds a Quarto extension manifest registering include-code-files.lua with metadata and quarto-required >= 1.2.
Filter implementation
_extensions/quarto-ext/include-code-files/include-code-files.lua
Adds a Lua filter that replaces code blocks with referenced file contents when include attribute present; supports startLine/endLine (and hyphenated aliases), snippet extraction via language-appropriate comment markers, optional dedent, error reporting to stderr, and returns pandoc.CodeBlock with modified content.
Quarto project wiring
_quarto.yml
Enables the quarto-ext/include-code-files filter in the project filters configuration.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title clearly and concisely describes the main change: converting course code into the MFIIDD package, with a draft reference to issue #22.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch spike/package-mfiidd

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@_extensions/quarto-ext/include-code-files/include-code-files.lua`:
- Around line 7-9: dedent currently strips all spaces within the first n
characters which mutates tokens; change it so it only removes up to n leading
spaces (i.e., compute how many leading spaces the line actually has, take min(n,
leadingCount), and remove that many characters from the start) and then return
the reconstructed line. Update the dedent(line, n) implementation to detect
leading whitespace (using line:match or equivalent), subtract only that many
leading spaces (not internal spaces in the first n chars), and return the
trimmed line.

In `@src/seit4l_bootstrap_filter.jl`:
- Around line 19-25: The particle_filter_seit4l function lacks a guard against
non-positive n_particles which leads to runtime errors later (e.g.,
maximum(log_w)); add an upfront check in particle_filter_seit4l that verifies
n_particles is an integer > 0 and throw a clear ArgumentError (or return/handle
as appropriate) if the check fails, so downstream code that assumes a non-empty
particle set (referencing n_particles, particles, and maximum(log_w)) never runs
with zero or negative particles.

In `@src/seit4l_gillespie.jl`:
- Around line 44-47: The simulation loop in gillespie_step_seit4l! and its
wrapper ignores the dt parameter by using a hardcoded horizon (t < 1.0), so
non-daily intervals are never honored; change the loop condition(s) from t < 1.0
to t < dt, ensure any wrapper functions that call gillespie_step_seit4l! pass
their dt argument through (or add a default dt and forward it), and update all
occurrences referenced in the diff (the loop in gillespie_step_seit4l! and the
wrapper calls around lines 53 and 83–85) so the exposed dt actually controls how
far the while loop advances. Ensure tests or callers that relied on the 1.0
behavior are adjusted accordingly.

In `@src/seit4l_runner.jl`:
- Around line 22-23: The constructors SEIT4LDynamics and PoissonObservation
expect concrete Float64 fields but are being passed θ that may contain
ForwardDiff.Duals; before creating dynamics and observation, convert θ to plain
Float64 values (e.g. use ForwardDiff.value or ForwardDiff.value. on θ or
reconstruct a Float64-typed parameter NamedTuple) and then pass that normalized
θ to SEIT4LDynamics(...) and PoissonObservation(...), mirroring the pattern used
in pmcmc.qmd so Duals do not flow into the particle filter.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c1384894-3c7a-4d4e-b0f9-1bb3893cf9fd

📥 Commits

Reviewing files that changed from the base of the PR and between 8e07466 and 93057ca.

📒 Files selected for processing (24)
  • Manifest.toml
  • Project.toml
  • _extensions/quarto-ext/include-code-files/_extension.yml
  • _extensions/quarto-ext/include-code-files/include-code-files.lua
  • _quarto.yml
  • scripts/generate_pmcmc_chains.jl
  • scripts/generate_pmcmc_seit4l.jl
  • scripts/generate_pmcmc_seitl.jl
  • sessions/mcmc.qmd
  • sessions/mcmc_diagnostics.qmd
  • sessions/particle_filters.qmd
  • sessions/pmcmc.qmd
  • sessions/seitl.qmd
  • sessions/universal_differential_equations.qmd
  • sessions/variational_inference.qmd
  • src/MFIIDD.jl
  • src/seit4l_bootstrap_filter.jl
  • src/seit4l_gillespie.jl
  • src/seit4l_particle_filter.jl
  • src/seit4l_runner.jl
  • src/seit4l_ssm_interface.jl
  • src/seitl_model.jl
  • src/seitl_particle_filter.jl
  • src/sir_model.jl
💤 Files with no reviewable changes (4)
  • src/seitl_model.jl
  • src/seitl_particle_filter.jl
  • src/sir_model.jl
  • src/seit4l_particle_filter.jl

Comment thread _extensions/quarto-ext/include-code-files/include-code-files.lua
Comment thread src/seit4l_bootstrap_filter.jl Outdated
Comment thread src/seit4l_gillespie.jl Outdated
Comment thread src/seit4l_runner.jl Outdated
@sbfnk

sbfnk commented May 4, 2026

Copy link
Copy Markdown
Owner

@coderabbitai review

@coderabbitai

coderabbitai Bot commented May 4, 2026

Copy link
Copy Markdown
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
sessions/pmcmc.qmd (1)

176-195: ⚡ Quick win

Use run_particle_filter here instead of re-implementing it.

This block now duplicates src/seit4l_runner.jl, so fixes to the packaged helper can drift away from the PMMH session. Given the new package structure, I'd keep the explanatory callout above for pedagogy but delegate the actual likelihood evaluation to run_particle_filter(...).

Suggested simplification
-    # Create parameter dictionary; extract underlying values so ForwardDiff
-    # Duals (used only by Turing's gradient-based init probe) don't flow into
-    # the particle filter, which is non-differentiable anyway.
-    θ = Dict(:R_0 => ForwardDiff.value(R_0),
-             :D_lat => ForwardDiff.value(D_lat),
-             :D_inf => ForwardDiff.value(D_inf),
-             :α => ForwardDiff.value(α),
-             :D_imm => ForwardDiff.value(D_imm),
-             :ρ => ForwardDiff.value(ρ))
-
-    # Build state-space model
-    # The SSMProblems interface expects a plain vector, not a Dict
-    # Order: [S, E, I, T1, T2, T3, T4, L]
-    init_state = [279.0, 0.0, 2.0, 3.0, 0.0, 0.0, 0.0, 0.0]
-    ssm = StateSpaceModel(SEIT4LInitial(init_state),
-                          SEIT4LDynamics(θ),
-                          PoissonObservation(θ[:ρ]))
-
-    # Run particle filter to estimate log-likelihood
-    _, log_lik = GeneralisedFilters.filter(Random.default_rng(), ssm, BF(n_particles), obs)
+    θ = Dict(:R_0 => R_0,
+             :D_lat => D_lat,
+             :D_inf => D_inf,
+             :α => α,
+             :D_imm => D_imm,
+             :ρ => ρ)
+
+    init_state = [279.0, 0.0, 2.0, 3.0, 0.0, 0.0, 0.0, 0.0]
+    log_lik = run_particle_filter(θ, obs, n_particles; init_state=init_state)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@sessions/pmcmc.qmd` around lines 176 - 195, The code duplicates the
particle-filter call; replace the manual construction and
GeneralisedFilters.filter call with the packaged helper run_particle_filter so
the session uses the canonical implementation: build θ (using ForwardDiff.value)
and the same init_state/SSM (SEIT4LInitial, SEIT4LDynamics, PoissonObservation)
as before but call run_particle_filter(ssm, obs; n_particles = n_particles, rng
= Random.default_rng()) (or the exact run_particle_filter signature in
src/seit4l_runner.jl) and capture the returned log-likelihood instead of calling
GeneralisedFilters.filter/BF directly; ensure you pass the same n_particles and
rng options to preserve behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/seit4l_runner.jl`:
- Around line 19-29: run_particle_filter currently passes init_state directly
into SEIT4LInitial which expects Vector{Float64}; coerce and validate init_state
inside run_particle_filter before creating initial: convert to a Vector{Float64}
(e.g. use Float64.(collect(init_state)) or similar) and check its length matches
the expected size (8) and elements are finite, then pass the coerced vector to
SEIT4LInitial; update the function where SEIT4LInitial is constructed (in
run_particle_filter) and ensure the default init_state remains compatible by
normalizing it the same way.

---

Nitpick comments:
In `@sessions/pmcmc.qmd`:
- Around line 176-195: The code duplicates the particle-filter call; replace the
manual construction and GeneralisedFilters.filter call with the packaged helper
run_particle_filter so the session uses the canonical implementation: build θ
(using ForwardDiff.value) and the same init_state/SSM (SEIT4LInitial,
SEIT4LDynamics, PoissonObservation) as before but call run_particle_filter(ssm,
obs; n_particles = n_particles, rng = Random.default_rng()) (or the exact
run_particle_filter signature in src/seit4l_runner.jl) and capture the returned
log-likelihood instead of calling GeneralisedFilters.filter/BF directly; ensure
you pass the same n_particles and rng options to preserve behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: b4f97a09-36ba-4cca-918f-4d8e175b65a5

📥 Commits

Reviewing files that changed from the base of the PR and between 93057ca and 08b9cec.

📒 Files selected for processing (11)
  • _extensions/quarto-ext/include-code-files/include-code-files.lua
  • sessions/mcmc.qmd
  • sessions/mcmc_diagnostics.qmd
  • sessions/particle_filters.qmd
  • sessions/pmcmc.qmd
  • sessions/seitl.qmd
  • sessions/universal_differential_equations.qmd
  • sessions/variational_inference.qmd
  • src/seit4l_bootstrap_filter.jl
  • src/seit4l_gillespie.jl
  • src/seit4l_runner.jl
✅ Files skipped from review due to trivial changes (3)
  • sessions/universal_differential_equations.qmd
  • _extensions/quarto-ext/include-code-files/include-code-files.lua
  • sessions/particle_filters.qmd
🚧 Files skipped from review as they are similar to previous changes (5)
  • src/seit4l_gillespie.jl
  • sessions/mcmc_diagnostics.qmd
  • src/seit4l_bootstrap_filter.jl
  • sessions/variational_inference.qmd
  • sessions/seitl.qmd

Comment thread src/seit4l_runner.jl
Co-authored-by: sbfnk <sebastian.funk@lshtm.ac.uk>
@sbfnk

sbfnk commented May 5, 2026

Copy link
Copy Markdown
Owner

@seabbs can't tag you for review (yet) but would be good if you could have a look.

Comment thread src/MFIIDD.jl Outdated
Comment thread src/seit4l_gillespie.jl
Comment thread sessions/pmcmc.qmd
Co-authored-by: sbfnk <sebastian.funk@lshtm.ac.uk>
@sbfnk-bot

Copy link
Copy Markdown
Collaborator Author

Wait-for-review summary:

This watcher is stopping; auto-merge not queued because the latest review state is COMMENTED, not APPROVED.

sbfnk-bot and others added 3 commits June 29, 2026 09:46
docs/Project.toml + docs/make.jl + docs/src/{index,api}.md generate an
@autodocs API reference for the MFIIDD module under /api. Run locally
with:

  julia --project=docs -e 'using Pkg; Pkg.develop(path="."); Pkg.instantiate(); include("docs/make.jl")'

Deployment to gh-pages is not wired up in CI yet — that belongs in
.github/workflows and is outside the scope of this PR.

Co-authored-by: sbfnk <sebastian.funk@lshtm.ac.uk>
Sessions previously inlined files via {.julia include="../src/X.jl"},
which hardcodes a path relative to the qmd. source_for reads the file
from the live pkgdir(MFIIDD) instead, and also accepts a function to
print just its methods via CodeTracking.

Co-authored-by: sbfnk <sebastian.funk@lshtm.ac.uk>
Co-authored-by: sbfnk <sebastian.funk@lshtm.ac.uk>
@sbfnk sbfnk merged commit 402083f into main Jun 29, 2026
2 checks passed
@sbfnk sbfnk deleted the spike/package-mfiidd branch June 29, 2026 09:14
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.

3 participants