Added a computation and plotting library for epidemiological outcomes post-simulation.
Computation functions — all accept optional subpop_name, age_group, and risk_group arguments (passing None aggregates over that dimension):
daily_hospital_admissions: daily ISH→HR + ISH→HD transition flows, aggregated to daily totals.daily_new_infections: daily S→E transition flows.cumulative_hospitalizations,cumulative_deaths: season-total scalars.attack_rate: cumulative infections / initial susceptible population.vaccine_preventable_events(baseline, counterfactual, metric_fn): difference in a scalar metric between two scenarios.summarize_outcomes(values, credible_interval): mean, median, and central CI across replicates.
Plotting functions:
plot_compartment_history: time series of selected compartments, with optional age/risk filtering and multi-scenario overlay vialinestyle/label_suffix.plot_epi_metrics: M and MV over time, averaged across subpopulations.plot_daily_new_infections: daily S→E curve, single model.plot_daily_hospital_admissions: accepts a single model or a{scenario: model}/{scenario: [list of models]}dict; multi-replicate input produces a median line + shaded 95 % CI ribbon.plot_attack_rate_by_age: bar chart of attack rate per age group.plot_scenario_comparison: bar chart (single-run) or box plot (multi-replicate) of any scalar metric across scenarios.
All functions that consume transition-variable histories require those variables to be listed in SimulationSettings.transition_variables_to_save before the simulation runs; a clear ValueError is raised if history is missing.
Exported from flu_core/__init__.py via from .flu_outcomes import *.
Fixed FluSubpopModel._check_vaccination_input(): exceeds_first_date now reads from df_vaccine.index[...] instead of df_vaccine['date'].values[...], consistent with the pre-indexed schedule DataFrames introduced in the 2026-02-19 update.
Updated beta_baseline from 0.031 → 0.042 for the 2024–2025 Austin instance.
Added a "higher_beta" example scenario (using "params" key with per-subpop beta_baseline overrides) to the module docstring to illustrate parameter-only scenario definitions alongside schedule-based ones.
scenario_and_outcomes_demo.py: end-to-end demo combiningScenarioRunner(multi-replicate, SQL-backed) withflu_outcomes(in-memory, single-replicate re-runs for plotting). Illustrates the two-step pattern: runScenarioRunnerfor statistics, then re-run each scenario once withtransition_variables_to_saveset to get histories forflu_outcomes.flu_sensitivity.pyandvaccination_scenario_analysis.py: additional analysis scripts (sensitivity sweeps and vaccination scenario comparisons).
replace_schedule on SubpopModel and MetapopModel (base_components.py)
- Added
SubpopModel.replace_schedule(schedule_name, new_df): swaps a named schedule'stimeseries_dfand re-runspostprocess_data_input()so the processed index is consistent with the new data. Copiesnew_dfbefore assigning to avoid mutating the caller's DataFrame. - Added
MetapopModel.replace_schedule(schedule_name, new_df, subpop_name=None): fans out to all subpopulations whensubpop_nameis omitted, or targets a single subpopulation when specified. - Added
MetapopModel.modify_random_seed(seed): re-seeds all subpopulation RNGs from a single integer usingnumpy.random.SeedSequence.spawn, giving each subpop a distinct but reproducible child seed.
ScenarioRunner (scenario_runner.py, new file)
- Runs a baseline
MetapopModel(orSubpopModel) and one or more named counterfactual scenarios as paired multi-replicate experiments, writing all results to a single SQLite database with ascenario_namecolumn. - Scenario definitions support three optional keys:
"schedules"(apply same DataFrame to all subpops),"subpop_schedules"(per-subpop DataFrame overrides), and"params"(per-subpop parameter updates viaupdated_dataclass). - Uses a save/restore pattern (
_save_overrideable_state/_restore_overrideable_state) instead ofdeepcopyto avoid infinite recursion triggered bySubpopModel.__getattr__during pickling. seedsparameter enables paired replicates:seeds[i]is used to re-seed the model before replicateiin every scenario, isolating the effect of each intervention.- SQL writes are batched: all rows for a replicate are accumulated in a Python list and flushed with a single
executemany+conn.commit()per replicate, instead of oneexecutemanycall per (subpop × state variable × timepoint). - Exported from
clt_toolkit/__init__.pyasScenarioRunnerandScenarioRunnerError.
seeds parameter in Experiment.run_static_inputs (experiments.py)
- Added optional
seedslist torun_static_inputsandsimulate_reps_and_save_results: before replicatei,model.modify_random_seed(seeds[i])is called, giving reproducible and cross-scenario-paired replicates. - Added module-level docstring to
experiments.pywith typical usage and database schema.
FluSubpopModel.reset_simulation() override (flu_core/flu_components.py)
- Overrides
SubpopModel.reset_simulation()to recomputeVaxInducedImmunity.adjust_initial_value()from the current vaccine schedule before restoring compartment values. UsesMV.original_init_val(the unmodified JSON baseline) to prevent adjustments from compounding across multiple resets. - Ensures
MV.init_val(andcurrent_val) are consistent with whateverdaily_vaccinesschedule is loaded at the time of reset, soreplace_schedule + reset_simulationproduces the same trajectory as a freshly constructed model using the same schedule.
- Pre-indexed schedule DataFrames by date (or day-of-week) in all 4 schedule classes in
flu_components.pyto eliminate O(n) boolean scans duringprepare_daily_state. Changes:DailyVaccines,MobilityModifier,AbsoluteHumidity,FluContactMatrix: added/updatedpostprocess_data_input()to callset_index('date')(orset_index('day_of_week')for the day-of-week mobility variant) at the end of setup, after all other DataFrame processing is complete.- All four
update_current_val()methods updated to use.loc[current_date]instead ofdf[df["date"] == current_date]. FluContactMatrix.update_current_val():except IndexErrorchanged toexcept KeyErrorto match.locsemantics.MV.adjust_initial_value(): mask updated to usevaccines_df.indexinstead ofvaccines_df['date']sincedateis now the DataFrame index.- Profiling result:
prepare_daily_statecumtime dropped from 0.256s (33%) to 0.082s (13%); total simulation time 0.78s → 0.627s (−20%).
- Updated the travel mixing exposure equations in
flu_travel_functions.pyto properly account for the proportion of individuals staying home in each location. - In
compute_local_to_local_exposure():proportion_staying_homeis now applied to both sides of the contact matrix multiplication — scaling both the susceptible pool and the infectious pool present in the local location. Previously it was only applied to the susceptible side. - In
compute_outside_visitors_exposure(): added the susceptible scalingproportion_staying_homeof the local (destination) location. - In
compute_residents_traveling_exposure(): the infectious pool at the destination is now correctly computed by aggregating over all subpopulations present at the destination (residents staying home + travelers from other locations) via a vectorizedeinsum. Previously only the local-to-local infectious individuals at the destination were considered, undercounting the infectious pool.
- Added vaccine immunity reset functionality to model seasonal vaccine immunity patterns. A new parameter
vax_immunity_reset_date_mm_ddis added toFluSubpopParamsinflu_data_structures.py. When set (format: "MM_DD", e.g., "08_01" for August 1st), the vaccine-induced immunity (MV) resets to zero on this date each year to represent the start of a new vaccine season. - Added
start_real_dateparameter toFluSubpopParamsinflu_data_structures.pyto track the real-world date corresponding to the simulation start, enabling date-based reset functionality. - Modified the
VaxInducedImmunityclass influ_components.pyto:- Adjust the initial vaccine-induced immunity value at simulation start by accounting only for vaccines administered after the most recent reset date (before simulation start), with appropriate waning applied. This ensures vaccines from previous seasons are not counted. The initial value variable
init_valsaved in the object's instance needs to updated so that even when we the simulation is reset the adjustment is taken into account. - Check each day whether the current date matches the reset date and reset MV to zero if it does.
- Adjust the initial vaccine-induced immunity value at simulation start by accounting only for vaccines administered after the most recent reset date (before simulation start), with appropriate waning applied. This ensures vaccines from previous seasons are not counted. The initial value variable
- Added
prepare_daily_state()method override inFluSubpopModelto check for vaccine immunity resets at the beginning of each simulated day. - Added
check_and_apply_MV_reset()function influ_torch_det_components.pyto support vaccine immunity resets in deterministic simulations. - Enhanced metapopulation model in
flu_components.pyto properly handle non-numerical parameters (strings and datetime objects) across subpopulations, ensuring consistency. - The reset functionality works in conjunction with
vax_protection_delay_daysto properly account for the delay between vaccination and effective protection. - Updated example parameter files (common_subpop_params.json, test files) and notebooks to demonstrate the new reset functionality.
- Added a new parameter
vax_protection_delay_daysto model the delay between vaccine administration and protection effectiveness. The parameter is added toFluSubpopParamsinflu_data_structures.pyand used in theDailyVaccinesclass influ_components.py. The vaccine timeseries is shifted forward by the specified number of days, with zero-valued entries backfilled at the beginning to preserve the original start date.
- Modified the variable mobility_modifier to be a schedule that varies through time instead of being a static variable. Input can either be a time series (like vaccines) or depend on the day of the week only.
- In function check_rate_input() in file
flu_components.pywe now let transition rate values be equal to zero and only issue a warning if that is the case. Values still need to be positive (>=0).
Added input checks for subpop and metapop models. We check that humidity, vaccination, contact matrix, and initial compartment values are non-negative. All values at zero are possible but wouldn't make sense. For transition rates we need strictly positive values. For vaccination rates we check whether cumulative vaccination rates in each age-risk group are not exceeding 100% in any 365-day period. This only issues a warning. The mobility matrix (or travel_proportions) should have rows that sum to 1: this ensures people either travel to another subpopulation or stay in their home location.
A new parameter called use_deterministic_softplus is added to the simulation settings. If the object oriented model is run with deterministic transitions this can be used to prevent softplus values instead of zeros in compartments, which leads to strange behaviors when epidemics occur in populations without any exposure.
Small fixes were made to the travel model equations in the file flu_travel_functions.py.
Updated website notation and made code updates in a lot of places.
Technical notes
- After making changes, please make sure ALL tests in
tests/folder pass, and add new tests for new code. - Due to the highly complicated influenza model, there are a lot of input combinations and formats -- errors might arise due to incorrect inputs (e.g. dimensions, missing commas, etc...) -- if there is an error in running the model, the inputs should be checked first. Additionally, more work should be spent on writing input validators and error messages.
Tests to add
- Experiments
- Make sure aggregating over subpopulation/age/risk is correct (e.g. in
get_state_var_df). - Make sure all the ways to create different CSV files lead to consistent results!
- Make sure aggregating over subpopulation/age/risk is correct (e.g. in
- Accept-reject sampling
- Reproducibility: running the algorithm twice (with the same RNG each time) should give the same result.
- Make sure the sampling updates are applied correctly (e.g. to the correct subpopulation(s) and with the correct dimensions).
Features to add
- Would be nice to make the "checker" in
FluMetapopModel__init__method more robust -- can check dimensions, check for nonnegativity, etc...