Skip to content

Age-structured SEIcIscR (clinical/subclinical)

Age-structured SEIcIscR model of the Wuhan COVID-19 outbreak with clinical and subclinical infectious compartments and contact-matrix mediated transmission.

SEIcIscRModel(config)

Bases: Model

Age-structured SEIcIscR model of the Wuhan COVID-19 outbreak.

Faithful translation of the SEIcIscR variant of Prem et al. (2020): a deterministic, discrete-time (dt = 1 day) age-structured model with

S  -> E                 (contact-matrix mediated force of infection)
E  -> Ic  (clinical,    fraction rho, age-specific)
E  -> Isc (subclinical, fraction 1 - rho)
Ic -> R
Isc-> R

Clinical (Ic) cases are fully infectious; subclinical (Isc) cases are only 25% as infectious. Transitions use the discrete-time hazard 1 - exp(-1/duration) exactly as in the source (rather than 1/duration), so a fixed-step Euler solver reproduces the difference equations.

NOTE: the source's location-decomposed contact matrix (home/work/school/ others) and its time-varying pWorkOpen intervention schedule cannot be represented exactly; the framework's single aggregated (Prem 2021) contact matrix is used and the interventions are approximated as reductions on beta.

Source code in compartment/models/wuhan_seir_model/model.py
def __init__(self, config):
    super().__init__(config)
    # Extra parameters (fall back to source defaults if the framework did not set them).
    if not hasattr(self, "subclinical_infectiousness") or self.subclinical_infectiousness is None:
        self.subclinical_infectiousness = 0.25
    if not hasattr(self, "clinical_fraction_child") or self.clinical_fraction_child is None:
        self.clinical_fraction_child = 0.4
    if not hasattr(self, "clinical_fraction_adult") or self.clinical_fraction_adult is None:
        self.clinical_fraction_adult = 0.8

get_initial_population(admin_zones, compartment_list, **kwargs) classmethod

Seed each zone's infected percentage into the clinical infectious track.

The framework default seeds a compartment literally named "I", which this model does not have. The seed goes into Ic (clinical infectious); the E/Ic/Isc balance relaxes to the model's own proportions within a few simulated days.

Source code in compartment/models/wuhan_seir_model/model.py
@classmethod
def get_initial_population(cls, admin_zones, compartment_list, **kwargs):
    """Seed each zone's infected percentage into the clinical infectious track.

    The framework default seeds a compartment literally named "I", which
    this model does not have. The seed goes into Ic (clinical infectious);
    the E/Ic/Isc balance relaxes to the model's own proportions within a
    few simulated days.
    """
    import numpy as onp

    col = {v: i for i, v in enumerate(compartment_list)}
    pop = onp.zeros((len(admin_zones), len(compartment_list)))
    for z, zone in enumerate(admin_zones):
        N = float(zone["population"])
        pct = max(float(zone.get("infected_population", 0.0) or 0.0), 0.0)
        infected = N * pct / 100.0
        pop[z, col["S"]] = max(N - infected, 0.0)
        pop[z, col["Ic"]] = infected
    return pop