2019-ics-malariafreek/malaria.py

333 lines
10 KiB
Python
Raw Normal View History

2019-03-07 16:06:20 +00:00
import matplotlib.pyplot as plt
import matplotlib.colors
2019-03-07 15:42:41 +00:00
import numpy as np
import random
from enum import IntEnum
class Model:
def __init__(self, width=32, height=32, humandens=0.15, mosquitodens=0.10,
2019-03-07 16:06:20 +00:00
immunepct=0.1, mosqinfpct=0.1, hm_infpct=0.5, mh_infpct=0.5,
hinfdiepct=0.01, mhungrypct=0.1, humandiepct=10**-6,
2019-03-07 20:39:11 +00:00
mosqdiepct=10**-3, mosqnetdens=0.05, time_steps=2000,
graphical=True):
2019-03-08 11:20:59 +00:00
2019-03-07 15:42:41 +00:00
self.width = width
self.height = height
2019-03-07 16:06:20 +00:00
2019-03-07 20:39:11 +00:00
# Determines if the simulation should be graphical
self.graphical = graphical
2019-03-07 18:19:46 +00:00
# The percentage of tiles that start as humans
2019-03-07 15:42:41 +00:00
self.humandens = humandens
2019-03-07 18:19:46 +00:00
# The percentage of tiles that contain mosquitos
2019-03-07 15:42:41 +00:00
self.mosquitodens = mosquitodens
2019-03-07 18:19:46 +00:00
# Percentage of humans that are immune
2019-03-07 15:42:41 +00:00
self.immunepct = immunepct
2019-03-07 20:27:17 +00:00
# Chance for a mosquito to be infectuous
2019-03-07 15:42:41 +00:00
self.mosqinfpct = mosqinfpct
# Chance for a mosquito to be infected by a human
2019-03-07 15:42:41 +00:00
self.hm_infpct = hm_infpct
# Chance for a human to be infected from a mosquito
2019-03-07 15:42:41 +00:00
self.mh_infpct = mh_infpct
2019-03-07 18:19:46 +00:00
# Chance that an infected human dies
2019-03-07 15:42:41 +00:00
self.hinfdiepct = hinfdiepct
2019-03-07 18:19:46 +00:00
# Chance for a mosquito to be hungry
2019-03-07 15:42:41 +00:00
self.mhungrypct = mhungrypct
2019-03-07 18:19:46 +00:00
# Chance for human to die from random causes
2019-03-07 15:42:41 +00:00
self.humandiepct = humandiepct
2019-03-07 18:19:46 +00:00
# Chance for a mosquito to die
2019-03-07 15:42:41 +00:00
self.mosqdiepct = mosqdiepct
2019-03-07 18:19:46 +00:00
# Percentage of tiles that contain mosquito nets
2019-03-07 15:42:41 +00:00
self.mosqnetdens = mosqnetdens
2019-03-07 18:19:46 +00:00
# The number of timesteps to run te simulation for
2019-03-07 15:42:41 +00:00
self.time_steps = time_steps
2019-03-07 18:53:26 +00:00
self.grid = self.gen_humans()
2019-03-07 15:42:41 +00:00
self.mosquitos = self.gen_mosquitos()
2019-03-07 21:07:26 +00:00
self.nets = self.gen_nets()
# statistics
self.stats = {
"natural deaths": 0,
"malaria deaths": 0,
"total deaths": 0,
"mosquitos fed": 0,
"humans infected": 0,
"mosquitos infected": 0,
"net count": 0
}
2019-03-07 20:39:11 +00:00
if self.graphical:
self.init_draw()
2019-03-08 11:20:59 +00:00
2019-03-07 18:19:46 +00:00
def init_draw(self):
2019-03-07 20:39:11 +00:00
plt.ion()
2019-03-07 18:19:46 +00:00
self.colors = matplotlib.colors.ListedColormap(
["black", "green", "red", "yellow"])
2019-03-07 15:42:41 +00:00
def recycle_human(self):
"""
2019-03-08 11:20:59 +00:00
Determine if a human dies of natural causes and then replace them by a
new human.
"""
2019-03-07 15:42:41 +00:00
# Get all living humans
2019-03-07 18:53:26 +00:00
humans = np.transpose(np.where(self.grid != Human.DEAD))
2019-03-07 16:06:20 +00:00
# Get a mask of humans to kill
deaths = np.random.rand(len(humans)) < self.humandiepct
# Kill them.
2019-03-07 18:53:26 +00:00
self.grid[humans[deaths][:, 0], humans[deaths][:, 1]] = Human.DEAD
# get num humans after killing
humans_survive = len(np.transpose(np.where(self.grid != Human.DEAD)))
death_count = len(humans) - humans_survive
self.stats["natural deaths"] += death_count
2019-03-07 16:06:20 +00:00
2019-03-07 15:42:41 +00:00
# Pick a random, unpopulated spot
2019-03-08 11:20:59 +00:00
births = np.array(random.sample(
list(np.transpose(np.where(self.grid == Human.DEAD))),
death_count))
2019-03-07 16:06:20 +00:00
2019-03-07 18:53:26 +00:00
# Deliver the newborns
for birth in births:
2019-03-08 11:20:59 +00:00
self.grid[birth[0]][birth[1]] = \
np.random.choice((Human.HEALTHY, Human.IMMUNE),
p=(1 - self.immunepct, self.immunepct))
2019-03-07 18:19:46 +00:00
def do_malaria(self):
"""
This function determines who of the infected dies from their illness
"""
# Get all infected humans
2019-03-07 18:53:26 +00:00
infected = np.transpose(np.where(self.grid == Human.INFECTED))
2019-03-07 18:19:46 +00:00
# Decide which infected people die
deaths = np.random.rand(len(infected)) < self.hinfdiepct
# Now let's kill them
2019-03-07 18:53:26 +00:00
self.grid[infected[deaths][:, 0], infected[deaths][:, 1]] = Human.DEAD
self.stats["malaria deaths"] += len(np.where(deaths)[0])
def feed(self):
"""
Feed the mosquitos that want to and can be fed
"""
2019-03-08 11:20:59 +00:00
# TODO: dit refactoren?
for mos in self.mosquitos:
if not mos.hungry:
continue
# state of current place on the grid where mosquito lives
state = self.grid[mos.x, mos.y]
if state != Human.DEAD:
self.stats["mosquitos fed"] += 1
mos.hungry = False
2019-03-08 11:20:59 +00:00
# check if healthy human needs to be infected or mosquito
# becomes infected from eating
if state == Human.HEALTHY and mos.infected \
and random.uniform(0, 1) < self.mh_infpct:
self.grid[mos.x, mos.y] = Human.INFECTED
self.stats["humans infected"] += 1
2019-03-08 11:20:59 +00:00
elif state == Human.INFECTED and not mos.infected \
and random.uniform(0, 1) < self.hm_infpct:
self.stats["mosquitos infected"] += 1
mos.infected = True
def determine_hunger(self):
"""
Determines which mosquitos should get hungry
"""
for mos in self.mosquitos:
2019-03-08 11:20:59 +00:00
mos.hungry = not mos.hungry and \
random.uniform(0, 1) < self.mhungrypct
def get_movementbox(self, x: int, y: int):
2019-03-07 20:27:17 +00:00
"""
Returns indices of a moore neighbourhood around the given index
"""
x_min = (x - 1)
x_max = (x + 1)
y_min = (y - 1)
y_max = (y + 1)
2019-03-08 11:20:59 +00:00
indices = [(i % self.width, j % self.height)
for i in range(x_min, x_max + 1)
for j in range(y_min, y_max + 1)]
2019-03-07 20:27:17 +00:00
2019-03-07 20:31:21 +00:00
# remove current location from the indices
indices.remove((x, y))
2019-03-07 21:07:26 +00:00
return np.array(indices)
2019-03-07 20:27:17 +00:00
def move_mosquitos(self):
2019-03-07 21:07:26 +00:00
"""
Move the mosquitos to a new location, checks for mosquito nets
"""
2019-03-07 20:31:21 +00:00
for mosq in self.mosquitos:
# get the movement box for every mosquito
movement = self.get_movementbox(mosq.x, mosq.y)
2019-03-07 21:07:26 +00:00
# check for nets, and thus legal locations to go for the mosquito
2019-03-08 11:20:59 +00:00
legal_moves = np.where(~self.nets[tuple(movement.T)])[0]
2019-03-07 20:31:21 +00:00
# choose random new position
2019-03-07 21:07:26 +00:00
new_pos = random.choice(legal_moves)
2019-03-07 20:31:21 +00:00
2019-03-07 21:07:26 +00:00
mosq.x = movement[new_pos][0]
mosq.y = movement[new_pos][1]
2019-03-07 18:19:46 +00:00
2019-03-07 15:42:41 +00:00
def gen_humans(self):
2019-03-07 20:27:17 +00:00
"""
Fill the grid with humans that can either be healthy or infected
"""
2019-03-07 16:06:20 +00:00
# Calculate the probabilities
2019-03-07 15:42:41 +00:00
p_dead = 1 - self.humandens
p_immune = self.humandens * self.immunepct
2019-03-07 16:06:20 +00:00
p_healthy = self.humandens - p_immune
2019-03-07 15:42:41 +00:00
2019-03-07 16:06:20 +00:00
# Create the grid with humans.
2019-03-07 15:42:41 +00:00
return np.random.choice((Human.DEAD, Human.HEALTHY, Human.IMMUNE),
size=(self.width, self.height),
2019-03-07 16:06:20 +00:00
p=(p_dead, p_healthy, p_immune))
2019-03-07 15:42:41 +00:00
def gen_mosquitos(self):
2019-03-07 20:27:17 +00:00
"""
Generate the list of mosquitos
"""
mosquitos = []
count = int(self.width * self.height * self.mosquitodens)
# generate random x and y coordinates
xs = np.random.randint(0, self.width, count)
ys = np.random.randint(0, self.height, count)
coords = list(zip(xs, ys))
# generate the mosquitos
for coord in coords:
# determine if the mosquito is infected
infected = random.uniform(0, 1) < self.mosqinfpct
# determine if the mosquito starts out hungry
hungry = random.uniform(0, 1) < self.mhungrypct
mosquitos.append(Mosquito(coord[0], coord[1], infected, hungry))
return mosquitos
2019-03-07 15:42:41 +00:00
2019-03-07 21:07:26 +00:00
def gen_nets(self):
"""
Generates the grid of nets
"""
2019-03-08 11:20:59 +00:00
return np.random.choice([False, True],
2019-03-07 21:07:26 +00:00
p=[1-self.mosqnetdens, self.mosqnetdens],
size=(self.width, self.height))
2019-03-07 15:42:41 +00:00
def run(self):
2019-03-07 20:27:17 +00:00
"""
This functions runs the simulation
"""
print(chr(27) + "[2J")
2019-03-08 11:20:59 +00:00
# Actual simulation runs inside try except to catch keyboard interrupts
# and always print stats
try:
for t in range(self.time_steps):
print("Simulating timestep: {}".format(t), end='\r')
self.step()
if self.graphical:
self.draw(t)
except KeyboardInterrupt:
pass
2019-03-08 11:20:59 +00:00
print(chr(27) + "[2J")
self.compile_stats()
self.print_stats()
def compile_stats(self):
"""
Compiles a comprehensive list of statistics of the simulation
"""
2019-03-08 11:20:59 +00:00
self.stats["total deaths"] = \
self.stats["malaria deaths"] + self.stats["natural deaths"]
# print(np.where(self.nets))
self.stats["net count"] = len(np.where(self.nets)[0])
def print_stats(self):
"""
Prints the gathered statistics from the simulation
"""
for stat in self.stats:
print(f"{stat}: {self.stats[stat]}")
2019-03-07 15:42:41 +00:00
def step(self):
2019-03-07 20:27:17 +00:00
"""
Step through a timestep of the simulation
"""
# check who dies from malaria
self.do_malaria()
# check if people die from other causes
2019-03-07 18:53:26 +00:00
self.recycle_human()
2019-03-07 20:27:17 +00:00
# move mosquitos
self.move_mosquitos()
# feed hungry mosquitos
self.feed()
# make mosquitos hungry again
self.determine_hunger()
2019-03-08 11:20:59 +00:00
2019-03-07 20:27:17 +00:00
def draw(self, t: int):
"""
Draws the grid of humans, tents and mosquitos
"""
2019-03-07 15:42:41 +00:00
# this function draws the humans
2019-03-07 18:19:46 +00:00
plt.title("t={}".format(t))
2019-03-07 20:27:17 +00:00
# draw the grid
2019-03-08 11:20:59 +00:00
plt.imshow(self.grid, cmap=self.colors)
2019-03-07 21:07:26 +00:00
# draw nets
net_locations = np.where(self.nets)
plt.plot(net_locations[0], net_locations[1], 'w^')
2019-03-07 20:27:17 +00:00
# draw mosquitos
for mos in self.mosquitos:
plt.plot(mos.x, mos.y, mos.get_color()+mos.get_shape())
2019-03-07 16:06:20 +00:00
2019-03-07 15:42:41 +00:00
plt.pause(0.0001)
plt.clf()
class Mosquito:
def __init__(self, x: int, y: int, infected: bool, hungry: bool):
self.x = x
self.y = y
self.infected = infected
self.hungry = hungry
2019-03-08 11:20:59 +00:00
2019-03-07 20:27:17 +00:00
def get_color(self):
# returns the color for drawing, red if infected blue otherwise
return "r" if self.infected else "b"
def get_shape(self):
# return the shape for drawing, o if hungry + otherwise
return "o" if self.hungry else "+"
2019-03-07 15:42:41 +00:00
class Human(IntEnum):
DEAD = 0
HEALTHY = 1
INFECTED = 2
IMMUNE = 3
2019-03-07 15:42:41 +00:00
if __name__ == "__main__":
model = Model(graphical=True)
2019-03-07 20:31:21 +00:00
model.run()
2019-03-07 15:42:41 +00:00