Components
laser.cohorts models are assembled from small, single-responsibility component classes. Each class handles one compartment or one transition rule. Combining them builds the disease model you need.
Component protocol
Every component implements the same interface:
| Method / property | Called by | Purpose |
|---|---|---|
states |
model.components = |
Declare which compartment names this component uses |
properties |
model.components = |
Declare per-tick, per-node output arrays |
setup() |
model.components = |
Seed initial conditions from model.scenario |
start_step(tick) |
model.run() |
Pre-step hook (rarely used) |
step(tick) |
model.run() |
Apply the component's transition logic |
end_step(tick) |
model.run() |
Post-step hook (rarely used) |
All six methods must exist. Components that don't need a hook implement it as a no-op.
states and properties determine what gets allocated when you assign model.components. The model collects the union of all declared state names and node properties, de-duplicates them, and allocates one StateArray and the required node property arrays before calling any setup().
Compartment components
These components own a compartment — they read initial conditions from model.scenario in setup() and otherwise let the carry-forward mechanism keep the values alive across ticks.
Susceptible
Declares state S. Seeds states.S[0] from scenario["S"] at setup.
1 | |
Exposed
Declares states E and I. Seeds states.E[0] from scenario["E"] at setup. Each tick draws newly infectious individuals from a binomial and moves them from E to I, recording the flow in nodes.newly_infectious.
1 | |
r_progression is a ValuesMap of shape (nticks, n_nodes) giving the per-tick, per-node rate of progression from E to I. Higher values mean shorter latent periods.
Infectious
Declares state I. Seeds states.I[0] from scenario["I"]. No transition logic — used in models where I is a terminal state (SI) or where a separate transition component handles recovery.
1 | |
Recovered
Declares state R. Seeds states.R[0] from scenario["R"]. No transition logic — used in SIR-type models where R is absorbing, or with RecoveredToSusceptible for waning immunity.
1 | |
Transition components
These components move individuals between existing compartments each tick. They extend one of the compartment components and override step().
InfectiousToRecovered
Moves individuals from I to R. Used in SIR, SEIR, and their variants. Records the daily flow in nodes.newly_recovered.
1 | |
In the SIR preset this class is re-exported as SIR.Infectious — it replaces the plain Infectious component and handles both the I compartment and the I→R transition.
InfectiousToSusceptible
Moves individuals from I back to S (no lasting immunity). Used in SIS and SEIS models. Records the flow in nodes.newly_susceptible.
1 | |
RecoveredToSusceptible
Moves individuals from R back to S (waning immunity). Used in SIRS and SEIRS models. Records the flow in nodes.newly_susceptible.
1 | |
Note
InfectiousToSusceptible and RecoveredToSusceptible both declare
newly_susceptible as a node property. When both are present in the same
model the model de-duplicates the declaration and allocates one shared array;
both components accumulate into it.
Transmission components
Transmission components compute the force of infection each tick, draw newly infected individuals from a binomial, and move them from S into a destination compartment. They also apply inter-node mixing via model.network.
Force of infection
The raw per-node force of infection is:
where \(s_i\) is the seasonality factor (1.0 by default), \(I_i\) is the current infectious count, and \(N_i\) is the total population. This is then mixed across nodes:
where \(w_{ij}\) is model.network[i, j]. Finally \(\lambda\) is converted from a rate to a per-tick probability: \(p = 1 - e^{-\lambda}\).
The raw \(\lambda\) values are stored in nodes.force_of_infection each tick.
TransmissionSI
S → I directly. Used in SI, SIR, SIS, and SIRS models.
1 2 | |
Records newly infected individuals in nodes.newly_infectious.
TransmissionSE
S → E (latent period). Used in SEI, SEIR, SEIS, and SEIRS models.
1 2 | |
Records newly exposed individuals in nodes.newly_infected.
Rate parameters and ValuesMap
All transition rates (r_recovery, r_progression, r_waning) and beta are expressed as per-tick rates, not probabilities. The conversion to a per-tick probability happens internally:
This means rates are additive (you can sum them) and do not depend on tick length. For a 7-day infectious period with daily ticks, use r_recovery = 1/7.
Parameters are passed as ValuesMap objects — per-tick, per-node arrays of shape (nticks, n_nodes).
1 2 3 4 5 6 7 8 | |
Seasonality
TransmissionSI and TransmissionSE accept an optional seasonality parameter — a ValuesMap or 2-D array of shape (nticks, n_nodes) that multiplies beta each tick. A value of 1.0 (the default) means no seasonal effect.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | |
Vital dynamics
NonDiseaseMortality
Applies background mortality to one or more compartments each tick. Individuals are drawn from a binomial and removed. Deaths are accumulated in nodes.non_disease_mortality.
1 2 3 4 5 6 7 | |
r_mortality can be a scalar, a ValuesMap, or a 2-D ndarray of shape (nticks, n_nodes). states accepts any iterable of state names; None (the default) targets every state.
ConstantPopBirths
Reads the deaths recorded by NonDiseaseMortality and adds the same count back into S, keeping total population constant. Must be ordered after NonDiseaseMortality in the component list.
1 2 3 4 5 6 7 8 9 10 11 12 13 | |
Migration
Migration moves individuals between nodes each tick using a 3-D routing tensor.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | |
r_migration is a scalar emigration rate applied to every state in every node each tick. routing is (nticks, n_nodes, n_nodes) — rows are source nodes, columns are destinations. Rows are normalized internally; zero-sum rows mean no emigration from that node.
For time-varying connectivity, build a real (nticks, n, n) array and set different slices for different periods. static_routing(routing_2d, nticks) is a convenience wrapper around np.broadcast_to for the common case where connectivity doesn't change over time.
Population is exactly conserved across migration: the last destination in the sequential-binomial decomposition absorbs any rounding remainder.