Worked examples of simulating a SARS-CoV-2 outbreak in a real city and watching it through clinical and wastewater surveillance. This is a Modjadji project built on covasim4wastewater, the Andersen lab's wastewater extension of Covasim.
Each example is a script in src/simulation_examples/<place>/ that writes its figures next to itself:
python src/simulation_examples/sandiego/sandiego_simulation.py
python src/simulation_examples/lusakacity/lusakacity_simulation.pyBoth follow the same steps: simulate an outbreak on a map, track viral shedding by sewershed, introduce variants with real genomes, simulate amplicon sequencing reads, compare clinical and wastewater surveillance, and draw the transmission phylogeny.
- San Diego (
sandiego/): San Diego County, with clinical cases by ZIP code and wastewater by treatment-plant sewershed. Written as a step-by-step tutorial.make_sewershed_map.pyandclip_worldpop.pybuild the sewershed and population-raster inputs. - Lusaka (
lusakacity/): Lusaka, Zambia, with the same workflow using administrative boundaries, sewershed catchments and a population raster.
To model another place, supply a boundary file, a sewershed file and a density raster to cv.Sim (boundary_shapefile, sewershed_file, density_raster).
Install covasim4wastewater following the instructions in its repository. The scripts need that build of covasim, plus geopandas, rasterio, matplotlib, pillow, baltic and bygul.
Covasim is an agent-based COVID-19 model. covasim4wastewater adds:
- spatial populations placed using a density raster and region boundaries
- sewershed assignment, so shedding can be summed per treatment plant
- per-agent viral shedding over time
- sequence evolution, with variants seeded from real genomes and transmissibility derived from their mutations
- clinical and wastewater samplers, and amplicon read simulation with Bygul
- an infection log for building transmission phylogenies
- Key Parameters:
pop_size = 20e3,pop_infected = 20,pop_type = random,location = None (seattle) - Core Functions:
make_people,assign_sewersheds,make_synthpop,make_randpop - Process: Generates agents based on the selected population type after verifying user-provided data.
- Synthpop (
pop_type = 'synthpop'): - Powered by the synthpops GitHub repository.
- Note: Limited location support and currently unmaintained.
- Output: Outputs a structured synthetic population with demographics (age, sex, household size) mapped onto realistic interaction networks across households, schools, workplaces, long-term care facilities, and community spaces using real census data.
-
Location Effects: Uses age and household size data for the specified location (defaults to Seattle if unspecified).
-
Contact Layers:
-
Household =
h -
Schools =
s -
Workplace =
w -
Random contact =
c -
(Stored globally in
layers_defaults) -
Person Attributes: Each individual contains:
uid,age,sex, andcontacts. -
Prognoses & Spatial Data: Default prognoses assigned by age bins alongside regions, coordinates, and sewershed assignments.
- Core Functions:
make_random_contacts,make_hybrid_contacts,make_rank_based_contacts
- Assigns contacts around a mean value
$N$ using a Poisson distribution. - Switches to a negative binomial distribution if a dispersion factor is active (default is
None) to simulate superspreading events.
- Sets exact contact rules per layer via
reset_layer_pars(h,s,w,c), with school age defined as[6, 22]and work age as[22, 65]. - Structure:
- Microstructured household clustering (cluster size drawn from a Poisson distribution).
- Random connections made across the broader population.
- Random contacts assigned specifically within school and work age brackets (household contacts are structured; school/work networks are random).
- Requires coordinates; converts longitude/latitude to kilometers via
lonlat_to_kmor generates them viaassign_density_based_coordsusing a user-provided density raster file. - Calculates rank-based contacts with a default mean of
20(frompars['contacts']). - Dispersion parameter defaults to
None.
- Layers can be set to dynamic, but the default for both random and hybrid models is
0(static contacts that do not change over simulation time).
- Parameters:
pop_scale = 1,scaled_pop = None,rescale = TRUE,rescale_threshold = 0.05,rescale_factor = 1.02 - Core Function:
rescale(self) - Dependencies:
self.people.false('naive'),self['pop_size'] - Mechanism:
pop_scaledefines how many real individuals each simulated agent represents, determining the totalscaled_popsize.- When
rescaleis enabled, the model monitors non-susceptible agents. Once infections/vaccinations hit therescale_threshold, it applies therescale_factorto prune agents and increase scale, optimizing memory and execution speed.
- Parameters:
beta = 0.016,n_import = 0(random Poisson-drawn cases may still occur),beta_dist = 'neg_binomial',par1 = 1.0,par2 = 0.45,step = 0.01,viral_dist = frac_time = 0.3,load_ratio = 2,high_cap = 4,asymp_factor = 1 - Core Concept: Transmission probability from an infectious person to a susceptible person depends on network layer connectivity, viral load, susceptible age, and other modifiers.
- Dynamically calculates relative transmissibility at timestep
$t$ based on infection duration (start date to recovery/death endpoint). - Splits timelines into a high-load early phase and a low-load late phase, bounded by a maximum cap (
high_cap) to prevent unrealistic peak lengths. - Automatically sets viral load to
0for uninfected, recovered, dead, or unaffected individuals.
- Initial Draw:
rel_transis drawn frombeta_distributionand multiplied by individual age-bin odds ratios (default is1). - Sequence Tracker Override: If active,
rel_transis multiplied by the calculated viral fitness value. - Base Beta (
beta): Fixed default of0.016(beta per symptomatic contact). - Variant Adjustments: Recalculated per variant using:
- Layer-Specific Betas: Individual beta values are assigned per network layer (household, school, workplace, community).
Calculates final relative transmission and susceptibility values using the following core multipliers:
- Asymptomatic Factor:
- Isolation Factor:
- Quarantine Factor:
- Final Transmissibility:
- Final Susceptibility:
-
Alive (
n\_alive):$\text{Total Population} - \text{Total Deaths}$ -
Naive (
n\_naive):$\text{Total Population} - \text{Deaths} - \text{Recovered} - \text{Exposed}$ -
Susceptible (
n\_susceptible):$\text{Alive} - \text{Exposed} - (\text{count_recov} \times \text{Cumulative Recoveries})$ -
Pre-infectious (
n\_preinfectious):$\text{Exposed} - \text{Currently Infectious}$ -
Removed (
n\_removed):$(\text{count_recov} \times \text{Cumulative Recoveries}) + \text{Cumulative Deaths}$ -
Prevalence:
$\frac{\text{Currently Exposed}}{\text{Alive}}$ -
Incidence:
$\frac{\text{New Infections}}{\text{Susceptible}}$ -
Fraction Vaccinated:
$\frac{\text{Vaccinated}}{\text{Alive}}$ -
By-Variant Metrics: Variant-specific incidence and prevalence calculated via Einstein summation (
$\text{einsum}$ ).
- Doubling Time: Calculated over a rolling window using exponential approximation:
- Effective Reproduction Number (
$R_{\text{eff}}$ ): - Daily Method:
(Includes kernel smoothing for noise reduction).
- Transmission Log Method (
infectious/outcome): Tracks direct person-to-person infections across the transmission tree, averaged over a rolling window. - Generation Time (Serial Interval):
- True Interval: Days elapsed from source exposure to target exposure.
- Clinical Interval: Days elapsed from source symptom onset to target symptom onset.