simpar package

sim module

SIR-style model simulation.

The module defines a [Sim] class. The class is initialized with base parameters for the simulation. The [step] method allows one to step through the simulation one or many time periods at a time. Infection spread and discovery parameters can be specified and the base parameters are used by default.

class simpar.sim.Sim(max_T: int, init_susceptible: ndarray, init_infected: ndarray, init_recovered: ndarray, infection_rate: ndarray, init_discovered: ndarray | None = None, init_hidden: ndarray | None = None, infection_discovery_frac: float | ndarray = 1, recovered_discovery_frac: float | ndarray = 1, outside_rate: ndarray = 0)[source]

Bases: object

Maintains an SIR-style model simulation on a heterogeneous population.

Infections last a single generation, after which people recover and are immune for the remainder of the simulation. Infections may be discovered immediately (e.g., through symptomatic self-reporting or surveillance) or may be discovered later (e.g., because of an asymptomatic test applied to the whole population).

During execution, S, I, and R are TxK matrices that track the number of susceptible, infectious, and recovered people in each of the K groups over T time periods. Additionally, D and H track the number of discovered and hidden non-susceptible people.

Initialize an SIR-style model simulation.

The initial state of the simulation is passed through the [init_susceptible], [init_infected], and [init_recovered] parameters. The [init_discovered] and [init_hidden] are optional parameters. If they are not set, the initial discovered and hidden assumes all recovered are discovered and [infection_discovery_frac] of infected are discovered.

Internal infection spread is determined by the [infection_rate] matrix parameter which indicates the number new infections that an infected person in one group creates in another. Additionally, the [outside_rate] parameter specifies the outside rate of infection.

To model infection discovery, the [infection_discovery_frac] parameter determines what fraction of infected are discovered in the generation they are infected. Otherwise, they become “hidden recovered.” Furthermore, the [recovered_discovery_frac] parameter determines what fraction of hidden recovered infections are discovered in each generation.

Parameters:
  • max_T (int) – Maximum number of time periods to simulate.

  • init_susceptible (np.ndarray) – Vector of the initial number of people in each group that are susceptible.

  • init_infected (np.ndarray) – Vector of the initial number of people in each group that are infected.

  • init_recovered (np.ndarray) – Vector of the initial number of people in each group that are recovered.

  • infection_rate (np.ndarray) – Matrix where infection_rate[i,j] is the number of new infections that an infected person in group i creates in group j

  • init_discovered (np.ndarray) – Vector of the initial number of people in each group that are discovered.

  • init_hidden (np.ndarray) – Vector of the initial number of people in each group that are hidden.

  • infection_discovery_frac (float or np.ndarray) – Fraction of infections discovered in the generation they are infected. Can be specified globally (as a float) or separately for each group (as a np.ndarray). Defaults to 1.

  • recovered_discovery_frac (float or np.ndarray) – Fraction of hidden recovered discovered in each generation. Can be specified globally (as a float) or separately for each group (as a np.ndarray). Defaults to 1.

  • outside_rate (np.ndarray) – infections per time period, weighed by population of each group in a meta-group.

property D
property H
property I
property R
property S
static from_dictionary(d: Dict)[source]

Initialize an instance of Sim from a dictionary.

step(n: int = 1, infection_rate: ndarray | None = None, infection_discovery_frac: float | ndarray | None = None, recovered_discovery_frac: float | ndarray | None = None, outside_rate: ndarray | None = None)[source]

Take [n] steps forward in the simulation.

To model change in infection and discovery parameters over time, these parameters can be provided to be applied to the next [n] time periods.

Parameters:
  • infection_rate (np.ndarray) – Matrix where infection_rate[i,j] is the number of new infections that an infected person in group i creates in group j

  • infection_discovery_frac (float or np.ndarray) – Fraction of infections discovered in the generation they are infected. Can be specified globally (as a float) or separately for each group (as a np.ndarray). Defaults to 1.

  • recovered_discovery_frac (float or np.ndarray) – Fraction of hidden recovered discovered in each generation. Can be specified globally (as a float) or separately for each group (as a np.ndarray). Defaults to 1.

  • outside_rate (np.ndarray) – infections per time period, weighed by population of each group in a meta-group.

