Visualizations

The human eye is a powerful tool for pattern recognition. Watching a simulation unfold can therefore be more enlightening than just looking at final plots. epipack comes with a light-weight visualization framework that shows an animation of a simulation of epipack.stochastic_epi_models.StochasticEpiModel that can be found in epipack.vis().

Basics

A visualization can be started using epipack.vis.visualize(). A model object is passed to this function, alongside a stylized network and a time delta which represents the amount of simulation time that is supposed to pass between two consecutive visualization updates. Optionally, a config dictionary can be passed.

The function then opens a window that comprises three basic elements:

  1. The visualization. Here, the network is displayed including links and after each update, colors are updated according to node status changes. Links are switched off if nodes are quarantined. If you want to use this functionality, pass the argument quarantine_compartments (as a list of compartments that are considered quarantined).

  2. Plots. Here, the counts of each compartment are drawn as time series. If you do not want to use this functionality, turn it off with config['show_curves'] = False Additionally, you can ignore to plot compartment curves by setting plot_ignore_compartments = [ "S", ...]

  3. Legend. A color-coded list of compartment names. Optionally, you can turn it off using config['show_curves'] = False.

You can customize many aspects of the visualization by adjusting the corresponding entry in the config dictionary. The default config dictionary is

_default_config = {
            'plot_sampled_curve': True,
            'draw_links':True,
            'draw_nodes':True,
            'n_circle_segments':16,
            'plot_height':120,
            'bgcolor':'#253237',
            'curve_stroke_width':4.0,
            'node_stroke_width':1.0,
            'link_color': '#4b5a62',
            'node_stroke_color':'#000000',
            'node_color':'#264653',
            'bound_increase_factor':1.0,
            'update_dt':0.04,
            'show_curves':True,
            'draw_nodes_as_rectangles':False,
            'show_legend': True,
            'legend_font_color':None,
            'legend_font_size':10,
            'padding':10,
            'compartment_colors':_colors,
            'palette': "dark",
        }

Note that you can interact with the visualization up to a certain level:

  1. Zooming: Use your mouse to scroll

  2. Panning: Use your mouse to drag

  3. Pausing: "SPACE" key

  4. Increasing the sampling time delta: repeatedly pressing the "UP" key

  5. Decreasing the sampling time delta: repeatedly pressing the "DOWN" key

Network Layout

We will use the "Social circles: Facebook" data from SNAP in the following. First, download the data. In the console, do

wget --no-check-certificate https://snap.stanford.edu/data/facebook_combined.txt.gz
gunzip facebook_combined.txt.gz

which downloads and extracts the combined dataset. Now, epipack expects a stylized network like it's produced by Netwulf. Style the network manually like so

import numpy as np
import networkx as nx
import netwulf as nw

# load edges from txt file and construct Graph object
edges = np.loadtxt('facebook_combined.txt')
G = nx.Graph()
G.add_edges_from(edges)

# visualize and save visualization
network, config = nw.visualize(G)
nw.save("FB.json",network,config)

Now you can simply load the stylized network every time you need it.

Let's use this network style to simulate an SIR model that has an additional "X" compartment for quarantine of symptomatic individuals.

import netwulf as nw

from epipack.vis import visualize
from epipack import StochasticEpiModel

# load network
network, config, _ = nw.load('/Users/bfmaier/pythonlib/facebook/FB.json')

# get the network properties
N = len(network['nodes'])
edge_list = [ ( link['source'], link['target'], 1.0 ) for link in network['links'] ]

# define model
model = StochasticEpiModel(list("SIRX"),
                           N=N,
                           edge_weight_tuples=edge_list,
                           )
k0 = model.out_degree.mean()
R0 = 5
recovery_rate = 1/8
quarantine_rate = 1.5 * recovery_rate
infection_rate = R0 * (recovery_rate) / k0

# usual infection process
model.set_link_transmission_processes([
        ("I","S",infection_rate,"I","I")
    ])

# standard SIR dynamic with additional quarantine of symptomatic infecteds
model.set_node_transition_processes([
        ("I",recovery_rate,"R"),
        ("I",quarantine_rate,"X"),
    ])

# set initial conditions with a small number of infected
model.set_random_initial_conditions({'I':20,'S':N-20})

# in every step of the simulation/visualization, let a time of `sampling_dt` pass
sampling_dt = 0.12

# simulate and visualize, do not plot the "S" count,
# and remove links from nodes that transition to "X"
visualize(model,
          network,
          sampling_dt,
          ignore_plot_compartments=['S'],
          quarantine_compartments=['X'],
          )

And this is the result:

Grid Layout

Sometimes, the positions of a network are not important. If this is the case, you can simply use a grid layout for the network. You can load the corresponding layout like so:

from epipack.vis import get_grid_layout

layout = get_grid_layout(number_of_nodes,windowwidth=400)

which will produce a window of width 400px. For such a layout, it's recommended to draw nodes as rectangles. You can do this by calling the visualize function with config['draw_nodes_as_rectangles'] = True.

You could use this, for instance, to animate a well-mixed system like so

from epipack.vis import visualize, get_grid_layout
from epipack import StochasticSIRModel

# get the layout
N = 100 * 100
layout = get_grid_layout(N)

# define model
R0 = 3
recovery_rate = 1/8
model = StochasticSIRModel(N,R0,recovery_rate)
model.set_random_initial_conditions({'I':20,'S':N-20})

# start visualization where the "S" count won't be shown
sampling_dt = 0.5

visualize(model,network,sampling_dt,
          ignore_plot_compartments=['S'],
          config={'draw_nodes_as_rectangles':True}
          )

Which yields

Lattice simulation

The grid layout can also be more than just showing a well-mixed simulation. It is the natural representation of a lattice. In order to simulate on a 2D lattice, construct the stochastic model with lattice links that you can get from epipack.networks.get_2D_lattice_links().

from epipack import get_2D_lattice_links

N_side = 100
N = 100**2
links = get_2D_lattice_links(N,periodic=True,diagonal_links=True)

R0 = 3; recovery_rate = 1/8
model = StochasticSIRModel(N,R0,recovery_rate,
                           edge_weight_tuples=links)

This will produce a lattice network with periodic boundary conditions where nodes are connected to their diagonal neighbors, as well.

In a simulation, make sure to not draw links because they won't be visible anyway, i.e.

config['draw_links'] = False

The complete visualization code:

from epipack.vis import visualize, get_grid_layout
from epipack import StochasticSIRModel, get_2D_lattice_links

# define links and network layout
N_side = 100
N = N_side**2
links = get_2D_lattice_links(N_side, periodic=True, diagonal_links=True)
network = get_grid_layout(N)

# define model
R0 = 3; recovery_rate = 1/8
model = StochasticSIRModel(N,R0,recovery_rate,
                           edge_weight_tuples=links)
model.set_random_initial_conditions({'I':20,'S':N-20})

sampling_dt = 1

visualize(model,network,sampling_dt,
        config={
             'draw_nodes_as_rectangles':True,
             'draw_links':False,
           }
      )

with result: