Welcome to flepimop2¶
The next generation of the flexible epidemic modeling pipeline.
What is flepimop2?¶
flepimop2 is a Python package and command-line tool for running dynamic system simulations. It works with configuration files to define and execute analysis pipelines. Because it is also a library, you can write custom analyses that read and write data in a way that works seamlessly with the rest of the pipeline, and advanced users can develop shareable modules that plug directly into it.
At the core of flepimop2 is a modular design that separates three concerns:
- System: the mathematical model describing how the dynamical system evolves (e.g., disease spreads with a compartmental SIR model)
- Engine: the numerical solver that runs the model forward in time
- Backend: the output format for saving results
Each of these is defined independently and referenced in a single YAML configuration file. Because the full pipeline - model, solver, parameters, time grid, and post-processing - lives in one file, workflows are reproducible, version-controllable, and easy to share with collaborators.
Installation¶
flepimop2 is published on PyPI and can be installed with pip:
This makes the flepimop2 command available globally so you can use it from any project directory.
If you want to work from a local clone instead, see the installation guide for a source install and dependency setup.
Create a Project¶
Download quickstart-project.zip, unzip it wherever you want your project to live, then run:
The bundle already contains the standard project structure created by flepimop2 skeleton:
quickstart-project/
├── configs/
│ ├── built/
│ ├── config.yaml
│ └── EDITME.yaml
├── environment.yaml
├── justfile
├── model_input/
│ ├── data/
│ └── plugins/
├── model_output/
├── postprocessing/
│ └── SIR_plot.R
└── README.md
Every flepimop2 project needs at least three things to run: a configuration file, a system, and an engine. The configuration file (saved in configs) is a YAML file that specifies your model parameters, which system and engine to use, where to write outputs, and optionally what post-processing steps to run after a simulation. The system and engine are backends that implement the model dynamics and the numerical solver, respectively. In this quickstart, we will use Python scripts (saved in model_input/plugins) for both the system and the engine. The ZIP bundle above already places those files in the correct locations, includes the dependencies required for this page, and includes the post-processing script used later in the guide.
Configuration File
---
name: Postprocessing_Sample_Model
system:
- module: wrapper
state_change: flow
script: model_input/plugins/SIR.py
model_state:
parameter_names: [s0, i0, r0]
labels: [S, I, R]
engine:
- module: wrapper
state_change: flow
script: model_input/plugins/solve_ivp.py
simulate:
demo:
times: [0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100]
hires:
times: 0.0:0.1:100.0
backend:
- module: csv
process:
demo:
module: shell
command: Rscript postprocessing/SIR_plot.R
args:
- configs/config.yaml
- model_output/SIR_plot.png
hires:
module: shell
command: Rscript postprocessing/SIR_plot.R
args:
- configs/config.yaml
- model_output/SIR_plot_hires.png
parameter:
beta: 0.3
gamma: 0.1
s0: 999
i0: 1
r0: 0
SIR System
"""SIR model plugin for flepimop2 demo."""
import numpy as np
from flepimop2.typing import Float64NDArray
def stepper(
time: float, # noqa: ARG001
state: Float64NDArray,
beta: float,
gamma: float,
) -> Float64NDArray:
"""
Compute dY/dt for the SIR model.
Args:
time: The current time (not used in this model, but included for compatibility).
state: A numpy array containing the current values [S, I, R].
beta: The infection rate.
gamma: The recovery rate.
Returns:
A numpy array containing the derivatives [dS/dt, dI/dt, dR/dt].
"""
y_s, y_i, _ = np.asarray(state, dtype=float)
infection = (beta * y_s * y_i) / np.sum(state)
recovery = gamma * y_i
dydt = [-infection, infection - recovery, recovery]
return np.array(dydt, dtype=float)
scipy ODE Engine
"""ODE solver plugin that wraps `scipy.integrate.solve_ivp` for flepimop2 demo."""
from collections.abc import Mapping
from typing import Any
import numpy as np
from scipy.integrate import solve_ivp
from flepimop2.parameter.abc import ModelStateSpecification, ParameterValue
from flepimop2.system.abc import SystemProtocol
from flepimop2.typing import Float64NDArray
def runner(
fun: SystemProtocol,
times: Float64NDArray,
initial_state: dict[str, ParameterValue],
params: Mapping[str, ParameterValue] | None = None,
*,
model_state: ModelStateSpecification | None = None,
**solver_options: Any,
) -> Float64NDArray:
"""Solve an initial value problem using scipy.solve_ivp.
Args:
fun: A function that computes derivatives.
times: sequence of time points where we evaluate the solution. Must have
length >= 1.
initial_state: Structured initial-state entries.
params: Optional dict of keyword parameters forwarded to fun.
model_state: Specification describing how to order the initial-state
entries into a numeric state array.
**solver_options: Additional keyword options forwarded to
scipy.integrate.solve_ivp.
Returns:
FloatArray: Array with time and state values evaluated at `times`.
Each row is [t, y...].
Raises:
ValueError: If `times` is not a 1D sequence of time points with length >= 1, or
if the first time point is negative.
"""
if not (times.ndim == 1 and times.size >= 1):
msg = "times must be a 1D sequence of time points"
raise ValueError(msg)
if model_state is None:
msg = "model_state must be provided to assemble the initial condition."
raise ValueError(msg)
times.sort()
t0, tf = 0.0, times[-1]
if times[0] < t0:
msg = f"times[0] must be >= 0; got times[0]={times[0]}"
raise ValueError(msg)
y0 = np.stack([
np.asarray(initial_state[name].value) for name in model_state.parameter_names
]).astype(np.float64)
args = tuple(val.item() for val in params.values()) if params is not None else None
result = solve_ivp(
fun,
(t0, tf),
y0,
t_eval=times,
args=args, # type: ignore[arg-type]
**solver_options,
)
return np.transpose(np.vstack((result.t, result.y)))
Next, set up the project's virtual environment. The bundled environment.yaml already includes the dependencies required for this guide.
Environment YAML file
Then create the environment:
As flepimop2 will only work when it is run in an appropriate environment, you will need to run conda activate ./venv each time you open a new terminal session before using flepimop2.
Simulate an Outbreak¶
Activate the project environment and run the simulation:
Results are saved automatically to the model_output directory as a CSV file.
Adding Post-Processing¶
flepimop2 supports post-processing steps that run after a simulation - useful for generating plots, rendering notebooks, or producing summary tables. Post-processing steps are defined in the process block of your configuration file and can invoke R scripts, Python scripts, or Jupyter notebooks.
The same quickstart-project.zip bundle already includes the post-processing config, script, and dependencies needed for this section.
The bundled project includes this post-processing file layout:
quickstart-project/
├── configs/
│ ├── built/
│ ├── config.yaml
│ └── EDITME.yaml
├── environment.yaml
├── justfile
├── model_input/
│ ├── data/
│ └── plugins/
│ ├── SIR.py
│ └── solve_ivp.py
├── model_output/
├── postprocessing/
│ └── SIR_plot.R
└── README.md
Take a look at configs/config.yaml. It defines two simulation targets - demo and hires - that share the same model parameters but use different time resolutions. A separate post-processing pipeline is defined for each target under the process block. flepimop2 defaults to the first defined target, but you can select a specific one with --target:
# Run the default (demo) target
flepimop2 simulate configs/config.yaml
# Run the high-resolution target instead
flepimop2 simulate --target hires configs/config.yaml
To run post-processing after a simulation, call process with the same config:
This runs the post-processing steps defined for the demo target, producing a plot. You can call post-processing for a specific plot with the same --target argument as you use for simulations.