EpIntervene Simulation Package
Project description
EpIntervene
EpIntervene is a custom simulation framework in Python for Event-Driven simulation of epidemics on networks. What makes this framework unique is its functionality for introducing interventions in the middle of epidemic simulations, its customizable event classes, and its book-keeping of generations of infection.
EpIntervene is still a work in progress. The current version of the software is available for installation
on the PyPi index, available here, available via a recommended pip
install in an anacondas
virtual environment.
Usage
For quick examples on how to use EpIntervene, we recommend walking through the examples in the examples/sandbox.py
file here
for standard demonstrations on SIR and SEIR epidemics. For the intervention features, you can walk through the examples
in examples/interventions_sandbox.py
found here.
For an interactive tool to let you explore what the results of using EpIntervene would look like,
check out my Streamlit app
with source code found here.
This package and its current capabilities support SIR or SEIR model simulations, and purely SIR for the intervention-based simulations.
The two primary Simulation objects can be found in the classes epintervene.simobjects.simulation.Simulation
and epintervene.simobjects.simulation.SimulationSEIR
. For the intervention feature, use any of the available Simulation classes in
epintervene.simobjects.extended_simulation.py
once you have familiarized yourself with how to run a standard simulation.
Currently, calls to simulators run_sim()
methods with the appropriate configurations and run time arguments
result in a single simulation. It is up to the user to aggregate results from an ensemble of runs on their own.
For sample usage, see the examples
module.
Setting up a Simulation Object
A single simulation object is accessed and created via simobjects.simulation.Simulation
and consists of an object encapsulating all information about the configuration, state, and results
for a single simulation. The base class, Simulation
, will run an SIR disease model.
Basic Configurations
To configure a Simulation
object, you'll first need to define a network via a symmetric adjacency list.
The network
module has a built-in function to assist with this and is recommended in order
to obtain an adjacency list in the right form. If supplying one's own adjacency list, make sure it is in the form
[line of node_label_index], node_label, neighbor, neighbor, ...
[line 0] 0 1 7
[line 1] 1 0 3
[lines ... ... ... ...
[line 3] 3 1 22 26 ...
To use the built-in functions, supply a networkX object of a graph G
and call
adjlist = network.NetworkBuilder.create_adjacency_list(G)
This will remove multi-edges, and make a symmetric adjacency list of the form displayed above. G
must be
a NetWorkX Graph object.
Then to create a simulation object, define
your_simulation_object = simulation.Simulation(adj_list=adjlist, N=len(adjlist))
where N
is the number of nodes in your network and should be the length of the list.
Then add your beta
and gamma
rates for infection rate and recovery rate to your simulation via
your_simulation_object.set_uniform_beta(beta=0.004)
your_simulation_object.set_uniform_gamma(gamma=0.001)
Additional Configurations
It may be the case that your network model is partitioned into sub-populations, e.g. a stochastic block model network. Or, your network is based on real data for which the labels indicate specific data on a node's sub-group within the network.
For this reason, you can specify membership_groups
and node_memberships
when initializing a new Simulation
object.
Simply provide a list of ids or labels for each unique membership group, which can be any simple data type (string, its, floats, etc)
and then supply a vector (1-d numpy array) of the membership id for each node. The position in the vector will be read
as the node id, and the entry will indicate the membership group. For example, for a network with 10 nodes representing a zoo, with adjacency list adjlist
, we would set up
zoo_sim = Simulation(adj_list=adjlist, N=10, membership_groups=['elephant', 'tiger', 'bird'], node_memberships=['tiger', 'tiger', 'bird', 'bird', 'bird', ..., 'elephant', 'tiger']
Intervention Models and Configurations
This package offers a few types of simulations with customizable intervention regimes. To access
the intervention simulation objects, access via simobjects.extended_simulation
.
UniversalInterventionSim(Simulation)
will reduce beta
for the entire network to the specified beta_redux
at the specified
intervention generation. How to configure:
adjlist = nb.create_adjacency_list(G)
sim = extended_simulation.UniversalInterventionSim(N=len(adjlist), adjlist=adjlist)
sim.set_uniform_beta(0.9)
sim.set_uniform_gamma(0.1)
sim.configure_intervention(intervention_gen=4, beta_redux=0.6)
This means that when the epidemic reaches generation 4
, beta
the transmission rate will be reduced to beta_redux
.
RandomInterventionSim(Simulation)
This simulation class lets the user configure an intervention at a given epidemic generation to model vaccinating a specified proportion
of the population to a transmissibility of zero, by selecting a random subset of the population. See the
examples/interventions_sandbox
module for examples.
Other interventions:
TargetedInterventionSim
, which works just as RandomInterventionSim
but where the vaccination strategy targets the specified
proportion of the population in decreasing order of degree; highest degree nodes are vaccinated first. Note: Under this
simulation framework, a desired percentage of nodes to vaccinate is specified, however the simulation will
fill that quota regardless of current vaccination or infection status of the node.
RandomRolloutSimulation
and TargetedRolloutSimulation
allow the user to configure a phased rollout of either
random or targeted vaccinations. Specify the list of generations to intervene at, and the proportion of the population
that should be vaccinated at each generation.
AbsoluteTimeNetworkSwitchSim
allows the user to switch the network on the same nodes at a specified time of intervention.
It is best to familiarize with oneself of the simulation on both networks first, to get a sense of the appropriate
time to intervene based on infection rates.
For all of the above, examples are provided in examples/interventions_sandbox.py
.
SEIR Model
An SEIR model simulation object can be set up and configured much in the same way as the SIR one. Currently, interventions are not yet supported on the SEIR framework, but this feature is coming soon. The SEIR simulation framework does support membership groups.
The additional configurations needed from the user are additional rate matrices to describe the node-pair rate for Exposed-Susceptible transmission, and a rate specifying the transition rate for individual nodes from Exposed to Infected. Example:
seir_sim = SimulationSEIR(N=N, adjlist=adj_list)
seir_sim.set_uniform_gamma(gamma=0.00001)
seir_sim.set_uniform_beta(beta=0.5)
seir_sim.set_uniform_beta_es(beta_es=0.5)
seir_sim.set_uniform_gamma_ei(gamma_ei=1.0)
Similarly, one can specify node_memberships=node_membership_vector, membership_groups=['tiger', 'bird', 'elephant']
with the SEIR model.
Running a Simulation object
Once your Simulation object is set up, you are ready to run it.
An important feature of EpIntervene is that even after running your simulation, the object will be preserved with all of its state and attributes, and can be accessed repeatedly for different types of results.
If you run a simulation again, you SHOULD create a NEW Simulation object in order to guarantee a clean state.
To run any of the simulations you've configured, call
your_sim_object.run_sim()
Calling run_sim()
without changing any of its default arguments is the simplest way to run a single simulation
of your object. If you want to specify certain aspects of the simulation, there are a range of options you can choose from.
As of now, run_sim(uniform_rate=True)
is the default. Non-uniform rates are no longer supported so there
is no need to specify a value for it.
If you want to track membership groups and you have configured them properly, you can run
your_sim_object.run_sim(with_memberships=True)
To stop the simulation when there are no more possible infection events, you can specify
your_sim_object.run_sim(wait_for_recovery=False)
Other optional arguments:
kill_by=13
will stop the simulation after Generation 13 when there are no more active members of any generation from 0-13.
p_zero=i
specifies the node label/index for patient zero. If not specified, a random node index will be chosen. To specify multiple
patient zeros, for user-defined node labels set
p_zero=[i, j, k]
and to generate a specific amount (greater than 1) random patient zeros, specify
p_zero=[None, None, None, None]
which will generate 4 random patient zero nodes. To specify a mix of random and non-random patient zeros,
specify p_zero=[i, j, None, k, None]
in any order. If two copies of the same label are added, one copy will be replaced with another random patient zero.
record_active_gen_sizes=True/False
a boolean, defaults to False. Will record the number and size of active generations over time.
An active node is defined as being infectious and still having susceptible neighbors. An active generation is defined as containing
one or more active nodes belonging to that epidemic generation.
Obtaining results from a Simulation
Basic Results
The Epintervene framework is driven by an Event-Driven algorithm, in which continuous time is tracked by drawing an exponential random variable for the waiting time until next event at each discrete time step with rate parameter the sum of the rates of all potential individual events. As a result, the resulting raw time series will not be normalized, making it hard to compare to an ensemble of results should the user intend to average the results of an ensemble. Therefore, when calling the Simulation object for continuous time results, results are binned into a time-resolution that is evenly split into bins.
To obtain a continuous-time time series of your infected and recovered nodes for an SIR simulation, after you have run your simulation call
ts, infect_ts, recover_ts = sim.tabulate_continuous_time(time_buckets=1000)
which will return a time series time values (binned evenly between the start time and end time of the simulation at the resolution of the time_buckets
optional parameter)
and the time series of infected and recovered nodes.
For the SEIR model, the same call returns four time series including the time values themselves, for example
ts, infect_ts, recover_ts, exposed_ts = seir_sim.tabulate_continuous_time(time_buckets=1000)
Tracking Generations of Infection
EpIntervene also provides the user the option to obtain a generational time series from a Simulation object. The form of this return value will be a single vector, where the index of the vector indicates the epidemic generation, in ascending order. Each entry corresponds to the number of cumulatively infected nodes belonging to a generation that is less than or equal to the entry's index. For example,
ts_by_gen = sim.tabulate_generation_results(max_gens=20)
might look like [1, 2, 4, 8, 13, 18, 37]
, which indicates that 1 node was infected in generation 0,
2 nodes were infected in generation 0 and 1 combined, and 13 nodes were infected including generations 0,1, 2, 3, and 4.
It is worth noting for the user that, to obtain the number of nodes in each unique generation,
one can call a diff()
method on the result, where ts_by_gen[i+1] - ts_by_gen[i]
will return the
number of nodes infected in generation i+1
.
This is not a traditional time series in this form, it is a chronological list of cumulative infections by the birth of each epidemic generation. It does not formally correspond to continuous time results.
Tracking results for population groups
To obtain distinct time series results for each membership group, if specified in the simulation, the user may call
ts, membership_ts_infc = sim.tabulate_continuous_time_with_groups(time_buckets=1000)
which will return the time series values, and a dictionary of time series of infections per group. An example of how to iterate through the results and plot each group is here:
for group in membership_ts_infc.keys():
plt.plot(ts, membership_ts_infc[group], label=group)
For the SEIR model, there are 3 return values to include the exposed time series. Example:
ts, membership_ts_infc, membership_ts_exp = seir_sim.tabulate_continuous_time_with_groups(time_buckets=1000)
Classes
See the docs directory for more detailed documentation on each class. Documentation will be updated and is a living document.
Network Objects
NetworkBuilder
A static class to assist users in creating NetworkX
graphs. The NetworkBuilder can return a network
built from a degree distribution, by calling from_degree_distribution()
or from an adjacency matrix,
by calling from_adjacency_matrix()
Node
Internal object that represents a single node in the network, created and disposed during simulations.
Edge
Internal object that represents a pair of nodes, where the left node and right node are distinct.
Examples
See module examples.sandbox.py
for examples of basic SIR, SEIR, and group membership simulations.
See module examples.interventions_sandbox.py
for examples of intervention-based simulations.
Results of an SIR simulation (single run) with membership groups:
Citing and using this code
If using all or any part of this code base in a published or non-published work, please cite as:
Cite this work as:
Andrea Allen. (2021). andrea-allen/epintervene: v1.0.3 EpIntervene Release (v1.0.3). Zenodo. https://doi.org/10.5281/zenodo.5514401
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
File details
Details for the file epintervene-1.1.1.tar.gz
.
File metadata
- Download URL: epintervene-1.1.1.tar.gz
- Upload date:
- Size: 45.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/4.0.1 CPython/3.9.14
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | ec9e2c4d4b63fe650c006642a62e70003b68dd96d684a8388a267639a1e79cac |
|
MD5 | 1fa009b2619ce422a8dae55b1206d6ba |
|
BLAKE2b-256 | e77c8afac3c8e755320a22549ce789d0f96d9e2b6e381ba83eb27877c15dac0b |
File details
Details for the file epintervene-1.1.1-py3-none-any.whl
.
File metadata
- Download URL: epintervene-1.1.1-py3-none-any.whl
- Upload date:
- Size: 44.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/4.0.1 CPython/3.9.14
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | 58f6727dd3b97f98f4b77bf230aaf08f8f15815b19df6b337d073ca181aad03a |
|
MD5 | 9a2335051d2322ddbf2f27beb90813d9 |
|
BLAKE2b-256 | be22fb08e80768d75f8f5b0baf88c3dbdb877c4e813456170608024ba4ed44cf |