Portfolio hardening: analytical ODE validation, tests + CI, config, docs - #1
Merged
Conversation
…d lockdown Introduce config.SimConfig to hold the tunable model parameters that were scattered as module-level constants in run_experiments.py; defaults reproduce the report Table 1 values so the published scenarios are unchanged. Define the zone radii (8, 16) once as CENTRE_RADIUS/MIDDLE_RADIUS in model.py and reuse them in make_density_map, vaccinate and the experiment zone masks. Add a guard in run_seiqr so a lockdown map without a window fails loudly instead of relying on short-circuit evaluation in the step loop. model.py public signatures are unchanged (make_density_map gains optional radius kwargs with defaults), so the dashboard is unaffected. Verified the regenerated curve data is bit-identical to the committed results.json. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Add ode_reference.py: the classical well-mixed SEIQR ODE (scipy solve_ivp) plus the tools to validate the CA against it. Per-step probabilities map to continuous rates by rho = -ln(1 - p) (the p ~= p shortcut is 28% wrong at p_infect = 0.50); the saturating exposure rule maps to a force of infection -ln(1 - p(1 - (1 - i)**8)) that linearises to beta = 8p; the single-draw I->Q / I->R competition maps to a combined-hazard split. A global-coupling CA variant drives exposure by the global infected fraction and reproduces the exact discrete recursion to ~2% of peak and the ODE peak to ~2%, while the standard local CA departs (a ~3.5x lower, ~50-step-later peak) as the genuine spatial effect. Comparisons use the interior 2304-cell population. Wire scenario 7 into run_experiments.py (results['ode_validation']) and add a guarded Validation figure and metrics to the dashboard Performance tab. Trim now-unused state-constant imports in the touched files. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Add tests/ covering the model core (density-map zone counts, S->E neighbour gating, E->I/I->Q thresholds, Q non-transmission, boundary preservation, the half-open lockdown window and its guard, vaccination dose/targeting/copy semantics, seed reproducibility, and statistical equivalence with the original seiqr.py) and the ODE reference (rate mapping, combined-hazard split, saturating force of infection, R0, compartment conservation, and the well-mixed-limit agreement of the CA against both the exact discrete recursion and the ODE). Add pyproject.toml with the ruff config (ignore E741 for the standard SEIQR state names; leave the frozen seiqr.py's legacy imports unlinted) and the pytest path config, and a GitHub Actions workflow running ruff and pytest on push and pull request. Split the remaining semicolon statements in run_experiments.py so the tree is clean under default ruff rules; curve data is unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Add make_demo_gif.py and the rendered assets/demo.gif preview (GitHub cannot embed the live Plotly dashboard) at the top of the README. Add a Validation section stating the mean-field-limit argument and the measured agreement, a CI badge, updated project structure and run instructions, and flip the roadmap to mark analytical validation and tests + CI as done. Normalise the README to the project's house style (British spelling, no em dashes). Give run_all a docstring noting the seeded reproducibility. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Add an MIT licence for the individual extension, with a note that the preserved seiqr.py originates from the UCL group project and its copyright remains with the original team. Gitignore the original report PDF so it is not published to the public repository (it contains other team members' work); team credit stays in the README. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Streamlit deprecated use_container_width; switch all plotly_chart and button calls to width='stretch'. No behaviour change; the dashboard loads cleanly with no deprecation warnings. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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.
Note
Drafted with an LLM-based AI tool (Claude Code / Claude Fable 5).
Turns the coursework-origin SEIQR cellular automaton into a more defensible portfolio piece, in five focused commits. The deployed dashboard and the group-vs-individual attribution are preserved, and
seiqr.py(the group original) is left untouched.What's in it
1. Analytical ODE validation (the headline). New
ode_reference.pyadds the classical well-mixed SEIQR ODE (scipy.solve_ivp) and validates the CA against it, so the "checked against the analytical model" claim is real rather than asserted. The CA's local rules reduce to the ODE in the mean-field limit: a global-coupling (well-mixed) CA ensemble reproduces the exact discrete recursion to about 2% of peak (RMSE) and the ODE peak height to about 2%, while the standard spatial CA departs (a roughly 3.5x lower, about 50-step-later peak), which is the genuine effect of spatial structure. Per-step probabilities map to rates by-ln(1-p)(notp, which is 28% wrong atp_infect=0.50); the saturating exposure rule gives a force of infection that linearises tobeta = 8*p_expose(R0 about 15 atp_expose=0.30). Wired intorun_experiments.py(scenario 7) and the dashboard Performance tab.2. Tests + CI. A pytest suite (23 tests) over the model core (transitions, boundary preservation, density-map zone counts, the half-open lockdown window and its guard, vaccination, seed reproducibility, and statistical equivalence with
seiqr.py) and the ODE reference (rate mapping, combined-hazard split, saturating force of infection, conservation, and the well-mixed-limit agreement). A GitHub Actions workflow runs ruff and pytest on push and PR.3. Config refactor. A
SimConfigdataclass replaces about 20 scattered module constants; the zone radii (8, 16) are deduped into named constants inmodel.py; the fragile lockdown-window handling now fails loudly instead of relying on short-circuit evaluation. Behaviour-preserving: the regenerated curve data is bit-identical to the committedresults.json(verified), somodel.pypublic signatures are unchanged and the dashboard is unaffected.4. Docs + demo GIF. A reproducible
make_demo_gif.pyrendersassets/demo.gifand embeds it at the top of the README (the live dashboard is Plotly, which GitHub cannot embed). The README gains a Validation section, a CI badge, and updated structure and run instructions.5. Licence + hygiene. An MIT licence for the individual extension, with a note that
seiqr.py's copyright stays with the group. The original report PDF is gitignored so it is not published to the public repo.Decisions for you to confirm
LICENSEnotes thatseiqr.pyoriginates from the group project and its copyright stays with the team. If you would rather licence the whole repo, use a different licence, or clear it with the team, adjust as you see fit.Verification
ruff check .clean;pytest23 passed.python run_experiments.pyregeneratesresults.json; curve data is bit-identical to the previous version (only the speedup timings and thegenerated_attimestamp differ).AppTest), including the new validation figure.🤖 Generated with Claude Code