# Viral Epidemic with Frational Susceptibility Simulation
# (c) 2020 Kevan Hashemi, Brandeis University.
# Set up the graphical user interface.
wm title . "Epidemic Simulation Version 3.2"
set param(run) "Stop"
set g [frame .f1]
pack $g -side top -fill x
label $g.state -textvariable param(run) -fg blue
pack $g.state -side left -expand yes
foreach a {Construct Start Stop Reset Help} {
set b [string tolower $a]
button $g.$b -text $a -command "epidemic_$b"
pack $g.$b -side left -expand yes
}
set g [frame .f2]
pack $g -side top -fill x
foreach a {unexposed unsusceptible infected recovered dead} {
label $g.l$a -text "$a"
entry $g.e$a -textvariable param($a) -width 6
pack $g.l$a $g.e$a -side left -expand yes
}
set g [frame .f3]
pack $g -side top -fill x
foreach a {susceptible_rate contact_rate recover_days death_rate time} {
label $g.l$a -text "$a"
entry $g.e$a -textvariable param($a) -width 4
pack $g.l$a $g.e$a -side left -expand yes
}
set t [text .text -relief sunken -border 2 -setgrid 1 \
-height 20 -width 60 -wrap word]
$t configure -yscrollcommand ".vsb set"
set vsb [scrollbar .vsb -orient vertical -command "$t yview"]
pack $vsb -side right -fill y
pack $t -expand yes -fill both
bind $t [list $t delete 1.0 end]
bind $t [list $t delete 1.0 end]
bind $t [list $t delete 1.0 end]
$t tag configure title -foreground purple
$t tag configure help -foreground brown
update
# Print title.
$t insert end "Viral Epidemic with Fractional Susceptibility Simulation" title
$t insert end "25-MAR-20, Kevan Hashemi, Brandeis University.\n" title
# The reset routine goes back to the no-infected state.
proc epidemic_reset {} {
global param
set param(unexposed) "1000000"
set param(unsusceptible) "0"
set param(infected) "100"
set param(recovered) "0"
set param(dead) "0"
set param(contact_rate) "5"
set param(susceptible_rate) "0.04"
set param(recover_days) "14"
set param(death_rate) "0.01"
set param(time) "0"
epidemic_construct
}
# The print routine prints time and the population distribution.
proc epidemic_print {} {
global param t population
set n [llength $population]
$t insert end "$param(time) "
foreach a {unexposed unsusceptible infected recovered dead} {
$t insert end "[format %.3f [expr 100.0*$param($a)/$n]] "
$t yview moveto 1
}
$t insert end "\n"
}
# The population list, each entry contains two numbers. The first
# is the number of days infected, which is zero until infected,
# then increases each day to recover_days, then stops, to indicate
# recovery.
set population [list]
# We construct a new array. All infected will be set to the first
# day of infection. Each member of the population gets a number
# to describe their state. Zero means they are unexposed, and their
# susceptibility is as yet unknown. -1 means they have been exposed
# and are unsusceptible. 1..n means they have been infected for 1..n
# days. When n = recover_days, they are recovered. When n = recover
# days + 1, they are dead.
proc epidemic_construct {} {
global param population t
if {$param(run) != "Stop"} {return}
set param(run) "Init"
update
set population [list]
for {set i 0} {$i < $param(unexposed)} {incr i} {
lappend population "0"
}
for {set i 0} {$i < $param(unsusceptible)} {incr i} {
lappend population "-1"
}
for {set i 0} {$i < $param(infected)} {incr i} {
lappend population "1"
}
for {set i 0} {$i < $param(recovered)} {incr i} {
lappend population "$param(recover_days)"
}
for {set i 0} {$i < $param(dead)} {incr i} {
lappend population "[expr $param(recover_days)+1]"
}
set param(time) "0"
set param(run) "Stop"
$t insert end "\nNew Distribution Constructed:\n" title
epidemic_print
}
# We stop the simulation by setting the run flag to Stop, which will bring
# the run procedure to a stop.
proc epidemic_stop {} {
global param
set param(run) "Stop"
}
# The start procedure starts the simulation.
proc epidemic_start {} {
global param
if {$param(run) != "Stop"} {return}
set param(run) "Run"
after 10 epidemic_run
}
# The run procedure goes through all entries in the population list.
# If currently infected, increment the entry value, and at random select
# member of population for exposure. If that person is unsusceptible, do
# nothing, if that person is unexposed so far, determine susceptibility.
# If susceptible, start infection. If unsusceptible, mark as such.
# At the end of the day, update the numbers of various subsets of the
# population, then posts itself to the event queue.
proc epidemic_run {} {
global param population t
if {$param(run) != "Run"} {return}
if {![winfo exists $t]} {return}
set n [llength $population]
for {set i 0} {$i < $n} {incr i} {
set p [lindex $population $i]
# Uninfected, unsusceptible, recovered, or dead people don't infect anyone.
if {($p == 0) || ($p < 0) || ($p >= $param(recover_days))} {
continue
}
# Those infected can expose another person. If that person is still
# among those who have not yet been exposed, we determine now if
# they are susceptible. If susceptible, infect them, otherwise mark
# tham as unsusceptible.
if {$p > 0} {
set cr $param(contact_rate)
while {$cr > 0} {
if {rand() < $param(contact_rate)} {
set ii [expr round(rand()*$n-0.5)]
if {$ii>$n} {set ii $n}
if {([lindex $population $ii] == 0)} {
if {rand() < $param(susceptible_rate)} {
lset population $ii "1"
} else {
lset population $ii "-1"
}
}
}
set cr [expr $cr - 1]
}
set d [expr $p + 1]
if {$d == $param(recover_days)} {
if {rand() <= $param(death_rate)} {
incr d
}
}
lset population $i $d
}
}
# Update our counts of the various subsets of the population.
foreach a {unexposed unsusceptible infected recovered dead} {
set $a 0
}
foreach p $population {
if {$p > $param(recover_days)} {
incr dead
} elseif {$p == $param(recover_days)} {
incr recovered
} elseif {$p > 0} {
incr infected
} elseif {$p == 0} {
incr unexposed
} else {
incr unsusceptible
}
}
foreach a {unexposed unsusceptible infected recovered dead} {
set param($a) [set $a]
}
incr param(time)
epidemic_print
after 10 epidemic_run
}
proc epidemic_help {} {
global t
$t insert end {
Summary: Set population sizes and parameter values, press Construct, press
Start. Simulation runs until epidemic is over, or you press Stop.
The simulation assumes only one virus strain and a fixed contact rate between
infected persons and randomly-selected other persons to determine exposure. It
allows for a fraction of the population to be unsusceptible to the virus, which
is to say they will not contract the virus, or will kill it so quickly they will
never be able to expose anyone else to the virus, nor will they get sick. Those
who are infected can expose others, but those who have recovered are no longer
infectious. Some fraction of those who have been infected for recover_days die,
and the rest survive. The contact rate is the number of people each infected
person exposes per day. If this number is less than one, they sometimes infect
people, and sometimes do not. If the rate is greater than one, we treat any
fraction as a probability. The susceptible rate is the probability that a person
exposed for the first time will turn out to be susceptible to the virus. We
press Construct to create the population database, then Start to run the
simulation, Stop to pause it. We get the default values of all the above numbers
with Reset. When there are no more infected people, the simulation stops.
The simulation printes to the screen the day number, and the numbers of:
unexposed, unsusceptible, infected, and recovered people. These numbers are
intended for cut and past into spreadsheets for plotting. These same numbers are
displayed in their entry boxes at the top of the window.
Kevan Hashemi, 25-MAR-20
} help
}
epidemic_reset