synthetic#

Synthetic scenario builders for laser-measles models.

Overview#

Every laser-measles model (ABM, Biweekly, Compartmental) requires a scenario: a Polars DataFrame with one row per geographic patch. This module provides ready-made scenario builders for testing and development. For production use, supply your own DataFrame following the schema below.

Correct import paths#

Import the module:

from laser.measles.scenarios import synthetic
scenario = synthetic.single_patch_scenario(population=50_000, mcv1_coverage=0.8)

Or import individual functions directly:

from laser.measles.scenarios.synthetic import single_patch_scenario, two_patch_scenario
from laser.measles.scenarios import single_patch_scenario   # also re-exported here

NEVER write import laser_measles or from laser_measles import .... The package is always laser.measles (dot, not underscore).

Scenario DataFrame schema#

All scenario DataFrames must contain at least these five columns:

Column

Dtype

Description

id

Utf8/str

Unique string identifier for the patch. Examples: "patch_0", "cluster_1:node_3"

pop

Int64

Total population of the patch (integer).

lat

Float64

Latitude of the patch centroid (degrees).

lon

Float64

Longitude of the patch centroid (degrees).

mcv1

Float64

Routine MCV1 vaccination coverage, 0.0-1.0.

Critical schema notes:

  • id must be a string (pl.Utf8). Integer IDs will fail validation.

  • pop must be named pop, not population.

  • mcv1 must be present even when vaccination is irrelevant to your analysis (use 0.0 as a placeholder).

  • Extra columns are allowed and are ignored by the model.

Building a scenario by hand (no helper function needed)#

import polars as pl

scenario = pl.DataFrame({
    "id":   ["patch_0", "patch_1", "patch_2"],
    "pop":  [200_000,   150_000,   80_000],
    "lat":  [40.0,      38.5,      36.0],
    "lon":  [4.0,       6.0,       8.0],
    "mcv1": [0.85,      0.70,      0.60],
})

Available helper functions#

single_patch_scenario(population, mcv1_coverage)

One patch. Useful for the simplest possible “hello world” run:

from laser.measles.scenarios.synthetic import single_patch_scenario
scenario = single_patch_scenario(population=10_000, mcv1_coverage=0.0)
two_patch_scenario(population, mcv1_coverage)

Two patches; the second patch has half the population of the first:

from laser.measles.scenarios.synthetic import two_patch_scenario
scenario = two_patch_scenario(population=100_000, mcv1_coverage=0.8)
two_cluster_scenario(seed, n_nodes_per_cluster, …)

100 patches arranged in two geographic clusters, with randomised populations and MCV1 coverage in a configurable range. Good for spatial spread studies:

from laser.measles.scenarios.synthetic import two_cluster_scenario
scenario = two_cluster_scenario(seed=42, n_nodes_per_cluster=50)
satellites_scenario(core_population, satellite_population, n_towns, …)

One large central city surrounded by smaller satellite towns — a classic city-and-hinterland structure:

from laser.measles.scenarios.synthetic import satellites_scenario
scenario = satellites_scenario(core_population=500_000, n_towns=30, mcv1=0.5)

Connecting a scenario to a model#

Pass the DataFrame directly as the first argument to any model constructor:

from laser.measles.abm import ABMModel, ABMParams
from laser.measles.scenarios.synthetic import single_patch_scenario

scenario = single_patch_scenario(population=50_000, mcv1_coverage=0.85)
params   = ABMParams(num_ticks=365, seed=42, start_time="2000-01")
model    = ABMModel(scenario, params)

The same pattern applies to BiweeklyModel and CompartmentalModel.

Functions

satellites_scenario

Create a cluster of nodes with a single large node in the center (core) surrounded by smaller nodes (satellites).

single_patch_scenario

Generate a synthetic scenario with a single patch.

two_cluster_scenario

Generate a synthetic scenario with two clusters of nodes.

two_patch_scenario

Generate a synthetic scenario with two patches where one is half the size of the other.