Agents
Agents
Each agent in the model represents a person with their own individual attributes. For the simplest version of this model we give these people the following attributes:
Position: Their location in 2D space, given as an {x,y} pair.
Direction: Their direction of travel in space, given as a number from 0 to 2π
Status: Their disease status, given as either “Immune”, “Vulnerable” or a number which indicates that they are infected. The value of the number describes how long they will remain infected.
Isolated: A number to indicate their isolation status. Positive number indicates the time remaining until they self-isolate. Negative indicates they are isolated. Isolation means that the person no longer moves around, though they can still be visited by other people who have not self-isolated.
Position: Their location in 2D space, given as an {x,y} pair.
Direction: Their direction of travel in space, given as a number from 0 to 2π
Status: Their disease status, given as either “Immune”, “Vulnerable” or a number which indicates that they are infected. The value of the number describes how long they will remain infected.
Isolated: A number to indicate their isolation status. Positive number indicates the time remaining until they self-isolate. Negative indicates they are isolated. Isolation means that the person no longer moves around, though they can still be visited by other people who have not self-isolated.
We start by creating a function to generate an initial random population of n agents. The agents start at random positions within a square space with a random initial direction.
In[]:=
initialize[n_(*Sizeofpopulation*),diseased_,immune_,recoveryTime_,isolationRate_,delay_,space_]:= Map[Join[#,<| "Position"RandomReal[space,{2}], "Direction"RandomReal[2Pi], "Isolated"RandomChoice[{isolationRate,1-isolationRate}{delay,Infinity}]|> ]&, Join[ Table[<|"Status"recoveryTime|>,{diseased}], Table[<|"Status""Immune"|>,{immune}], Table[<|"Status""Vulnerable"|>,{n-diseased-immune}]] ];
For example, here is a population of 10 people with 1 diseased and 25% set to self-isolate in 5 days.
In[]:=
testPopulation=initialize[10(*populationsize*),1(*diseased*),0(*immune*),20.(*recoverytime*),0.25(*isolationrate*),5(*isolationdelay*),20(*spaceavailable*)]
Out[]=
{Status20.,Position{10.3648,18.5176},Direction1.56859,Isolated5,StatusVulnerable,Position{15.6157,19.0584},Direction0.412972,Isolated∞,StatusVulnerable,Position{12.22,3.20619},Direction5.67977,Isolated∞,StatusVulnerable,Position{1.33157,3.86196},Direction2.94968,Isolated∞,StatusVulnerable,Position{4.86843,11.209},Direction1.63861,Isolated∞,StatusVulnerable,Position{6.13558,17.4433},Direction6.17746,Isolated∞,StatusVulnerable,Position{17.8775,2.29511},Direction3.57282,Isolated∞,StatusVulnerable,Position{8.11507,19.5828},Direction4.36879,Isolated∞,StatusVulnerable,Position{6.82111,19.8707},Direction3.26842,Isolated∞,StatusVulnerable,Position{0.232099,11.1059},Direction2.73981,Isolated∞}
Updating the agents
Updating the agents
At each time step we must update each agent’s properties according to some rules:
1) The agent must change position according to their Direction.
2) If the agent is infected, the time remaining for their infection (Status) must count down.
3) The agents Direction is updated. For this model we will assume small random changes of direction - a purposeless meander.
4) The agent’s Isolation status is updated. If the agent is waiting for self-isolation the timer must count down, but in this model we also assume that if they are already self-isolated there is a chance that they abandon self-isolation depending on a Patience parameter.(See below)
2) If the agent is infected, the time remaining for their infection (Status) must count down.
3) The agents Direction is updated. For this model we will assume small random changes of direction - a purposeless meander.
4) The agent’s Isolation status is updated. If the agent is waiting for self-isolation the timer must count down, but in this model we also assume that if they are already self-isolated there is a chance that they abandon self-isolation depending on a Patience parameter.(See below)
In[]:=
updateAgent[agent_Association,timeStep_Real,patience_Real]:= Joinagent, <|"Position"->If[agent["Isolated"]≤0,agent["Position"],agent["Position"]+timeStep{Cos[agent["Direction"]],Sin[agent["Direction"]]}], "Status"Which[NumberQ[agent["Status"]]&&agent["Status"]<=0,"Immune",NumberQ[agent["Status"]],agent["Status"]-timeStep,True,agent["Status"]], "Direction"agent["Direction"]+RandomReal[timeStep{-0.1,0.1}]; "Isolated"Ifagent["Isolated"]≤0&&RandomReal[]<1-,Infinity,agent["Isolated"]-timeStep |>
-
timeStep
patience
2
In[]:=
updateAgent["Status"20,"Position"{10.3,11.2},"Direction"1.973`,"Isolated"10,0.1(*timeStep*),10.(*patience*)]
Out[]=
Status19.9,Position{10.2609,11.292},Direction1.973,Isolated9.9
We assume that the agents are in a constrained space, so after updating the position must check if they are leaving this space. If the agent is outside of the box, change their direction so that they return to it on the next update.
In[]:=
bounceAgent[agent_,space_]:= Which[ agent["Position"][[1]]>space,Append[agent,"Direction"RandomReal[{Pi/2,3Pi/2}]], agent["Position"][[1]]<0,Append[agent,"Direction"RandomReal[{-Pi/2,Pi/2}]], agent["Position"][[2]]>space,Append[agent,"Direction"RandomReal[{Pi,2Pi}]], agent["Position"][[2]]<0,Append[agent,"Direction"RandomReal[{0,Pi}]], True,agent];
For example:
In[]:=
bounceAgent[<|"Position"{20.01,0.5},"Direction"0|>,20]
Out[]=
Position{20.01,0.5},Direction1.86937
All these updates can be performed on each agent in isolation. The final update must consider the population as a whole. We assume that if a vulnerable agent touches an infected agent (defined as a EuclideanDistance <= 1) then the vulnerable agent is immediately infected for a fixed period.
In[]:=
infectPopulation[population_List,recoveryTime_]:= Block[infected, (*Forefficiencysplitthepopulationintothreegroups*) vulnerable=Select[population,#Status=="Vulnerable"&], immune=Select[population,#Status=="Immune"&], infected=Complement[population,immune,vulnerable]; Join[ immune, infected, Map[(*Foreachvulnerableperson*) Function[{v},(*passoninfection*) With[{pos=v["Position"]}, If[Min[EuclideanDistance[pos,#]&/@infected[[All,"Position"]](*Smallestdistancetoaninfectedagent*)]<1, Append[v,"Status"recoveryTime], v] ]], vulnerable]] ];
We now create a function to perform all of these updates for the entire population:
In[]:=
updatePopulation[population_,timeStep_,recoveryTime_,patience_,space_]:= infectPopulation[ bounceAgent[ updateAgent[#,timeStep,patience], space]&/@population, recoveryTime];
Agent patience
Agent patience
We assume that patience is randomly distributed with a decision made at each time-step on whether to abandon isolation. The half-life of the population remaining in isolation is set with the patience parameter. In order to calculate the individual decision probabilities we must solve the following equation
In[]:=
Quiet@FullSimplify[Assuming[{timeStep>0,patience>0,0<p<1},Solve[Probability[t>0,tBinomialDistribution[patience/timeStep(*Numberofdecisions*),p]]1/2,p]]]
Out[]=
p1-
-
timeStep
patience
2
Analyzing behaviour
Analyzing behaviour
Now that we can simulate the behaviour of our agents, we need to collect data from experiments for each time-step we are interested in counting the number of agents in each state:
In[]:=
countStates[population_List]:=Counts[ReplaceAll[population[[All,"Status"]],_Real"Infected"]]
For example:
In[]:=
countStates[testPopulation]
Out[]=
Infected1,Vulnerable9
We now build an experiment by generating a population and then repeatedly applying the updatePopulation function to it and counting the states until there are no infected agents remaining:
In[]:=
runExperiment[n_(*Sizeofpopulation*),diseased_Integer,immune_Integer,recoveryTime_,isolationRate_,delay_Real,patience_Real,timeStep_Real,space_]:= Block[{population=initialize[n,diseased,immune,recoveryTime,isolationRate,delay,space],history={}}, While[Count[population[[All,"Status"]],_Real]>0, population=updatePopulation[population,timeStep,recoveryTime,patience,space]; AppendTo[history,countStates[population]]]; history]
In[]:=
experiment=runExperiment[10,5,0,20.,0.,0.,0.,1.,20.]
Out[]=
{Infected5,Vulnerable5,Infected5,Vulnerable5,Infected5,Vulnerable5,Infected5,Vulnerable5,Infected5,Vulnerable5,Infected6,Vulnerable4,Infected6,Vulnerable4,Infected6,Vulnerable4,Infected6,Vulnerable4,Infected6,Vulnerable4,Infected6,Vulnerable4,Infected6,Vulnerable4,Infected6,Vulnerable4,Infected6,Vulnerable4,Infected6,Vulnerable4,Infected6,Vulnerable4,Infected6,Vulnerable4,Infected6,Vulnerable4,Infected6,Vulnerable4,Infected6,Vulnerable4,Immune5,Infected1,Vulnerable4,Immune5,Infected1,Vulnerable4,Immune5,Infected1,Vulnerable4,Immune5,Infected1,Vulnerable4,Immune5,Infected2,Vulnerable3,Immune5,Infected2,Vulnerable3,Immune6,Infected1,Vulnerable3,Immune6,Infected1,Vulnerable3,Immune6,Infected1,Vulnerable3,Immune6,Infected1,Vulnerable3,Immune6,Infected1,Vulnerable3,Immune6,Infected1,Vulnerable3,Immune6,Infected1,Vulnerable3,Immune6,Infected1,Vulnerable3,Immune6,Infected1,Vulnerable3,Immune6,Infected1,Vulnerable3,Immune6,Infected1,Vulnerable3,Immune6,Infected1,Vulnerable3,Immune6,Infected1,Vulnerable3,Immune6,Infected1,Vulnerable3,Immune6,Infected1,Vulnerable3,Immune6,Infected1,Vulnerable3,Immune6,Infected1,Vulnerable3,Immune6,Infected1,Vulnerable3,Immune6,Infected1,Vulnerable3,Immune7,Vulnerable3}
This simulation data can be analyzed in many ways, but here we will simply visualize it:
Example experiments
Example experiments
For all experiments, I will use 200 agents in a 25 x 25 space and 2 initial infections.
With no isolation strategy infections rapidly rise to 100% following an approximately logistic curve.
With the rapid implementation of a 100% isolation policy the infections can be halted.
Such a policy is unrealistic. Let us first consider a very strong policy but a less patient population who gradually tires of the policy. The spread is briefly halted but then continues. The whole population is still infected but the delay has been sufficient to reduce the peak infection rate:
Lower initial uptake of isolation but greater patience produces a similar effect but prolongs the outbreak.
For very strong isolation policies the outcome is better but can cause waves of infection.
Interactive simulation
Interactive simulation
We can add a simple user interface using Manipulate
Visualization of agents
Visualization of agents
To give a more detailed animated view of the spread we first need to create a visualization of the population:
We can now view our testPopulation data set :
User interface
User interface
We can now add a user interface to this. For a smooth animation we need to reduce the timeStep between updates, but for performance reasons we do not need to accumulate all of these, now more frequent, simulation results: