Standard results output
The ResultsWriter component writes a canonical JSON summary of a simulation to disk at end of run. Use it as the contract that downstream tooling — validators, plotting scripts, model-comparison reports — reads from, instead of parsing simulation stdout or pulling fields off trackers ad-hoc.
Why use it?
- Stable schema across model variants. ABM, compartmental, and biweekly models all produce the same top-level keys, so the same downstream code works for any of them.
- One component instead of many. No need to remember which tracker exposes peak infectious vs attack rate vs final state — all the common quantities land in one file with predictable names.
- Decouples stdout from automation. Progress bars, warnings, and debug prints stay in stdout for humans; numeric results stay in
results.jsonfor code. - Opt-in. A calibration loop that only wants final stats (no per-trial JSON files) simply omits
ResultsWriterfrommodel.components.
Basic usage — add ResultsWriter as a component
Adding ResultsWriter to model.components causes the JSON dump to happen automatically at end of run, alongside any other end-of-run work declared by other components.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | |
For a custom path:
1 2 3 4 | |
Skipping the writer for calibration
A calibration loop that runs the model thousands of times typically doesn't want a results.json per trial. Just omit ResultsWriter from the components list — the model itself writes nothing. Read the metrics you care about off the tracker (or any other component) in-process instead.
1 2 3 4 5 | |
Requirements
- A
StateTrackercomponent must already be attached before you add or instantiateResultsWriter(for example viaadd_component(ResultsWriter)or when building the components list). If no tracker is present,ResultsWriter.__init__raises a clearRuntimeErrorduring component wiring, beforemodel.run(). - For per-group breakdowns (attack rate per community, peak per patch, final S/E/I/R per patch), the tracker's
aggregation_levelmust be>= 0. The default (-1) sums over all patches and produces global aggregates only. "I"must be inmodel.params.states— required for peak-infectious metrics andfinal_state_per_group["I"]in the written summary.
Output schema
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | |
Field reference
| Key | Type | Notes |
|---|---|---|
model_type |
string | Class name of the model ("ABMModel", "CompartmentalModel", "BiweeklyModel"). |
num_ticks |
int | Total simulation length in ticks (days for ABM/compartmental, 14-day units for biweekly). |
num_groups |
int | Number of tracker groups represented in the per-group arrays. Equals len(group_ids); 1 for global-only output. |
group_ids |
array of strings | Tracker group identifiers in the same order as the per-group arrays. ["all_patches"] for global-only output. May be leaf scenario IDs at full aggregation depth, or higher-level keys like "cluster_1" when the tracker rolls up. |
group_aggregation_level |
int | The tracker's aggregation_level: -1 means global, 0+ means grouped at that hierarchy depth. Use this to know whether the _per_group arrays are leaf-level (true per-patch) or aggregated above. |
states |
array of strings | The disease states tracked, in order — ["S", "E", "I", "R"] for SEIR; ["S", "I", "R"] for SIR. |
summary.peak_infectious_global |
int | Maximum total infectious count summed across all groups over the run. |
summary.peak_tick |
int | Tick index at which peak_infectious_global occurred. Convert to calendar time using the model's tick→day mapping (1 day for ABM/compartmental, 14 days for biweekly). |
summary.attack_rate_global |
float or null | Fraction of initial susceptibles globally that ever left the S compartment, i.e. (S[0] - S[-1]).sum() / S[0].sum(), clamped to [0, 1]. null if S isn't in states. Defined this way so the value stays well-defined under spatial migration (where an R[-1] / initial_pop formulation would exceed 1.0) and is robust to per-patch state-counter underflow (laser-measles #117). |
summary.final_state_global |
object | Final count of each state summed across all groups. Keys are the state names present in states. Always emitted (works with both per-group and global-only trackers). |
summary.attack_rate_per_group |
array of floats or null | Per-group version of attack_rate_global (same formula, applied per tracker group). Each entry is in [0, 1]. null when only global tracking is available. |
summary.peak_infectious_per_group |
array of ints or null | Per-group peak; null when only global tracking is available. |
summary.final_state_per_group |
object or null | Final count of each state per group. Keys are the state names present in states. null when only global tracking is available. |
Reading the results back
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | |
Comparing models
Because all three model variants emit the same schema, a cross-model comparison is just three reads:
1 2 3 | |