Spatially-aware virus transmission

Spatially-aware virus transmission preview image

1 collaborator

Me Ian Heath (Author)


covid-19, epidemiology 

Tagged by Ian Heath over 3 years ago


Tagged by Ian Heath over 3 years ago

virus spread 

Tagged by Ian Heath over 3 years ago

Visible to everyone | Changeable by the author
Model was written in NetLogo 6.1.1 • Viewed 240 times • Downloaded 18 times • Run 0 times
Download the 'Spatially-aware virus transmission' modelDownload this modelEmbed this model

Do you have questions or comments about this model? Ask them here! (You'll first need to log in.)


This model simulates the spread of an infectious disease in a closed population.

It diverges from the classical Kermack-McKendrick model, which we henceforth call the Standard model, in that it takes account of spatial proximity on transmission. It is novel in that it doesn't model the movement of persons but instead assumes that transmission is between static households, and that households can transmit at a distance. Of course in reality, the way this remote transmission takes place is by the movement of persons from the households. However, we don't need to model this movement. We simply assume that transmission is between static households and that it is more likely to be between nearer households. For simplicity, every patch is a household.

This proximity effect is determined by a Reach parameter which is the average distance of transmission to other households. For example, a Reach of 1% means that transmissions are to households that are at a 1% radius of the transmitter, on average. The households at 1% radius are those on the boundary of the 1% of all households that are closest to the transmitter. Note that this 1% radius is only an average. The actual transmission radius is a random-Poisson of the 1% radius.

This is a simple model that is designed only to show the Reach-effect on the spread of infection. It is for experimental purposes only, so that users can see how the Reach% compresses and depresses the spread of the infection and how it differs significantly from the non-spatial Standard model.

I have avoided using the standard term R_0 as it is often misused and could be confused with some of my terms relating to the Removeds measure. Instead I use the term Transmission# (also abbreviated to T#) as I think this is much more self-descriptive.

There are 3 main parameters that can be experimented with using the sliders:

  • generation-perHousehold
  • meanReach%, and
  • Transmission#

The first, generation-perHousehold, requires a little explanation. It means the average number of days from first infection in a household till the day it reinfects other households. This is complicated as there will sometimes be a delay from the first infection in a household and any reinfection of other members in the household who will in turn elongate the total reinfection interval of the household. I have assumed a default which is my guess for Covid-19 and have assumed that secondary infections within the household have little effect on the household reinfection interval. In terms of the Standard Model it equals 1/gamma.


Initially, the household at the centre of the World is infected. This household transmits an average T# times at the end of its reinfection interval to other nearby households and then becomes Removed (i.e. recovered or deceased). Infected households, in turn do the same. Thus all households transmit at the end of their reinfection interval, making the model temporally-aware as well as spatially-aware. Temporal awareness only makes a difference because the model is spatially-aware. Restricting transmission to the end of the reinfection interval ensures that the oldest (and hence the less-distributed) households transmit each day. This transmission by the oldest infected is implemented using a queueing system for infected households.

The state of each household is shown by its colour:

  • red for the Infectious
  • green for the Removed
  • black for the still Susceptible

The %Infectious and the %Removed is plotted by day. Examination of the %Infectious plot shows it is approximately the shape of a Normal-distribution curve. It is therefore overlaid with a fitted Normal-distribution curve and the %Removed curve is overlaid with the corresponding fitted curve that is gamma * (the daily accumulated fitted %Infectious). You can see that they are a very good fit.

The reason why the Normal-distribution curve is a better fit than the Standard-model curves is because infection is suppressed by the Reach-effect and the Standard-model does not take this into account. Also, the Standard-model %Infectious curves show a positive-skew (increasing markedly with T# above 4). Whereas, the Reach-model shows very little skew in the Reach% range of 0.6% to 1.4%, a slight negative-skew when Reach% < 0.6% and a slight positive-skew when Reach% > 1.4%. I suspect that this could be corrected by introducing a skew coefficient into the Random-distribution formula.


First, run it with the "view updates" on and observe how the red and green household dots spread through the World, eventually leaving just completely-randomised green households. However, this slows down the model and you will soon want to speed up the runs and plotting by unclicking "view updates" in the Interface ribbon.

As per usual, you can adjust the input-parameter sliders then click the Setup button and then click the Go-forever button (or you can click the Go botton to run single daily steps).

However, you can just click the "run current Transmission#" button to do both. Alternatively, you can click the "run the next Transmission#" button to step-up to the next Transmission# and automatically run that. This latter button is a great-way of observing dynamically how the increasing Transmission# affects the plots.

You can observe how good the Normal-curve fit is. This fit will vary on repeated runs due to stochastic noise and occasionally you will observe an almost perfect fit.

The other item that you may want to vary is the number of households. The default is 250,000 for a World that is 500 by 500. The only way to change this is to resize the World which you can do by clicking Settings on the Interface ribbon.For example, you can set a World that is 1000 by 1000 to produce a number of households (N) of 1 million. I have tested this up to 20 million households, which equates to an approximate population of 50 million persons. Beware though that changing to a bigger World size can be slow and it slows down the run time considerably.


If you mention this model or the NetLogo software in a publication, we ask that you include the citations below.

For the model itself:

  • Ian J Heath, the author, an independent researcher in Canterbury, UK

For the NetLogo software:

Comments and Questions

Please add your thoughts on this model here (Question)

I would love to see any comments.

Posted over 3 years ago

Click to Run Model

globals [
  #I           ;; # of Infectious     - red
  #R           ;; # of Removeds       - green   (note: not exactly the integral of #I)
  nI           ;; fractional I for the fitted Normal curve
  nR           ;; fractional R for the fitted Normal curve
  rR0          ;; to align Normal-R with Reach-R at liftoff
  RedsQ        ;; list of red patches - to remove the oldest first
  T#           ;; # of transmissible (i.e. always infects susceptibles) contacts per household
  meanReach    ;; mean infection distance  = radius that spans meanReach% of HHs
  gamma        ;; the removal rate per day for infectious households
  N            ;; # households  (that fill the whole World)
  #Imax        ;; peak #I
  ImaxDay      ;; peak #I day
  Istdev       ;; standard deviation (width of the "bell") for a Normal curve that best fits the Proximity model I-curve
  Ilist        ;; list of daily #Is - used to calc VAR of the fitted I curve
  plottingReach;; a switch to turn this off when plotting over it
  liftoff      ;; switch = (#I / N > .001)
  day          ;; for plotting Normal curves

to runCurrentT#  ;; avoid having to setup first
  while [not stopGos] [go]  ;; run for current T#

to runNextT#     ;; to dynamically show the effect of stepping up through each T#
  set Transmission#  precision (Transmission# + 0.1) 5   ;; Increment the T#  (note: precision is to correct an anomylous addition error)

to setup
  set stopGos   false       ;; enable resumption of go's for this Transmission# - used in runCurrentTransmission#

  ;; setup the Reach model
  set N          count patches
  set T#         Transmission#                ;; the T# abbreviation will be used throughout this model
  set gamma      1 / generation-perHousehold  ;; removeds per day per infectious HH
  set meanReach  sqrt(meanReach% / 100 * N / pi)
  set RedsQ      []
  ask patch      (world-width / 2) (world-width / 2) [turn-infectious]            ;; infect the origin
  set #I         1
  set #R         0
  set #Imax      #I
  set Ilist      []

  ;; LIFTOFF - startup until liftoff (when #I >= N * .001).  During liftoff "infect&remove" avoids accidentally exhausting all infections.
  set liftoff  false                                                              ;; start liftoff
  while [#I < N * .001] [goReachModel]                                            ;; run till liftoff
  set liftoff  true                                                               ;; achieved liftoff
  set rR0  #R / N                                                                 ;; to align Normal-R with Reach-R at liftoff in plotNORMdistFit
  set plottingReach true                                                          ;; enable plot of Reach curves until we plot Normal curves over the top of them


to go
  ;; tally Imax and ImaxDay
  if #I > #Imax  [
    set #Imax    #I
    set ImaxDay  ticks + 1

  ;; stop when I is passed its peak and nearly exhausted
  if (ticks > ImaxDay) and (#I <= N * .0001) [
    print (word "N=" N  ", genDays=" generation-perHousehold ", Reach=" meanReach%  "%, T#=" PadRight T# 3  ", ImaxDay=" PadRight ImaxDay 4 ", %Imax=" precision (#Imax / N * 100) 2 ", %R=" precision (#R / N * 100) 1)
    set stopGos true                                                              ;; tell runNextTransmission# to stop go's

  tick                                                                            ;; advance to next day

to goReachModel
  ;; step the proximity model today. Transmit then remove the oldest reds, according to the removal rate, gamma
  set Ilist  lput #I  Ilist                                                       ;; append #I to Ilist
  if #I = 0 [stop]
  let #newR  max list 1 round(gamma * #I)                                         ;; the number of oldest reds - to transmit and then remove
  foreach   (sublist RedsQ 0     #newR         ) [HH -> ask HH [infect&remove]]  ;; transmit these oldest reds (those at the front of the queue), which adds new reds to the back of the queue
  set RedsQ (sublist RedsQ #newR (length RedsQ))                                  ;; drop these oldest reds from the front of the list, now that they have been processed

to infect&remove  ;; infect and then remove
  let #Contacts  ifelse-value liftoff [random-poisson     T#       ] [1 + random-poisson     (T# - 1)       ]
  let Reach      ifelse-value liftoff [random-exponential meanReach] [1 + random-exponential (meanReach - 1)]

  repeat #Contacts [ ;; make a transmissible contact at distance random-exponential of meanReach
    ask patch-at-heading-and-distance (random 360) Reach [if pcolor = black [turn-infectious]]
  turn-removed                                                                    ;; remove this HH, now that it has transmitted

to turn-infectious    ;; turn into an "infectious"
  set RedsQ  lput self RedsQ                                                      ;; put new red at the back of the queue
  set pcolor red      ;; set infectious
  set #I  #I + 1

to turn-removed       ;; turn into a "removed"
  set pcolor green    ;; set removed
  set #I  #I - 1
  set #R  #R + 1

to-report PadRight [number to-length]  ;; to align decimal columns in output
  report substring (word number "             ") 0 to-length

to plotNORMdistFit
  ;; plot nI (the Normal curve that fits the Reach I curve) and nR (the R curve that is the accumulation of nI * gamma), alongside the Reach I and R curves

  set plottingReach false   ;; to cease plotting the Reach curves while plotting the Normal curves over the Reach curves

  ;; plot the nI and nR plots
  let Imax    #Imax / N
  let cumI    #R / gamma                                                          ;; cumI = the integral of the Reach #I-curve
  set Istdev  cumI / (#Imax * sqrt(2 * pi))

  set nR   rR0                                                                    ;; to align nR with rR at liftoff

  set day  (- 1)
  set day  0
  repeat ticks + 1 [
    set day  day + 1
    set nI   Imax * exp(-(((day - ImaxDay)/ Istdev)^ 2)/ 2)                       ;; the Normal distribution with this ImaxDay and Istdev
    set nR   nR + nI * gamma

There are 2 versions of this model.

Uploaded by When Description Download
Ian Heath over 3 years ago Slight editing of the Info page Download this version
Ian Heath over 3 years ago Initial upload Download this version

Attached files

File Type Description Last updated
Spatially-aware virus transmission.png preview Plots showing good fits over 3 years ago, by Ian Heath Download

This model does not have any ancestors.

This model does not have any descendants.