micro module

Methods to perform “microscopic” calculations that predict days infectious.

T | time between surveillance tests
X | beginning of period when an individual is infectious (and detectable)
D | delay to isolate after testing positive
R | total length of the infectious period
N | index of first surveillance to test
I | total time an individual is infectious AND free

========================================
Example timeline:
Individual tests positive on second test

|---|------|-----|----|-----|----|----|
0   X      T    T+D   2T    I    3T   R

N          0          1          2
========================================

We will use the above notation to discuss the calculations performed in this module. The infection begins between two surveillance tests at some point X which is uniformly distributed on [0,T]. Hence, the number of days between infection and the first surveillance test is T-X.

Note: We (pessimistically) assume a person is infectious the entire time they are PCR-detectable and that the infection level remains constant.

Using this notation, the number of days when the person is infectious is I = min(T-X+NT+D, R) where the first case represents isolating the individual before the end of their infectious period.

simpar.micro.days_infectious(days_between_tests: float, isolation_delay: float, sensitivity: float, max_infectious_days: float)[source]

Return the expected time someone is infectious and free.

Equivalently, Compute E[I]. The number of surveillance tests N that are required for a person to test positive is a geometric random variable with probability given by [sensitivity]. So the person tests positive on test n (where the first test is n=0) with probability P(N=n) = [sensitivity] * np.pow(1-[sensitivity], n).

Parameters:
  • days_between_tests (float) – Number of days between surveillance tests. Provide np.inf for no surveillance testing.

  • isolation_delay (float) – Number of days to isolate after positive test.

  • sensitivity (float) – Sensitivity of surveillance test.

  • max_infectious_days (float) – Maximum infectious period.

simpar.micro.days_infectious_perfect_sensitivity(days_between_tests: float, isolation_delay: float, max_infectious_days: float)[source]

Return the expected time someone is infectious and free assuming perfect test sensitivity.

groups module

Heterogeneous population manager.

This module defines a [MetaGroup] class which describes a group of people for which most parameters are similar but who have varying degrees of social contact. Furthermore, it defines a [Population] which is comprised of multiple [MetaGroup]s. Testing strategies can be set at granularity of [MetaGroup]s.

class simpar.groups.MetaGroup(name, pop, contact_units)[source]

Bases: object

Initialize a meta-group.

A “meta-group” is a collection of groups. Each group within the meta-group is associated with a level of social contact.

The population is assumed to be well-mixed meaning that group i’s amount of interaction with group j is proportional to group j’s fraction of the population and both groups contact levels.

In a school setting, example meta-groups could be undergraduates, professional students, graduate students, and faculty/staff.

static from_truncated_pareto(name: str, population: float, a: float, ub: int)[source]

Initialize a meta-group from a truncated Pareto distribution.

Parameters:
  • name (str) – Name of the metagroup.

  • population (float) – Total population in this metagroup.

  • a (float) – Shape parameter (alpha) for the Pareto distribution.

  • ub (int) – Truncation point for the Pareto distribution.

get_init_SIR_and_DH(init_infections: float, init_recovered: float, init_discovered: float | None = None, init_hidden: float | None = None, weight: str = 'population')[source]

Return initial SIR and DH vectors.

Given [init_infections] and [init_recovered] and optionally [init_discovered] and [init_hidden] aggregated at the meta-group level, the [weight] parameter specifies how these counts should be distributed across the groups within this metagroup. The available options for [weight] are:

  • “population”: Each person is equally likely to be infected.

  • “population x contacts”: A person’s probability of being infected is proportional to their amount of contact.

  • “most social”: The initial infections are in the most social group.

If [init_discovered] and [init_hidden] are not provided, assume that everyone is discovered.

Parameters:
  • init_infections (float) – Initial infections count in meta-group.

  • init_recovered (float) – Initial recovered count in meta-group.

  • init_discovered (float) – Initial discovered count in meta-group.

  • init_hidden (float) – Initial hidden count in meta-group.

  • weight (str) – {population, population x contacts, most_social}

infection_matrix(infections_per_contact_unit: float)[source]

Return the infection matrix.

outside_rate(outside_rate: float)[source]

Return the outside rate.

class simpar.groups.Population(meta_groups_dict: Dict[str, MetaGroup], meta_group_contact_matrix: ndarray)[source]

Bases: object

A population is a collection of meta-groups.

Initialize a population.

A population is defined by a dictionary of meta-groups: [meta_groups_dict]. Meta-group interactions are not assumed to be well-mixed. The matrix [meta_group_contact_matrix] indicates how metagroups interact with one another. This is encoded in the [infection_matrix] which is normalized to be in “contact units.”

Parameters:
  • meta_groups_dict (Dict[str, MetaGroup]) – Dictionary of meta-groups.

  • meta_group_contact_matrix (np.ndarray) – Interactions between meta-groups where entry (i,j) is the conditional probability that the exposed is in meta-group j, given that the source is in meta-group i.

static from_truncated_paretos_dictionary(d: Dict)[source]

Return a [Population] initialized from the given dictionary.

get_init_SIR_and_DH(init_infections: ndarray, init_recovered: ndarray, init_discovered: ndarray | None = None, init_hidden: ndarray | None = None, weight: str = 'population')[source]

Return initial SIR and DH vectors.

Given [init_infections] and [init_recovered] and optionally [init_discovered] and [init_hidden] at the meta-group level, the [weight] parameter specifies how these counts should be distributed across the groups within each metagroup. The available options for [weight] are:

  • “population”: Each person is equally likely to be infected.

  • “population x contacts”: A person’s probability of being infected is proportional to their amount of contact.

  • “most social”: The initial infections are in the most social group.

If [init_discovered] and [init_hidden] are not provided, assume that everyone is discovered.

Parameters:
  • init_infections (np.ndarray) – Initial infections per meta-group.

  • init_recovered (np.ndarray) – Initial recovered per meta-group.

  • init_discovered (np.ndarray) – Initial discovered per meta-group.

  • init_hidden (np.ndarray) – Initial hidden per meta-group.

  • weight (str) – {population, population x contacts, most_social}

infection_discovery_frac(infection_discovery_frac)[source]

Return the fraction of infections that are discovered.

infection_matrix(infections_per_contact_unit: float)[source]

Return the infection matrix.

meta_group_ids(meta_group)[source]

Return the group ids of the groups in the given meta-group.

outside_rate(outside_rates: ndarray)[source]

Return the outside rate.

recovered_discovery_frac(recovered_discovery_frac)[source]

Return the fraction of recovered that are discovered.

strategy module

Manage types of surveillance tests and strategies.

This module defines a [Test] class which maintains properties about a specific surveillance test. Furthermore, it defines an [IsolationRegime], [ArrivalTestingRegime], and [TestingRegime] which are used to comprise a [Strategy]. This specifies how long people are isolated, how people are tested upon arrival, and what testing regime(s) are used after they arrive and for what periods of time.

class simpar.strategy.ArrivalTestingRegime(pre_departure_test_type: List[Test], arrival_test_type: List[Test])[source]

Bases: object

This class maintains an arrival testing regime.

It offers methods to return the percentage of people that are discovered in pre-departure testing and the percentage of people that are discovered upon arrival. This allows for planning surrounding potential isolation of those who arrive and test positive.

Initialize an arrival testing regime.

Parameters:
  • pre_departure_test_type (List[Test]) – The type of test to be used for pre-departure testing per meta-group.

  • arrival_test_type (List[Test]) – The type of test to be used for arrival testing per meta-group.

