# Epidemic Simulation
# (c) 2020 Kevan Hashemi, Brandeis University.
# Set up the graphical user interface.
wm title . "Epidemic Simulation Version 1.1"
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 {uninfected_nonisolating infected_nonisolating recovered_nonisolating} {
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 {uninfected_isolating infected_isolating recovered_isolating} {
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 .f4]
pack $g -side top -fill x
foreach a {nonisolating_contact_rate isolating_contact_rate recover_days 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
# The reset routine goes back to the no-infected state.
proc epidemic_reset {} {
global param
set param(uninfected_nonisolating) "9000"
set param(infected_nonisolating) "1"
set param(recovered_nonisolating) "0"
set param(uninfected_isolating) "1000"
set param(infected_isolating) "0"
set param(recovered_isolating) "0"
set param(nonisolating_contact_rate) "0.25"
set param(isolating_contact_rate) "0.025"
set param(recover_days) "14"
set param(time) "0"
epidemic_construct
}
# The print routine prints time and the population distribution.
proc epidemic_print {} {
global param t
foreach a {time uninfected_nonisolating infected_nonisolating recovered_nonisolating\
uninfected_isolating infected_isolating recovered_isolating} {
$t insert end "$param($a) "
$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.
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(uninfected_nonisolating)} {incr i} {
lappend population "0 $param(nonisolating_contact_rate)"
}
for {set i 0} {$i < $param(infected_nonisolating)} {incr i} {
lappend population "1 $param(nonisolating_contact_rate)"
}
for {set i 0} {$i < $param(recovered_nonisolating)} {incr i} {
lappend population "$param(recover_days) $param(nonisolating_contact_rate)"
}
for {set i 0} {$i < $param(uninfected_isolating)} {incr i} {
lappend population "0 $param(isolating_contact_rate)"
}
for {set i 0} {$i < $param(infected_isolating)} {incr i} {
lappend population "1 $param(isolating_contact_rate)"
}
for {set i 0} {$i < $param(recovered_isolating)} {incr i} {
lappend population "$param(recover_days) $param(isolating_contact_rate)"
}
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 and
# determines if they get infected that day. At the end of the day, it
# updates 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 state [lindex $population $i 0]
set prob [lindex $population $i 1]
if {$state == 0} {
# This member has a chance of making contact with another
# member of the population today, and perhaps being
# infected.
if {rand() <= $prob} {
set nn [expr round(rand()*$n-0.5)]
if {$nn>$n} {set nn $n}
if {([lindex $population $nn 0] > 0) \
&& ([lindex $population $nn 0] <= $param(recover_days))} {
lset population $i "1 $prob"
}
}
} elseif {$state <= $param(recover_days)} {
# This member of population is recovering and is contageous
# but cannot be re-infected. We increment the number of days
# of recovery.
incr state
lset population $i "$state $prob"
} else {
# This member of population has recovered and is assumed to
# be immune to the disease.
}
}
foreach a {uninfected_nonisolating infected_nonisolating recovered_nonisolating \
uninfected_isolating infected_isolating recovered_isolating} {
set $a 0
}
foreach p $population {
set state [lindex $p 0]
set prob [lindex $p 1]
if {$prob == $param(nonisolating_contact_rate)} {
if {$state == 0} {
incr uninfected_nonisolating
} elseif {$state <= $param(recover_days)} {
incr infected_nonisolating
} else {
incr recovered_nonisolating
}
} else {
if {$state == 0} {
incr uninfected_isolating
} elseif {$state <= $param(recover_days)} {
incr infected_isolating
} else {
incr recovered_isolating
}
}
}
foreach a {uninfected_nonisolating infected_nonisolating recovered_nonisolating \
uninfected_isolating infected_isolating recovered_isolating} {
set param($a) [set $a]
}
incr param(time)
epidemic_print
if {$infected_nonisolating + $infected_isolating == 0} {
$t insert end "Done: no more infected people.\n" title
set param(run) "Stop"
} else {
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 divides a population into two sub-populations: those who do not
isolate themselves from other members of the population, and those who do
isolate themselves. For each of sub-population, we have the number of
uninfected, infected, and recovered. We set these values, and the contact rate
for both sup-populations, and the number of days to recover, in entry boxes. 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 proceeds in steps of length one day. Each day, every member of
the population has a chance of making contact with another member of the
population in such a way as to receive the infection if the person contacted is
currently infected with the disease. We have two probabilities of such contact
taking place each day, one for the non-isolating sub-population, another for the
isolating sub-population. Each day, the simulation goes through the entire
population, isolating and non-isolating, and determines what happens to each of
them. If they are infected, they move one day closer to recovery, but remain
infected and contageous. If they are recovered, they remain recovered. They
neither infect nor can they be infected. If they have never been infected, there
is a chance they will be infected. If person A has never been infected, the
simulation determines at random if a potentially infecting contact has taken
place with another person B. This probability is the "contact rate". We have one
contact rate for the isolating sub-population, and another for the non-isolating
sub-population. If a contact has taken place, the simulation picks a person B at
random from the population for the contact. If this person is infected and not
recovered, A now becomes infected, and will remain infected for "recovery days".
The simulation printes to the screen the day number, and the numbers of:
uninfected non-isolating, infected non-isolating, recovered non-isolating,
uninfected isolating, infected isolating, and recovered isolating. These numbers
are intended for cut and past into spreadsheets for plotting. These same numbers
are displayed in their entry boes at the top of the window.
The default values we obtain with Reset are supposed to represent the
introduction of one person infected with coronavirus into a population of 10000,
of whome 9000 don not isolate themselves, and 1000 work very hard to isolate
themselves from others. The contact rate for the non-isolating gives a doubling
of the number of infected people every four days at the start of the epidemic,
which is consistent with the spread of the coronavirus through Italy in February
and March 2020.
The simulation assumes that the isolating population is much smaller than the
non-isolating population, because it ignores the contact rate of person B when
considering if person A is infected by person B. The assumption is that almost
all the time, person B is from the non-isolating population.
Kevan Hashemi, 15-MAR-20
} help
}
epidemic_reset