pypmca Elements

​Each of the following sections describes the classes that make up the pypmca population modelling engine.

Model

A model object consisting of an ordered set of connector objects that connect population objects.

The following takes place in order to calculate the evolution by one time step:

The default time_step length is 1 day. The initial release for the ipypm user interface requires the time step to be 1 day. If working withe the pypmca package directly, shorter or longer time steps are allowed.

The model can be built by writing python code that creates the objects and adds them to the model, or by using the ipypm graphical user interface. An example of the code that produced reference model #2 is found here.

Important methods:

reset() Returns the model to its initial state, removing data from previous calculations. This should be called before a new calculation is made.

evolve_expectations(n_step) Calculates the expected size of each population for n_step time steps. The population objects store their expectedsize histories in the history instance variable.

generate_data(n_step) Simulates the system by reporting outcomes of the integer random variables that underly the connectors in the model. The population objects store their size histories in the same history instance variable that would be used for expectations.

copy_values_from(from_model) This allows you to transfer the tuned values from the from_model to a revised model (having the same names for parameters).

save_file(filename) The model is saved to a file (typically < 50kB) that has all information about the connections between populations and all parameters and their values. The filetype for a pyPM.ca model file is .pypm.

Model.open_file(filepath) Class method to restore a previously saved model. Returns a model object.

Booting a model

After resetting a model to initial state, the first step taken when the next model evolution is requested is to boot the model to arrive at t_0 with a steady state solution for all populations. This ensures that the evolution beyond t_0 will continue a steady state development, until the next transition.

The boot process deals with this, by starting with small populations and evolve the system until a population exceeds a target value. History is removed and the current populations and futures are used for the initial state of the model. To allow for continuous dependence, the current value and futures are scaled by the ratio of the target value to actual value, except for the exclusion_populations.

The booting process is therefore defined by:

The boot sequence ends when the boot_population reaches or exceeds the value set for the initial_value of that population.

Population

A population class represents an identifiable category that is useful to be tracked. Some populations are necessary to describe the evolution of the system as a whole. Others are created in order to define a population that corresponds to category for which data exists.

After the expectations or simulation data are produced, the population instance variable history is an python list with length n_step+1.

The populations are accessed through the populations dictionary in the model. For example the ‘contagious’ population history is accessed by: my_model.populations['contagious'].history

Connector

The ordered list of connector objects define the calculations for the evolution of the system. In general connectors take the incoming members and distribute them to other populations either all in the next time step, or spread over time in the future, to represent a delay distribution. There are several types of connectors:

Multiplier

A multiplier produces new members according to the product of the sizes two populations (and optionally dividing by a third). This defines the infection cycle of an epidemic as the number of new infections is calculated to be the transmission rate (alpha) times the product of the sizes of the suceptible and contagious populations, divided by the size of the total population.

Propagator

A propagator distributes new members from one population to one or more other populations. If more than one population, this is done independently when generating simulated data.

Splitter

A splitter divides a fraction of new members from one population to two or more populations. When generating simulated data, this is done by drawing random numbers from a multinomial distribution, therefore treating the dependence of the “to populations” correctly.

Chain

This connector combines several one-to-one propagators to and sends the remainder to another population. By combining these into a single connector the dependence of the various populations involved are treated correctly when generating data.

Adder

This is a simple connector that copies incoming members from one population to another population immediately. This is useful if two populations are needed to record similar quantities, such as “hospitalized” - total number admitted to hospital, and “in_hospital” - the number in hospital that day. See “Subtractor”.

Subtractor

This simple connector allows for members to be removed from a population. As patients in a hospital recover they are subtracted from the “in_hospital” population, but not from the “hospitalized” population”.

Parameter

Parameter objects are created for each parameter that may affect the evoluton of the system.

Important methods:

get_value() returns the current value

set_value(value) sets the value

After changing the value of one or more parameters, you may want to recalculate the model evolution. As described above this means:

my_model.reset()
my_model.evolve_expectations(n_step)

Parameters can be set to variable in order to identify those which are to be tuned to best fit data. The resulting values for the parameters are point estimates.

Parameters are accessed through the parameters dictionary in the model. For example the current value of the ‘alpha_0’ parameter (the initial transmission rate) is accessed by: my_model.parameters['alpha_0'].get_value()

Delay

Delay objects are created to describe the delay distribution for the connectors. The delay_type can be:

Transition

There are two types of transition objects (injector and modifier). A transition occurs when the step counter reaches the step specified by the transition object. At that step, either a parameter value is changed or a set of members is injected into a population.

Injector

An injector object will inject a set of members into a population. Example uses are to model a burst of infections, such as seen in Alberta and Saskatchewan in April and May 2020.

Modifier

A modifier object will cause the value of a parameter to change at a particular time step. Example usage includes the change in transmission rates that arise when changes to social distancing rules are made.

Ensemble

An ensemble object is a collection of models. It allows for categorization of populations by age, risk, or other factors. It can also be used to combine many models that may or may not be nearly independent (such as separate provinces to make a Canada wide model).

Each category has its own model, and one model can influence the growth within other models. The ensemble disables the infection cycle in each of the models and performs the infection cycle which includes the mixing between models, as specified by the contact_maxtrix. The ensemble sums the histories of all its models to represent the evolution of the entire system. Like for a model, the ensemble population histories can be accessed through the ensemble populations dictionary.

When mixing models having different growth rates, achieving the desired initial condition at t_0 through the boot process is challenging. The boot process starts with a small number in each model’s boot_population and the boot ends when the ensemble exceeds its goal. It is possible that the relative sizes for each model would differ significantly from the desired proportions, at t=0, and after scaling each model for the t_0 condition, the state would be far from a steady state. A second boot can be done (by adjusting the small numbers in each model’s boot_population) according to the outcome from the first boot - ie. doing an iterative boot. This issue is not as serious if, at least initially, the growth behaviours of the different groups are similar.

Owing to the complexity of ensembles made from mixtures of very different growth rate sub groups, such situations should be treated with care until such behaviour has been thoroughly tested!

Important methods:

save_file(filename) The ensemble is saved to a file that has all information about the models and their connections The filetype for a pyPM.ca ensemble file is .pypm_e.

Ensemble.open_file(filepath) Class method to restore a previously saved ensemble. Returns an ensemble object.

upload_models(list_of_models) and upload_model_files(list_of_files) adds models to the ensemble.

define_cross_transmission(infection_cycle_name, infected_name, susceptible_name, total_name, contagious_name, alpha_name, contact_type, contact) defines how the populations mix and gives sufficient information for the ensemble to replicate the infection cycle.

For example new infections in model A arising from interactions with group B are calculated by Susceptible_A / M * f[A][B] * Contagious_B * alpha_AB

In a homogeneous society, all alphas are the same, and all terms of the matrix are 1. f represents the relative probability for a contagious members of B group to infect a random member of the A group (relative to infecting a random member of the B group). The off diagonal elements are typically less than 1. The diagonal elements are 1 by definition. A diagonal matrix describes a set of independent populations. Given the definintion of M, both homogenous and independent populations yield their original infection rate without adjusting alpha.

The contact matrix can be specified in a number of ways, defined by contact_type: