Skip to main content

flepiMoP

🧠0% 🤖100%

This page is for people who use flepiMoP, usually for the Scenario Modeling Hubs: a YAML configuration of compartments, places with their populations and commuting, modifiers for NPIs and vaccination, outcomes chained from the flows, and inference that fits each place’s modifiers to its data, ending in a hub submission of quantiles by scenario, location, target, and week. It shows a flepiMoP configuration next to the same model in Starsim, how their results agree, how long each takes, and what works and what does not yet, including what fails.

The same model in both

flepiMoP’s two-population tutorial: an SEIR in a large and a small province, whose residents spend part of each day in the other:

name: sample_2pop
setup_name: minimal
start_date: 2020-02-01
end_date: 2020-08-31
nslots: 1

subpop_setup:
  geodata: model_input/geodata_sample_2pop.csv
  mobility: model_input/mobility_sample_2pop.csv

initial_conditions:
  method: SetInitialConditions
  initial_conditions_file: model_input/ic_2pop.csv
  allow_missing_subpops: TRUE
  allow_missing_compartments: TRUE

compartments:
  infection_stage: ["S", "E", "I", "R"]

seir:
  integration:
    method: rk4
    dt: 1
  parameters:
    sigma:
      value: 1 / 4
    gamma:
      value: 1 / 5
    Ro:
      value: 2.5
  transitions:
    - source: ["S"]
      destination: ["E"]
      rate: ["Ro * gamma"]
      proportional_to: [["S"],["I"]]
      proportion_exponent: ["1","1"]
    - source: ["E"]
      destination: ["I"]
      rate: ["sigma"]
      proportional_to: ["E"]
      proportion_exponent: ["1"]
    - source: ["I"]
      destination: ["R"]
      rate: ["gamma"]
      proportional_to: ["I"]
      proportion_exponent: ["1"]

with three CSV files: the populations (9,000 and 1,000), the commuters (100 from the large province to the small, 20 the other way), and 5 exposed people in the large province at the start. In Starsim, the places are a stratification with commuting. flepiMoP’s travelers are infected where they go but infect nobody there, which is kind='visit':

import numpy as np
import starsim as ss

pop     = dict(large_province=9000, small_province=1000)
commute = [[0, 100/9000], [20/1000, 0]] # The share of each province's residents who travel to the other
gamma   = 1/5

S, E, I, R = ss.states('S E I R')
seir = ss.Disease(
    infection = ss.Transition(S, E, rate=2.5*gamma * I),
    onset     = ss.Transition(E, I, rate=1/4),
    recovery  = ss.Transition(I, R, rate=gamma),
    init_n    = dict(E=ss.by(patch=[5, 0])),
)
away = [[0.5*share for share in row] for row in commute] # Travelers are away for half the day
seir.stratify(patch=ss.levels(pop, mobility=ss.geo.Mobility(matrix=away, kind='visit')))
sim = ss.Sim(seir, total_pop=sum(pop.values()), start='2020-02-01', stop='2020-08-31', verbose=0)
sim.run()
sim.plot()

Or the configuration can be imported as it is, with the CSV files next to it:

sim = ss.import_from('config_sample_2pop.yml', format='flepimop')
sim.run()

How the results agree

Model Agreement
The two-population tutorial (above) Agree: every compartment in both provinces within 0.05 people
The vaccine scenarios tutorial (vaccination, three scenarios of stacked modifiers, Ro and the share of cases detected drawn for each run, outcomes chained with delays) Agree exactly, by province: run with the values flepiMoP drew, the mean, 5%, and 95% of all 28 series in each province agree within 1e-9 of their largest value in all three scenarios. With Starsim’s own draws, they agree in distribution
A configuration with 3,000 places and 29,958 commuting links, imported with ss.import_from() Agree: incident cases, hospitalizations, deaths, and current hospitalizations within 0.007% of each place’s peak on every day

Timings

Lines are lines of model code (not counting blank lines, comments, imports, or plotting, and counting a line over 100 characters twice), against the lines of flepiMoP’s YAML; times are for one run, the first in a new process and a rerun of the same model. Both were measured as described on the comparison page. flepiMoP’s times were recorded on the same machine, as a whole command (flepimop simulate), most of which is compiling its integrator with Numba, which it does on every run.

