SEIRD: A Complex Contagion Model
Contents
SEIRD
: A Complex Contagion Model¶
This is a simple model combining concepts and ideas from the well known SEIR (Susceptible-Exposed-Infected-Recovered) and SIRD (Susceptible-Infected-Recovered-Deceased) models, and adapting them to a spatial 2D grid model.
Fundamentals¶
We model a population of agents on a two-dimensional grid of cells that can move randomly or away from infected agents. Each cell can be in one of the following states:
empty
: there is no agent on the cell.susceptible
: the agent on the cell is healthy but susceptible to the disease and can be exposed if in contact with the disease.exposed
: the agent on the cell is exposed to the disease, meaning that it can already infect other agents in contact with the disease; however, there are no symptoms yet, thus, the agent gets noticed as infected only after the incubation period.infected
: the agent on the cell is infected and can infect neighboring agents.recovered
: the agent on the cell is recovered, thus, it is immune to the disease. However, it can lose its immunity after a while and become susceptible again.deceased
: the agent on the cell is deceased. The cell will again be empty in the next time step.
Special Cell States¶
Additionally, cells can also have the following “special” states:
source
: the cells are infection sources, thus, they can transition neighboring agents from the susceptible to the exposed state.inert
: inert cells are cells that do not partake in any of the model dynamics. They can be used to model spatial heterogeneities like compartmentalization, much likestones
in the Forest Fire model.
Cells that are initialized in one of these states should not be regarded as representing agents: there is no movement for these cells, nor can these cells change their state.
Implementation¶
The implementation allows for a range of different storylines by changing the parameters. Keep in mind that individual processes can often be disabled by setting probabilities to zero.
Update Rules¶
In each time step the cells update their respective states asynchronously, but randomly shuffled to reduce artefacts, according to the following rules:
A living (susceptible, exposed, infected, or recovered) cell becomes empty with probability
p_empty
.An
empty
cell turns into asusceptible
one with probabilityp_susceptible
. With probabilityp_immune
the cell isimmune
to being infected.A
susceptible
cell either becomes randomly exposed with probabilityp_exposed
or becomes exposed with probabilityp_transmit * (1 - p_random_immunity)
if a neighboring cell isexposed
orinfected
, withp_transmit
being the neighboring cell’s probability of transmitting the disease. Disease transmission happens only if the cell is _not_immune
.An
exposed
cell becomesinfected
with probabilityp_infected
.An
infected
cell recovers with probabilityp_recovered
and becomesimmune
, becomesdeceased
with probabilityp_deceased
, or else stays infected.A
recovered
cell can lose its immunity withp_lose_immunity
and becomessusceptible
again.A
deceased
cell turns into anempty
cell.
Movement¶
In each time step, the agents on the cells can move to empty
neighboring cells according to the following rules:
A living (susceptible, exposed, infected, or recovered) cell moves with probability
p_move_randomly
to a randomly chosenempty
neighboring cell, if there is any.A living cell moves away from an
infected
neighboring cell to a randomly selected neighboringempty
cell if there is any.
Note
Movement is meant to represent an agent moving from one cell to another.
Thus, the agents also take with them their agent-specific properties immune
and p_transmit
. This is implemented using a swap operation of the corresponding State
objects.
Heterogeneities¶
As in the Forest Fire model, there is the possibility to introduce heterogeneities into the grid that are implemented as two additional possible cell states:
source
: these are constant exposure sources. They spread the infection like normal infected or exposed cells, but don not revert to the empty state. If activated, they are per default at the lower boundary of the grid, though this can be changed in the configuration.inert
: inert cells are cells that do not partake in the dynamics of the model, and hence can be used to represent barriers. If enabled, the default mode isclustered_simple
, which leads to randomly distributed inert cells whose neighbors have a certain probability to also be inert.
Both make use of the entity selection interface.
Immunity Control¶
Via the immunity_control
parameter in the model configuration, additional immunities can be introduced at desired times, thereby manipulating a cell’s immune
state.
This feature can be used e.g. to investigate the effect of vaccination. The immunities are introduced before the update rule above is carried out.
Exposure Control¶
Via the exposure_control
parameter in the model configuration, additional exposures can be introduced at desired times. The exposures are introduced before the update rule above is carried out.
Transmission Control¶
Via the transmission_control
parameter in the model configuration, the cell-specific state p_transmit
can be manipulated. The cell state manipulation happens before the update rule above is carried out.
Data Output¶
The following data is stored alongside the simulation:
kind
: the state of each cell:0
:empty
1
:susceptible
2
:exposed
3
:infected
4
:recovered
5
:deceased
6
:source
, is constantly infectious7
:inert
, does not take part in any interaction
age
: the age of each cell, reset after a cell turns empty.cluster_id
: a number identifying to which cluster a cell belongs; is0
for non-living cells. Recovered cells do not count towards it.exposed_time
: the time steps a living cell has already been exposed to the disease, for each cell.immunity
: whether or not a cell is immune, for each cell.densities
: the densities of each of the kind of cells over time; this is a labeled 2D array with the dimensionstime
andkind
.counts
: cumulative counters for a number of events, e.g. state transitions. This is a 2D array with the dimensionstime
andlabel
, where the latter describes the name of the counted event:empty_to_susceptible
, i.e. “birth”living_to_empty
, i.e. “random death”susceptible_to_exposed_contact
, via local contact with a neighborsusceptible_to_exposed_random
, via a random point exposure, controlled byp_exposed
susceptible_to_exposed_controlled
, via Exposure Controlexposed_to_infected
, as controlled byp_infected
infected_to_recovered
, as controlled byp_recovered
infected_to_deceased
, as controlled byp_deceased
recovered_to_susceptible
, which happens when losing immunity, as controlled byp_lose_immunity
move_randomly
, as controlled byp_move_randomly
move_away_from_infected
, as enabled bymove_away_from_infected
Hint
When setting the write_ca_data
parameter to False
, only the densities
and counts
are stored.
If spatially resolved information is not required, it is recommended to use this option, as it improves the run time and reduces the amount of disk space needed (which can be very large for large grids).
If spatial information is required, the write_start
and write_every
options can help cut down on disk space usage.
Default configuration parameters¶
Below are the default configuration parameters for the SEIRD
model:
# --- Space parameters --------------------------------------------------------
# The physical space this model is embedded in
space:
periodic: true
# --- CellManager and cell initialization -------------------------------------
cell_manager:
grid:
structure: hexagonal
resolution: 128 # in cells per unit length of physical space
neighborhood:
mode: hexagonal
# Cell initialization parameters
cell_params:
# Initial susceptible density, value in [0, 1]
# Probability that a cell is initialized as susceptible (instead of empty)
p_susceptible: 0.3
# Initial immune density, value in [0, 1]
# With this probability, a cell is initialized as immune; however, only if
# it was initialized as susceptible already (determined by p_susceptible)
p_immune: &p_immune 0.001
# The cell-specific probability p_transmit to transmit the disease to other
# cells. This parameter sets the initial value for each cell.
# This probability can be manipulated using the transmission_control to either
# manipulate it for all cells or a subset of cells at specified times.
# This allows, for example, to set some cells to be superspreader.
p_transmit: &p_transmit
mode: value
value:
default: 1.
uniform:
range: [0., 1.]
# --- Model Dynamics ----------------------------------------------------------
# NOTE that the default parameters below are chosen according to the
# Covid-19 storyline roughly approximating the current knowledge, which
# of course still changes and updates weekly (updated June 16th, 2020).
# Parameters are chosen such that one time-step corresponds to one day.
# Probability per site and time step to transition from state empty to
# susceptible.
# As default, there are no randomly appearing susceptible cells coming into
# the system.
p_susceptible: 0.
# Probability to be immune per transition from an empty to a susceptible cell
# via p_susceptible.
p_immune: *p_immune
# Cell specific probability of transmitting the disease set at transition
# from an empty to a susceptible cell via p_susceptible. It sets the cell
# state `p_transmit` used in the model dynamics.
p_transmit: *p_transmit
# Probability per site and time step for a susceptible cell to _not_ become
# infected if an infected cell is in the neighborhood. This probability
# applies per event, so it does _not_ mean that an immune cell is also immune
# in the next iteration step.
p_random_immunity: 0.
# Probability per susceptible cell and time step for a random point exposure
# NOTE This is affected by the exposure control; see below.
p_exposed: 0.001
# Probability per exposed cell and time step to transition to infected state
# The default corresponds to a mean incubation period of 5 time steps
p_infected: 0.2
# Probability to recover if infected
# Note that p_recovered + p_deceased need to be smaller than 1
# The default is set to 1/14=0.0714, modeling that, on average, infections
# require approximately 14 days to go away.
p_recovered: 0.0714
# Probability to decease if the cell is infected
# Note that p_recovered + p_deceased need to be smaller than 1
# The default is set to 2%, approximating the infection fatality rate
# (see e.g. https://ourworldindata.org/mortality-risk-covid for a rough
# explanation).
p_deceased: 0.02
# Probability per site and time step to transition to empty
# As default, no living cells are vanishing by chance i.e. the system
# is approximated to be closed for the given time scales.
p_empty: 0.
# The probability to lose immunity if a cell is recovered
# This value is quite uncertain because it is not known whether and with what
# probability immunity is lost in the case of Covid-19. Currently, the effect
# of losing immunity through virus mutation seems to be smaller than for the
# common influenza virus with roughly one year. However, there are studies
# suggesting that especially for light Covid-19 infections, new infections
# can happen with a probably non-negligible rate.
# The given default tries to incorporate and estimate both effects roughly.
p_lose_immunity: 0.01
# ... Movement ...............................................................
# Whether to allow cells to move away from infected neighboring cells
# If a neighbor is infected a cell searches for a random empty neighboring
# place and moves towards it. If there is no space, do nothing.
move_away_from_infected: false
# Probability to move in a random direction
p_move_randomly: 0.2
# --- Exposure Control -------------------------------------------------------
# Exposure control to investigate the time-dependent influence of the
# disease driving force. Note that exposure control is applied at the
# beginning of an iteration step. Its effect is seen in the following
# time step
exposure_control:
enabled: false
# The number of additional exposures to be placed on the grid
num_additional_exposures: 10
# Add the additional exposures at the given times
# Note that the !listgen tag creates a list from the parameters
# (start, stop, step_size)
# To disable, pass an empty sequence.
at_times: !listgen [0, 100, 20]
# Change the probability of random exposure.
# The expected value is a list of [iteration_step, new_value] pairs, e.g.
# - [10, .5]
# - [42, 0.]
# ... will set p_expose from the default value to .5 at time 10 and set it
# back to 0. at time 42.
# To disable, pass an empty sequence.
change_p_exposed: []
# --- Immunity Control --------------------------------------------------------
# Immunity control to investigate the time-dependent influence of actively
# provided immunities. Note that immunity control is applied at the
# beginning of an iteration step but after the exposure control. Its effect
# is seen in the following time step
immunity_control:
enabled: false
# The number of additional immunities to be placed on the grid
num_additional_immunities: 10
# Add the additional immunities at the given times
# Note that the !listgen tag creates a list from the parameters
# (start, stop, step_size)
# To disable, pass an empty sequence.
at_times: !listgen [0, 100, 20]
# Change the probability of a random immunity when new susceptible cells
# appear through p_susceptible.
# The expected value is a list of [iteration_step, new_value] pairs, e.g.
# - [10, .5]
# - [42, 0.]
# ... will set p_immune from the default value to .5 at time 10 and set it
# back to 0. at time 42.
# To disable, pass an empty sequence.
change_p_immune: []
# --- Transmission Control ----------------------------------------------------
# Transmission control to investigate the time-dependent influence of actively
# changing decease transmission probabilities. Note that transmission control is
# applied at the beginning of an iteration step after the exposure and immunity
# control. Its effect is seen in the following time step
transmission_control:
enabled: false
# Change the probability to transmit a decease of some randomly
# selected exposed or infected cells.
# The expected value is a list of mappings
# change_p_transmit:
# - time: 10
# num_cells: 6
# cell_kind: susceptible
# p_transmit: 0.5
# - time: 42
# num_cells: 2
# cell_kind: exposed
# p_transmit: 0.
# ... will set p_transmit from the default value to .5 for 6 randomly
# selected susceptible cells at time 10 and set it to 0. for 2 randomly
# selected exposed cells at time 42.
# If num_cells exceeds the current number of present cells with the specified
# kind all of them are chosen to reset their p_transmit value.
# To disable, pass an empty sequence.
change_p_transmit: []
# --- Heterogeneities ---------------------------------------------------------
# Some cells can be permanently infected or turned into inert cells.
# Both these features are using the `select_entities` interface; consult the
# documentation regarding the information on available selection modes.
# Make some cells inert: these do not take part in any of the processes
inert_cells:
enabled: false
mode: clustered_simple
# Clustering parameters
p_seed: .02 # Probability with which a cell is a cluster seed
p_attach: .1 # Attachment probability (per neighbor)
num_passes: 5 # How many attachment procedures to perform
# Set some cells to be permanently infected (invoked after inert cells are set)
infection_source:
enabled: false
mode: boundary
# Boundary selection parameters (requires space to be set to NON-periodic!)
boundary: bottom
# --- Output Configuration ----------------------------------------------------
# Whether to write out spatially resolved data from the CA
# If false, will write only the non-spatial `densities` and `counts` datasets.
# Setting this to false can be useful if no spatial analysis is required or if
# using huge grids.
write_ca_data: true
# HDF5 Compression level for all datasets
# A value of 1-3 is a good default. Choose a lower value if speed is limited by
# the CPU or a higher value if speed is limited by data writing.
compression: 3
Available plots¶
The following plot configurations are available for the SEIRD
model:
Default Plot Configuration¶
# --- Plot of all densities over time -----------------------------------------
densities:
based_on: densities
# --- Counter-based -----------------------------------------------------------
# Transition plots ............................................................
transitions/raw:
based_on: counters
transform:
- &op_sel_transitions
.sel: [!dag_tag d_counts]
kwargs:
label:
- empty_to_susceptible
- living_to_empty
- susceptible_to_exposed_contact
- susceptible_to_exposed_random
- susceptible_to_exposed_controlled
- exposed_to_infected
- infected_to_recovered
- infected_to_deceased
- recovered_to_susceptible
# Define as data to be plotted
- define: !dag_prev
tag: data
kind: line
x: time
col: label
col_wrap: 3
sharey: true
transitions/combined: &transitions_combined
based_on: counters
transform:
- *op_sel_transitions
- define: !dag_prev
tag: data
kind: line
x: time
hue: label
helpers:
set_labels:
y: Counts
transitions/smoothed:
<<: *transitions_combined
transform:
- *op_sel_transitions
# Apply rolling mean over a number of time coordinates
- callattr: [!dag_prev , rolling]
kwargs:
dim:
# NOTE This value should be reduced if writing less frequently
time: &smoothed_by 7
# Configure rolling window such that early values are included
min_periods: 2
center: false
- callattr: [!dag_prev , mean]
# Define as data to be plotted
- define: !dag_prev
tag: data
helpers:
set_labels:
y: !format ["Counts (smoothed over {} data points)", *smoothed_by]
# Movement events .............................................................
movement:
based_on: counters
transform:
- .sel: [!dag_tag d_counts]
kwargs:
label:
- move_randomly
- move_away_from_infected
tag: data
kind: line
x: time
hue: label
helpers:
set_labels:
y: Movement Events
# --- Distribution plots ------------------------------------------------------
age_distribution/final:
based_on: [age_distribution, style.prop_cycle.seird]
transform:
- SEIRD.compute_age_distribution:
age: !dag_tag age
kind: !dag_tag kind
coarsen_by: 10
# Select the last time step
- .isel: [!dag_prev , {time: -1}]
tag: counts
helpers:
set_title:
title: Final Age Distribution
age_distribution/time_series:
based_on: [age_distribution, style.prop_cycle.seird, .animation.ffmpeg]
transform:
- SEIRD.compute_age_distribution:
age: !dag_tag age
kind: !dag_tag kind
coarsen_by: 10
normalize: false # optional. Default: false
tag: counts
# Represent the time dimension as frames of the animation
frames: time
age_distribution/deceased:
based_on: [age_distribution, style.prop_cycle.deceased]
transform:
- SEIRD.compute_age_distribution:
age: !dag_tag age
kind: !dag_tag kind
coarsen_by: 10
compute_for_kinds: [deceased]
- .sum: [!dag_prev , time]
- print: !dag_prev
tag: counts
helpers:
set_title:
title: Deceased Age Distribution
# --- Phase diagrams from two densities ---------------------------------------
phase_diagram/SI:
based_on: phase_diagram
# Select from which densities to create the phase diagram
x: susceptible
y: infected
helpers:
set_labels:
x: Susceptible Density [1/A]
y: Infected Density [1/A]
phase_diagram/SE:
based_on: phase_diagram
x: susceptible
y: exposed
helpers:
set_labels:
x: Susceptible Density [1/A]
y: Exposed Density [1/A]
phase_diagram/EI:
based_on: phase_diagram
x: exposed
y: infected
helpers:
set_labels:
x: Exposed Density [1/A]
y: Infected Density [1/A]
# --- Snapshots and animations of the spatial grid ----------------------------
# NOTE These are both based on the snapshot base plots and add the remaining
# parameters: For snapshots, the time index that is to be plotted; for
# animations, the animation parameters (using multiple inheritance).
# ... The CA ..............................................................
CA_snapshot:
based_on: CA_snapshot
enabled: true
time_idx: -1
CA:
based_on:
- CA_snapshot
- .ca.state.anim_ffmpeg
- animation
# ... The CA age ..........................................................
CA_age_snapshot:
based_on: CA_age_snapshot
enabled: false
time_idx: -1
CA_age:
based_on: [CA_age_snapshot, .ca.state.anim_ffmpeg, animation]
# ... The clusters ............................................................
clusters_snapshot:
based_on: clusters_snapshot
enabled: false
time_idx: -1
clusters:
based_on: [clusters_snapshot, .ca.state.anim_ffmpeg, animation]
# --- Miscellaneous -----------------------------------------------------------
# ... Combined plot of CA states and clusters .............................
CA_and_clusters:
based_on: [CA_snapshot, clusters_snapshot, .ca.state.anim_ffmpeg]
enabled: false
Base Plot Configuration¶
# -- Shared Definitions -------------------------------------------------------
_cmap: &cmap
empty: &color_empty darkkhaki
susceptible: &color_susceptible forestgreen
exposed: &color_exposed darkorange
infected: &color_infected firebrick
recovered: &color_recovered slategray
deceased: &color_deceased black
source: &color_source maroon
inert: &color_inert moccasin
_style:
constrained_layout: &constrained_layout
figure.constrained_layout.use: true
# .. Property cycles ..........................................................
# Using these as base configurations allows to have consistent kind colors
style.prop_cycle.seird:
style:
axes.prop_cycle: !format
fstr: "cycler('color', ['{cmap[susceptible]:}', '{cmap[exposed]:}', '{cmap[infected]:}', '{cmap[recovered]:}', '{cmap[deceased]:}'])"
# NOTE The fstr has to match the order of the compute_for_kinds argument
# of the SEIRD.compute_age_distribution operation
cmap: *cmap
style.prop_cycle.deceased:
style:
axes.prop_cycle: !format
fstr: "cycler('color', ['{cmap[deceased]:}'])"
cmap: *cmap
style.prop_cycle.default:
style:
axes.prop_cycle: &default_prop_cycle "cycler('color', ['1f77b4', 'ff7f0e', '2ca02c', 'd62728', '9467bd', '8c564b', 'e377c2', '7f7f7f', 'bcbd22', '17becf'])"
# default animation configurations
animation:
animation:
enabled: true
writer_kwargs:
frames:
saving:
dpi: 300
ffmpeg:
init:
fps: 8
saving:
dpi: 300
writer: ffmpeg
file_ext: mp4
# -- Any kind of phase plot ---------------------------------------------------
phase_diagram:
based_on: .default_style_and_helpers
creator: universe
universes: all
module: model_plots.SEIRD
plot_func: phase_diagram
cmap: viridis_r
helpers:
set_title:
title: Phase Diagram
set_limits:
x: [0, ~]
y: [0, ~]
# Parameters that are passed on to plt.scatter
s: 10
# -- Densities plot -----------------------------------------------------------
densities:
based_on: .basic_uni.lineplots
model_name: SEIRD
to_plot:
empty:
path_to_data: densities
transform_data:
- sel: { kind: empty }
label: empty
color: *color_empty
susceptible:
path_to_data: densities
transform_data:
- sel: { kind: susceptible }
label: susceptible
color: *color_susceptible
exposed:
path_to_data: densities
transform_data:
- sel: { kind: exposed }
label: exposed
color: *color_exposed
infected:
path_to_data: densities
transform_data:
- sel: { kind: infected }
label: infected
color: *color_infected
recovered:
path_to_data: densities
transform_data:
- sel: { kind: recovered }
label: recovered
color: *color_recovered
deceased:
path_to_data: densities
transform_data:
- sel: { kind: deceased }
label: deceased
color: *color_deceased
source:
path_to_data: densities
transform_data:
- sel: { kind: source }
label: source
color: *color_source
inert:
transform_data:
- sel: { kind: inert }
path_to_data: densities
label: inert
color: *color_inert
helpers:
set_limits:
x: [min, max]
y: [0., 1.]
set_labels:
x: Time [Iteration Steps]
y: Density [1/A]
set_title:
title: Densities
set_legend:
loc: best
# --- Counter-based -----------------------------------------------------------
counters:
based_on:
- .creator.universe
- .dag.generic.facet_grid
select:
counts: counts
d_counts:
path: counts
transform:
- .diff: [!dag_prev , time]
dag_options:
select_path_prefix: data/SEIRD
style:
# Set the default prop cycle; somehow lost for categorical label dimension
axes.prop_cycle: *default_prop_cycle
helpers:
set_limits:
x: [0, max]
y: [0, ~]
# Only set the x labels on the bottom row, as x-axes are shared.
axis_specific:
bottom_row:
axis: [~, -1]
set_labels:
x: Time [Iteration Steps]
# --- Distributions -----------------------------------------------------------
age_distribution:
based_on:
- .default_style_and_helpers
- .creator.universe
- .dag.generic.histogram
select:
age: age
kind: kind
dag_options:
select_path_prefix: data/SEIRD
x: age
hue: kind
helpers:
set_labels:
x: Age
y: Counts
set_title:
title: Age Distribution
# --- Grid Snapshots ----------------------------------------------------------
# NOTE These can also be used as basis for grid animations.
# ... The CA ..................................................................
CA_snapshot:
based_on: .ca.state
model_name: SEIRD
to_plot:
kind:
title: ""
limits: [0, 7]
# Be aware that the colors need to be in the correct ordering!
cmap:
empty: *color_empty
susceptible: *color_susceptible
exposed: *color_exposed
infected: *color_infected
recovered: *color_recovered
deceased: *color_deceased
source: *color_source
inert: *color_inert
style:
<<: [*constrained_layout]
# ... The CA age ..............................................................
CA_age_snapshot:
based_on: .ca.state
model_name: SEIRD
to_plot:
age:
title: Age
cmap: YlGn
# ... The clusters ............................................................
clusters_snapshot:
based_on: .ca.state
model_name: SEIRD
to_plot:
cluster_id:
title: Clusters
limits: [0, 20]
cmap: tab20
no_cbar_markings: true
transform_data:
cluster_id:
- where: ['!=', 0] # 0 is masked: not part of a cluster
- mod: 20 # ... to match the tab20 color map
style:
<<: [*constrained_layout]
For the utopya base plots, see Multiverse Base Configuration.