A discrete event simulation (DES) is a useful tool used in many areas of science and engineering. In domains where experiments are expensive (e.g., completely reconfiguring Amazon’s supply chain), unethical (e.g., human genetics, where offspring cannot always be generated from specific parents), or impractical (e.g., astronomy, where stars cannot be commanded to implode), simulation provides a practical means for for understanding how processes work and interact. By playing with the parameters of the model, the experimenter is then able to observe the downstream effects on the outcome. DES is used extensively to study industrial workflows, machine placements, scheduling (in many domains), digital design, infection control, etc. Here, we will be building an object-oriented discrete event simulator and using it to experiment with the spread of infectious diseases like influenza or the mumps.
Infectious Disease Simulation
Infectious diseases like influenza, tuberculosis or the mumps are transmitted when an infected agent comes into contact with another agent. Here, we’ll be working on what’s called an agent based model, that is, a model that consists of a collection of actors (or agents), each having independent state and interacting with the other agents. Traditionally, infection simulations use models with names like SIS, SIR, SEIS or SEIR, where the acronym represents the possible states of an individual agent (S=susceptible, E=exposed, I=infected, R=recovered, although some models permit other states as well).
Different diseases map different progressions through these states on different time schedules; for example, for influenza, an agent, once infected, enters an exposed state for roughly 2 days (sometimes referred to as the incubation period), then an infected state for 7 days. When exposed, an agent sheds virus and can infect other agents, but does not yet exhibit symptoms (and so is unlikely to, for example, stay home from work). When in the infected state, the agent is symptomatic (and therefore hopefully less likely to interact with as many other people) while they gradually recover. Finally, once the agent recovers from the illness, they may for some diseases achieve lifelong immunity (the recovered state, which is no longer susceptible to reinfection; this is typical with, for example, the mumps) or may re-enter the susceptible pool, as is the case for the common cold.
In our simulation, transmission of a disease will occur when an agent in the exposed (E) or infected (I) state comes into contact with an agent in the susceptible state based on a computed disease-specific transmission probability. The transmission probability is itself calculated as a product of probabilities, where these probabilities reflect many different factors. More specifically:
(1) Each disease has an inherent transmissitivity parameter t that reflects how intrinsically easy the disease is to transmit.
(2) Each agent has an inherent susceptibility parameter s that reflects the resistance of the agent.
(3) Each agent has an inherent vaccination state v that reflects how protected, if at all, the agent is by vaccination.
Whether or not transmission occurs depends on these factors as well as the duration and proximity of the contact between agents as well as the current agent state (e.g., first day of exposed state, fourth day of infected state, etc.) as this affects the amount of infectious material shed by an agent. Disease transmission is inherently complex! For example, it’s actually much more difficult to catch the flu than to catch the mumps; the flu virus is transmitted by someone coughing or sneezing, and these kinds of contact only occur if the agents are within 1 or 2 meters of each other. On the other hand, the mumps virus or the tuberculosis bacillus easily spread throughout an airplane or a room, so you needn’t be very close to an infected agent in order to be exposed.
So this begs the question of which agents come into contact with which other agents? For now, we’ll make the random mixing assumption, that is, that every agent is in contact with every other agent each day of the simulation with some random probability. This is a pretty reasonable assumption if you are modeling a herd of cattle in a pasture, but a much less reasonable assumption when modeling human behavior. Later, in a subsequent project, you’ll extend these classes to remove the random mixing assumption and make the simulation more realistic.
Your initial implementation consists of three classes and their associated methods, described next.
Note: the signature of the constructor should read __init__(self, name=’influenza’, t=0.95, E=2, I=7, r=0.0).
Objects in this class describe a particular disease, like influenza, the measles, or rubella. The initialization parameters correspond to the disease name, its transmissivity t, the number of days in the exposed state E, the number of days in the infected state I, and the probability that recovery confers lifetime immunity r, respectively (these default values are reasonable values for modeling the flu).
Note: the signature of the constructor should read __init__(self, s=0.99).
Objects in the class model the individual agents in the simulation. The only initialization parameter is the intrinsic susceptibility of the agent to disease; a higher number may indicate a frail elderly person or someone with a compromised immune system (both highly susceptible) while a lower number may indicate a healthy young adult in peak physical condition. You’ll also need a few additional variables which are not set at initialization, like the agent’s vaccination state, v, and some sort of internal state indicating if the agent is sick or not.
This last requirement is easily met by keeping an internal state counter initially set to -1, indicating that the agent is initially in the susceptible state. When the agent is infected, the counter is set to I+E (parameters of the disease), and then decremented on each successive day of the simulation until it reaches 0, the recovered state. At this point, depending on the probability that lifetime immunity to the disease is achieved upon recovery, the counter may be reset to -1, the susceptible state.1 Your agent class will also require a few methods. The first method, vaccinate(self, v) sets the agent’s internal v variable to indicate that some immunity to the disease has been instantly conferred by immunization. We’ll use this variable to model how quickly we can dampen how fast the disease spreads by starting to innoculate the herd: v close to 0 indicates the vaccine is very effective, while v close to 1 indicates very little protection. By default, v should be 1.0, indicating that no vaccination has taken place.
The second method, infect(self, other, disease) gives agent other the chance to infect agent self with disease. Of course, first you’ll need to check if other is infective, if self is susceptible, vaccinated, etc.
before you ‘‘roll the die’’ to determine if the infection is transmitted. If successful, update self’s internal state counter as described above. The general idea here is that, if self is susceptible and other is either exposed or infected, we want to infect self with probability self . s * self . v * disease. t, and adjust self’s internal state accordingly.
2 Also, be careful not to interpret this method the other way around — since only one agent’s internal state is changed, it makes more self to represent that agent, and therefore the agent that is being infected. This better fits with the notion of encapsulation that lies at the heart of the object-oriented worldview.
The third method, state(self), returns True if self internal state corresponds to exposed or infected, and False otherwise.
The fourth method, update(self), is inv oked on each agent every day, and is used to update self’s internal state indicating another day has elapsed, putting self one step closer to recovery if they are exposed or infected.
Note: the signature of the constructor should read __init__(self, D=500).
The final class represents a simple simulation that runs at most D days of simulation (the simulation may terminate early if there are no longer any infected agents remaining). The class must obviously have some internal variables that contain, e.g.,alist of instances of Agent that are in the simulation and the instance of Disease that is being simulated. Other variables may also be necessary, such as, for example, a mixing coefficient m that indicates the probability any one agent will encounter any other agent on any particular day. Interactions with instances of this class occur via the following methods.
The first method, join(self, agent) adds agent to the simulation.
The second method, introduce(self, disease) adds disease to the simulation.
The third method, run(self) starts the simulation. The simulation runs for at most self.D days, but may terminate early if there are no longer any infected agents. The value returned should be a list of tuples (Ne,Ni) corresponding to daily snapshots of the simulation, where Ne is the number of exposed agents and Ni is the number of infected agents on the corresponding day (the list returned will contain at most D tuples).
At this point you should have the following working:
(1) A Disease class with the following methods:
__init__(self, name=’influenza’, t=0.95, E=2, I=7, r=0.0)
(2) An Agent class with the following methods:
infect(self, other, disease)
(3) A Simulation class with the following methods:
Now we’re ready to extend the Simulation class to support running our infection simulations. Mostly this will involve refining a few of the existing methods as well as adding new methods.
First, we need a mechanism to populate the Simulation with an appropriate number of agents. Write a new method populate(self, n, m=0.01) that uses the join() method to add a bunch of agents to the simulation:
>>> S = Simul a t i on( )
>>> S. popu l ate ( 1000 , 0.01 )
The method should introduce 1000 agent instances to the simulation’s internal list of agents, and should set the isimulation’s internal mixing coefficient m to the specified value.
Next, we need a method seed(self , disease, k = 1) that sets some number of agents in the simulation to be initially exposed or infected. This method should select k elements off of the simulation’s internal list of agents and infect those agents with disease. The trick here is to select k agents at random without replacement, that is, without infecting the same agent twice.
We also need to go back and refine our implementation of run(). This method should iterate over at most self . D days. On each day, it should update the state of each agent in the simulation, counting how many infected and exposed agents are still active (you may find slight modifications to the update() method from the Agent class — especially in what the method returns — will make things easier here). It should give each infected or exposed agent a chance to infect the rest of the agents in the simulation with probability m, the mixing parameter. Finally, it should update the internal history containing exposed and infected for this step, returning the completed history at the end of the run.
Finally, we’ll add a new method plot() to simulation that displays the pandemic curve for the simulation — that is, produces nice output from the history stored in the simulation object.
This material may consist of step-by-step explanations on how to solve a problem or examples of proper writing, including the use of citations, references, bibliographies, and formatting. This material is made available for the sole purpose of studying and learning - misuse is strictly forbidden.
import matplotlib.pyplot as plt
def __init__(self, name='influenza', t=0.95, E=2, I=7, r=0.0):
self.name = name
self.t = t
self.E = E
self.I = I
self.r = r
self.s = s
self.v = 1.0
self.stateCounter = -1
self.r = None...
This is only a preview of the solution. Please use the purchase button to see the entire solution