static from_dictionary(d: Dict, tests: Dict[str, Test])[source]

Initialize a [ArrivalTestingRegime] instance.

The dictionary [d] should contain two keys: [pre_departure_test_type] and [arrival_test_type] which contain list of strings. These are interpreted as keys in the [tests] dictionary which provides the corresponding [Test] instance.

Parameters:
  • d (Dict) – Dictionary representing the arrival testing regime.

  • tests (Dict[str, Test]) – Dictionary of [Test] instances.

get_pct_discovered_in_arrival_test()[source]

Return the percentage of infections discovered upon arrival.

get_pct_discovered_in_pre_departure()[source]

Return the percentage of infections discovered in pre-departure.

class simpar.strategy.IsolationRegime(iso_lengths: List[int], iso_props: List[int])[source]

Bases: object

This class maintains properties about an isolation regime.

Initialize an isolation regime.

Parameters:
  • iso_lengths (List[int]) – List of isolation lengths (in days).

  • iso_props (List[int])) – List of probability of isolation lengths.

static from_dictionary(d)[source]

Initialize an [IsolationRegime] from a dictionary

class simpar.strategy.Strategy(name: str, period_lengths: List[int], testing_regimes: List[TestingRegime], transmission_multipliers: List[float] | None = None, arrival_testing_regime: ArrivalTestingRegime | None = None, isolation_regime: IsolationRegime | None = None)[source]

Bases: object

This class maintains a testing strategy comprised of an [IsolationRegime], [ArrivalTestingRegime], and a list of [TestingRegime]s.

If offers methods to return the initial number of infections, the initial number of recovered, and the arrival discovered (useful for understanding isolation capacity needs).

Initialize a testing strategy.

Parameters:
  • name (str) – Name for this strategy.

  • period_lengths (List[int]) – Length (in generations) of each period of the simulation.

  • testing_regimes (List[TestingRegime]) – Testing regime to be used in each period of the simulation.

  • transmission_multipliers (List[float]) – Transmission multiplier to be used in each period of the simulation. Defaults to 1.

  • arrival_testing_regime (ArrivalTestingRegime) – Arrival testing regime to be used. Defaults to None.

  • isolation_regime (IsolationRegime) – Isolation regime to be used. Defaults to None.

static from_dictionary(d: Dict, arrival_testing_regimes: Dict, testing_regimes: Dict, isolation_regimes: Dict)[source]

Initialize a [Strategy] instance.

The dictionary [d] should contain a key [testing_regimes] and [isolation_regime] and may contain a key [arrival_testing_regime]. These should be keys in [arrival_testing_regimes], [testing_regimes], and [isolation_regimes], respectively.

Parameters:
  • d (Dict) – Dictionary representing the strategy.

  • arrival_testing_regimes (Dict) – Dictionary of [ArrivalTestingRegimes] instances.

  • testing_regimes (Dict) – Dictionary of [TestingRegimes] instances.

  • isolation_regimes (Dict) – Dictionary of [IsolationRegime] instances.

get_initial_discovered(recovered: ndarray, pct_recovered_discovered: ndarray, active_infections: ndarray)[source]

Return the initial discovered when this strategy is used.

Parameters:
  • recovered (np.ndarray) – Recovered per meta-group.

  • pct_recovered_discovered (np.ndarray) – Percentage of the recovered population that is discovered per meta-group.

  • active_infections (np.ndarray) – True number of active infections per meta-group.

get_initial_hidden(recovered: ndarray, pct_recovered_discovered: ndarray, active_infections: ndarray)[source]

Return the initial hidden when this strategy is used.

Parameters:
  • recovered (np.ndarray) – Recovered per meta-group.

  • pct_recovered_discovered (np.ndarray) – Percentage of the recovered population that is discovered per meta-group.

  • active_infections (np.ndarray) – True number of active infections per meta-group.

get_initial_infections(active_infections: ndarray)[source]

Return the initial infections when this strategy is used.

Parameters:

active_infections (np.ndarray) – True number of active infections per meta-group.

get_initial_recovered(recovered: ndarray, active_infections: ndarray)[source]

Return the initial recovered when this strategy is used.

Parameters:
  • recovered (np.ndarray) – Recovered per meta-group.

  • active_infections (np.ndarray) – True number of active infections per meta-group.

class simpar.strategy.Test(name: str, test_sensitivity: float, test_delay: float, compliance: float = 1)[source]

Bases: object

Initialize a test

Parameters:
  • name (str) – Name of the surveillance test.

  • test_sensitivity (float) – Probability of positive given infectious.

  • test_delay (float) – Delay in receiving results from test (in days).

  • compliance (float, optional) – Compliance with test. Defaults to 1.

static from_dictionary(name, d)[source]

Initialize a [Test] from a dictionary

class simpar.strategy.TestingRegime(test_type: List[Test], tests_per_week: ndarray)[source]

Bases: object

Initialize a testing regime.

Parameters:
  • test_type (List[Test]) – The test type to be used per meta-group.

  • tests_per_week (np.ndarray) – Test frequency per meta-group.

static from_dictionary(d: Dict, tests: Dict[str, Test])[source]

Initialize a [TestingRegime] instance.

The dictionary [d] should contain a key [test_type] which contain list of strings. These are interpreted as keys in the [tests] dictionary which provides the corresponding [Test] instance.

Parameters:
  • d (Dict) – Dictionary representing the testing regime.

  • tests (Dict[str, Test]) – Dictionary of [Test] instances.

get_days_infectious(max_infectious_days: float)[source]

Return the expected number of days infectious.

This value requires the context of the maximum infectious days.

Parameters:

max_infectious_days (float) – Max days someone is infected.

get_infection_discovery_frac(symptomatic_rate: float)[source]

Return the discovery rate among infected people.

This value is equivalent to the “true sensitivity” of a test when surveillance testing is in place. Otherwise, it is the test sensitivity multiplied by the symptomatic rate as only those that are symptomatic seek out a test. This assumes all symptomatic people will seek out a test. I.e. compliance among the symptomatic is 100%.

Parameters:

symptomatic_rate (float) – Symptomatic rate.

get_recovered_discovery_frac(no_surveillance_test_rate: ndarray)[source]

Return the discovery rate among recovered people.

This value is equivalent to the “true sensitivity” of a test when surveillance testing is in place. This serves as a rough estimate which becomes more inaccurate with less frequent testing. If there is no surveillance testing, it is the no surveillance test rate multiplied by the test sensitivity.

Parameters:

no_surveillance_test_rate (np.ndarray) – Test rate per meta-group.

simpar.strategy.strategies_from_dictionary(d: Dict, tests: Dict)[source]

Return a dictionary of strategies.

Parameters:
  • d (Dict) – Dictionary maintaining strategies.

  • tests (Dict) – Dictionary of test types used in the strategies.

scenario module

Scenario for disease spread.

This module defines a [Scenario] class which maintains a scenario on which a testing strategy can be applied. By combining a scenario with a testing strategy, a simulation can be run allowing for the comparison of multiple testing strategies on a single scenario.

class simpar.scenario.Scenario(population: Population, max_T: int, generation_time: float, infections_per_contact_unit: ndarray, init_infections: ndarray, init_recovered: ndarray, outside_rate: ndarray, max_infectious_days: float, symptomatic_rate: float, no_surveillance_test_rate: ndarray, pct_recovered_discovered: ndarray, hospitalization_rates: ndarray, arrival_period: float, tests: Dict[str, Test])[source]

Bases: object

This class maintains a scenario on which a testing strategy can be applied.