Model Lines: flepiMoP Lines: Starsim One run: flepiMoP One run: Starsim
The two-population tutorial 42, plus 10 lines of CSV 16 first 8.6 s, rerun 8.8 s as a command first 0.018 s, rerun 0.00045 s Pass
The vaccine scenarios tutorial, 30 runs (3 scenarios of 10) 141, plus 10 lines of CSV 33 first 23 s, rerun 23 s as a command first 0.84 s, rerun 0.55 s Pass
The 3,000-place configuration, one SEIR run with its outcomes 34 s (58 s the first time, with compiling), plus 6 to 9 s for the outcomes 5.0 s, plus 1.7 s to read the configuration

The last row was measured on 17 October 2026, on a machine shared with other jobs.

What works and what does not yet

These are from Starsim’s switching demo for flepiMoP users (tests/switching/test_flepimop.py in this repo), which checks each task on a configuration of three counties, and from the review it was built from. All three of the demo’s checks pass today.

Task Today
Import a configuration and run it as an ODE by place Works, with a list of anything not translated. A parameter drawn from a distribution (distribution: truncnorm) is imported with that distribution as its prior
Fit one value per place: Ro by county, fitted to weekly hospitalizations by county with a Poisson likelihood Works: sim.new(pars=dict(Ro=ss.par(ss.by(subpop=[2.2, 2.2, 2.2]), bounds=[1, 4]))) and fit(); the fitted values are within 5% of the true ones. Data by place are a long table with a subpop column
A Poisson likelihood on data that are not whole numbers (an ODE’s output) Works: the likelihood is finite
Runs that each draw their parameters from their priors Works: ss.parallel(sim, n_runs=, priors=True)
A hub submission (hubverse columns): 23 quantiles or the runs themselves, by scenario, location, target, and week; incident, cumulative, and peak targets Works: ss.converters.to_hub(msim, targets=..., origin_date=..., by='subpop', every='week'), below; output_type='sample' gives the runs
Scenarios from the configuration Works: sim.imported.scenarios gives each scenario’s modifiers, as ss.modify interventions
Fits by NUTS with gradients through the run Works: on flepiMoP’s California and New York benchmark (four parameters, a value for each state in two periods, fitted to six weekly series), NUTS took 70 s on 4 cores against 283 s for flepiMoP’s emcee run on 10 cores, with estimates within 1.5% of the truth (measured on 17 October 2026, with each state’s values written into the rate)
Batch jobs on a cluster, R post-processing Not planned: Starsim runs in one process on many cores

Runs with Ro drawn from a truncated normal, written as a hub submission (a long table with the columns the hubs ask for):

Ro = ss.par(2.5, prior=ss.truncnorm(mean=2.5, std=0.2, low=2.0, high=3.0))
S, E, I, R = ss.states('S E I R')
seir = ss.Disease(
    infection = ss.Transition(S, E, rate=Ro*gamma * I),
    onset     = ss.Transition(E, I, rate=1/4),
    recovery  = ss.Transition(I, R, rate=gamma),
    init_n    = dict(E=ss.by(patch=[5, 0])),
)
seir.stratify(patch=ss.levels(pop, mobility=ss.geo.Mobility(matrix=away, kind='visit')))
sim = ss.Sim(seir, total_pop=sum(pop.values()), start='2020-02-01', stop='2020-08-31', verbose=0)

msim = ss.parallel(sim, n_runs=20, priors=True) # Each run draws its own Ro
hub = ss.converters.to_hub(msim, targets=dict(infection='inc inf'), by='patch', every='week')
hub.head()
origin_date scenario_id location target horizon output_type output_type_id value
0 2020-02-01 large_province inc inf 1 quantile 0.01 8.339158
1 2020-02-01 large_province inc inf 2 quantile 0.01 18.628803
2 2020-02-01 large_province inc inf 3 quantile 0.01 37.786574
3 2020-02-01 large_province inc inf 4 quantile 0.01 76.008290
4 2020-02-01 large_province inc inf 5 quantile 0.01 150.568296

Bringing your model in

ss.import_from('config.yml', format='flepimop') reads a flepiMoP configuration. The compartments become states (with several dimensions, one state for each combination, named as flepiMoP names them, e.g. S_unvaccinated); the places become a stratification subpop with commuting (ss.geo.Mobility(kind='visit')); each transition becomes a flow; the modifiers of a scenario become ss.modify interventions; and the outcomes become ss.outcomes. A configuration with age, vaccination, and variant as dimensions comes in as a flat product of states, which runs the same as flepiMoP but is not how it would be written in Starsim, where each dimension is a stratification. What is not translated is listed (sim.imported.incomplete), and sim.imported.prompt is a prompt for an AI assistant with Starsim’s convert-model skill to finish the job.

See Import/export for importing in general: checking an import, keeping it as code, and finishing one that is incomplete.

Further reading