A flepimop2 external provider that runs epiworld agent-based models from a flepimop2 configuration, through the epiworldpy Python bindings.
Supports epiworld’s ModelSEIRCONN. Other models plug into the same
seam; see Adding a model.
- Python 3.11+ and uv
epiworldpy ships prebuilt wheels, so there is no compiler, R, or other toolchain to install.
uv add flepimop2-epiworldsystem:
- module: epiworld
model: seirconn
state_change: state
engine:
- module: epiworld
backend:
- module: csv
parameter:
s0: 9700 # initial compartment counts
e0: 120
i0: 60
r0: 120
contact_rate: 6.0
transmission_rate: 0.08
incubation_days: 3.0
recovery_rate: 0.2
seed: 1912
simulate:
demo:
times: '0:1:150'flepimop2 simulate config.yaml writes the usual (time, S, E, I, R)
CSV.
flepimop2 has no replicate concept, and does not need one: simulate
already runs one simulation per scenario tuple and writes each to its
own file. Sweeping seed over a grid gives independent epiworld
replicates, which a process: step can aggregate:
scenarios:
- module: grid
parameters:
seed: [1, 2, 3, 4, 5]
simulate:
replicates:
times: '0:1:150'
scenario: defaultThe folder
examples/seirconn-replicates contains
a complete project that produces a median and 95% interval plot. You can
look at the configuration file
here. The following code
executes the simulation and processing steps:
cd examples/seirconn-replicates
rm -f model_output/*.csv
uv run --group example flepimop2 simulate config.yaml
uv run --group example flepimop2 process config.yamlwrote figures/seirconn_ci.png from 20 replicates
We can look at the generated image:
epiworld is an agent-based model whose C++ core owns its own simulation
loop and tracks per-agent exposure clocks. It cannot be expressed as
flepimop2’s f(time, state) -> state stepper, so the work is split:
EpiworldSystemis declarative. It names the compartments, the parameters holding their initial counts, and the model’s rate parameters, so flepimop2 can resolve them from theparameter:section.EpiworldEngineignores the stepper and runs the whole simulation in-process through epiworldpy, translating the resulting compartment history back into flepimop2’s array contract.
Running in-process has no per-run startup cost. The flip side is that a fault in epiworld’s C++ takes the flepimop2 process down with it, and a run cannot be interrupted by a timeout.
epiworld seeds a model from a population size, a prevalence
proportion, and an initial_states() proportion vector, not from
absolute counts. Recovering exact counts takes care, because epiworld
stores prevalence as a C float and truncates in two places. Passing
the bare ratio lands one agent short in roughly a third of
configurations. This provider adds a half unit before dividing so
truncation is exact, and the engine hard-fails if realized day-0 counts
ever disagree with the config. The test suite fuzzes this against real
epiworld runs.
- Add an
EpiworldModelSpectoMODEL_SPECSinsrc/flepimop2_epiworld/_models.pyand widenEpiworldModelKey. Itsconstructoris the class name inepiworldpy.epimodels. - Confirm the model’s
initial_states()semantics — they differ per family — and extend the seeding mapping if it is not SEIR-shaped.
A test builds every registered model through epiworldpy and compares its states with the registry, so a mistyped entry fails in CI rather than at run time.
uv sync --group dev
just dev # ruff, mypy, tests
just example # run the replicate example end to endThe integration test installs this package into a throwaway environment
via flepimop2.testing, which needs flepimop2 itself to be a source
install. The lockfile resolves flepimop2 from git, so point it at a
local checkout to run that test — otherwise it skips itself:
uv pip install -e ../flepimop2
just integrationA dev container is published to
ghcr.io/epiforesite/flepimop2-epiworld.
GPL-3.0-or-later, as required for a flepimop2 provider. See LICENSE.
