Using AI to Calibrate 3 Base Measles Models to Similar Biennial Results - laser-base/laser-measles GitHub Wiki
One Prompt, Three Models, One Calibration: LASER Measles via JENNER MCP
The Actual Story
This entire session -- three calibrated biennial measles models, a comparative analysis, and the figures below -- was produced from a single starting prompt to Claude Code:
"Using just the JENNER MCP services for laser-measles and laser-core to learn about LASER, create biennial measles models using all 3 base model types, and then use the documented calibration approach to calibrate the parameters for all 3 to produce similar but not identical results. Visualize and document the convergences and divergences."
No source code was read. No documentation was opened. The jenner-measles-mcp and jenner-mcp MCP services answered every API question interactively as the code was being written. The result is a principled comparison of laser-measles's three model types -- Biweekly (BWK), Compartmental (CMP), and Agent-Based (ABM) -- all tuned to the same endemic regime, with their structural differences quantified.
What the Three Models Are
laser-measles ships three distinct model types with very different performance and fidelity trade-offs:
- Biweekly (BWK): A fast SIR stepping in 14-day ticks. Best for parameter sweeps over large spaces.
- Compartmental (CMP): A daily SEIR. Best for deterministic outbreak timing and growth-rate estimation.
- Agent-Based (ABM): A stochastic daily SEIR where individual agents can go extinct. Best when stochastic fadeout and critical community size matter.
What We Were Trying to Show
Measles in large, partially-vaccinated populations produces biennial cycles: a large epidemic every two years, with a subdued off-year. This is driven by the two-year accumulation of susceptible births after a major epidemic depletes the immune pool. Getting all three model types to reproduce this behaviour -- at the same effective reproduction number -- is the prerequisite for any honest comparison.
Demographic Parameters: What Calibration Does Not Find
The natural inter-epidemic period is set by demography and pathogen biology, not by transmission parameters. Specifically:
T = 1 / (R0 x CBR)
where T is the period in years and CBR is births per person per year. Calibration optimises beta (per-contact transmission rate) and importation_rate (seeding from outside), but cannot change the underlying cycle length -- it can only find parameters that work within a given demographic context.
We target a true 2-year period, which requires R0 x CBR = 0.5 per year. Our parameter set:
- R0 = 15: consistent with measured measles R0 in high-burden settings
- CBR = 33 /1k/yr: plausible for sub-Saharan Africa (Kenya ~27, Nigeria ~38 /1k/yr)
- CDR = 15 /1k/yr: balanced vital dynamics (natural growth rate ~1.8%/yr)
This gives T = 1 / (15 x 0.033) = 2.02 years -- directly biennial by construction. Once demographics are set, calibration finds the specific beta and importation rate that hit our quantitative targets within this regime.
The Calibration
We calibrated each model with Optuna, jointly optimising beta and importation rate toward three targets:
- Mean S/N = 0.067 (= 1/R0 at endemic equilibrium)
- Biennial ratio >= 6x (on-year peak at least 6x the off-year peak)
- Epidemic frequency: 7-11 major outbreaks in 17 post-burn years (ensuring true ~2-year periodicity, not the "one big outbreak every 5 years" degenerate solution the optimizer discovered when only the ratio was penalised)
That third constraint -- epidemic frequency -- turned out to be essential. Without it, the optimizer found technically high biennial ratios by driving importation so low that epidemics occurred every 5-6 years rather than every 2. Adding a frequency penalty steered all three models into the correct regime.
Calibrated parameters:
| Model | beta | importation | val. biennial ratio |
|---|---|---|---|
| BWK | 1.049 | 0.045 /1k/yr | 10.3x |
| CMP | 1.730 | 0.152 /1k/yr | 5.5x |
| ABM | 1.805 | 0.259 /1k/yr | 15.7x |
Convergences and Divergences
All three models hit S/N = 0.067 (target: 0.067) within +/- 0.003 -- confirming they're genuinely in the same endemic regime. The biennial ratios and importation rates tell a more structured story.
A structural split in importation: BWK needed 0.045/1k/yr, CMP needed 3.4x more (0.152/1k/yr), and ABM needed the most (0.259/1k/yr). This reflects real model-structural differences: the daily SEIR (CMP) continuously pushes individuals through E and I, maintaining a low background infection level that partially pre-seeds the next epidemic. The SIR biweekly model (BWK) lacks the latent compartment, so between-epidemic troughs go lower, and less importation is needed to trigger the next cycle. The ABM goes furthest -- agents die or recover to zero in the trough, so higher importation is needed to restart from true zero.
Biennial ratios: CMP 5.5x, BWK 10.3x, ABM 15.7x. The ordering isn't a calibration failure -- it's each model doing something structurally different with the off-year. CMP maintains a continuous low-level infectious compartment (it cannot go to zero). BWK snaps to near-zero between epidemics. ABM agents literally reach zero infectious individuals in the trough, then importation-seeded recolonisation triggers a fresh epidemic. The ABM's higher ratio is genuine extinction-recolonisation dynamics -- invisible in the other two models.
This is the core comparative value: use the fast BWK for parameter sweeps, CMP for precise outbreak timing, ABM when the off-year extinction probability is itself a quantity of interest.
Appendix: Calibration Lessons
A few things that only emerged from running code:
- The biennial ratio metric alone is insufficient. A high ratio is achievable both by true 2-year cycling and by rare large outbreaks every 5+ years. Adding an epidemic-frequency constraint (6-10 outbreaks in 17 years) was necessary to distinguish these regimes.
- VitalDynamicsProcess must be first in the ABM component list, or numba crashes at runtime.
- ABMModel accepts a raw polars DataFrame directly -- no
BaseScenariowrapper needed. - StateTracker returns a StateArray that requires an explicit
np.array(..., dtype=float)cast before arithmetic. - The default importation rate (~1/1k/yr) actively suppresses biennial cycling; the calibrated range is 0.045--0.26/1k/yr depending on model type (ABM needs more because it starts each cycle from true zero).