A scenario is a list of parameters which describe the population of people, the environment, and the disease spreading. By combining a scenario with a testing strategy, a simulation can be run allowing for the comparison of multiple testing strategies on a single scenario.

Initialize a [Scenario] instance.

Parameters:
  • population (Population) – The population.

  • max_T (int) – The length of the simulation.

  • generation_time (float) – Number of days per generation of sim.

  • infections_per_contact_unit (np.ndarray) – Number of infections per contact unit across meta-groups.

  • init_infections (np.ndarray) – Number of initial infections across meta-groups.

  • init_recovered (np.ndarray) – Number of initial recovered across meta-groups.

  • outside_rate (np.ndarray) – The number of infections caused by outside infection in a generation time across meta-groups.

  • max_infectious_days (float) – Maximum period someone is infectious.

  • symptomatic_rate (float) – Symptomatic rate.

  • no_surveillance_test_rate (np.ndarray) – Rate at which people test under no surveillance testing across meta-groups.

  • pct_recovered_discovered (np.ndarray) – Percentage of the recovered population who are discovered across meta-groups.

  • hospitalization_rates (np.ndarray) – Rate of hospitalizations among those who get infected across meta-groups.

  • arrival_period (float) – The number of generations the arrival occurs over. Testing is assumed constant over these days. If there is no arrival period, set to None.

  • tests (Dict[str, Test]) – Dictionary of available tests.

static from_dictionary(d: Dict)[source]

Initialize a [Scenario] instance from a dictionary.

simulate_strategy(strategy: Strategy)[source]

Return a simulation of the given strategy on this scenario.

trajectory module

Simulation trajectory manager.

Defines a [Trajectory] class which is comprised of a fully run [Sim] that was the result of applying a given [Strategy] to a [Scenario]. The class offers methods to return various metrics.

class simpar.trajectory.Trajectory(scenario: Scenario, strategy: Strategy, sim: Sim, color: str = 'black', name: str | None = None)[source]

Bases: object

Manages all of the attributes of a simulation run.

This includes the underlying simulation [Sim] and the scenario [Scenario] and the strategy [Strategy] that was used. It also maintains a color and name for use when plotting trajectories.

Initialize a [Trajectory] instance.

Parameters:
  • scenario (Scenario) – Scenario that the simulation was run under.

  • strategy (Strategy) – Strategy that was used to run the simulation.

  • sim (sim) – Simulation which used the provided strategy.

  • color (str) – Color of the trajectory when plotting.

  • name (str) – Name of the trajectory when plotting.

get_bucket(bucket: str, meta_groups: List[str] | None = None, aggregate: bool = True, cumulative: bool = False, normalize: bool = False)[source]

Return the given bucket vector.

Parameters:
  • bucket (str) – One of the simulation buckets: {S, I, R, D, H}.

  • meta_groups (List[str]) – Meta-groups to aggregate over. Default to all.

  • aggregate (bool) – Aggregate over the meta-groups if True.

  • cumulative (bool) – Return cumulative metric over time if True.

  • normalize (bool) – Normalize relative to total population.

get_hospitalizations(meta_groups: List[str] | None = None, aggregate: bool = True, cumulative: bool = False, normalize: bool = False)[source]

Return hospitalizations as computed from hospitalization rates.

Parameters:
  • meta_groups (List[str]) – Meta-groups to aggregate over. Default to all.

  • aggregate (bool) – Aggregate over the meta-groups if True.

  • cumulative (bool) – Return cumulative metric over time if True.

  • normalize (bool) – Normalize relative to total population.

get_isolated(meta_groups: List[str] | None = None, aggregate: bool = True, cumulative: bool = False, normalize: bool = False)[source]

Return the number of isolated individuals at each generation.

Parameters:
  • meta_groups (List[str]) – Limit to isolated in these metagroups.

  • aggregate (bool) – Aggregate over the meta-groups if True.

  • cumulative (bool) – Return cumulative metric over time if True.

  • normalize (bool) – Normalize relative to total population.