diff --git a/year2-report.pdf b/year2-report.pdf new file mode 100644 index 0000000..ea37adc Binary files /dev/null and b/year2-report.pdf differ diff --git a/year2.py b/year2.py new file mode 100755 index 0000000..d1e7ca4 --- /dev/null +++ b/year2.py @@ -0,0 +1,310 @@ +#!/usr/bin/env python3 + +import random +import time +from matplotlib import pyplot + +HUMAN_DEAD = 0 +HUMAN_HEALTHY = 1 +HUMAN_INFECTED = 2 +HUMAN_IMMUNE = 3 + +MOSQ_POSITION = 0 +MOSQ_INFECTED = 1 +MOSQ_HUNGRY = 2 + + +class Model: + def __init__(self, width=32, height=32, humandens=0.15, mosquitodens=0.10, + immumepct=0.1, mosqinfpct=0.1, hm_infpct=0.5, mh_infpct=0.5, + hinfdiepct=0.01, mhungrypct=0.1, humandiepct=10**-6, + mosqdiepct=10**-3, mosqnetdens=0.05): + self.width = width + self.height = height + + # The initial density of humans + self.humandens = humandens + + # The initial density mosquitos + self.mosquitodens = mosquitodens + + # The chance that a human is immune + self.immumepct = immumepct + + # The chance that a mosquito is born infected + self.mosqinfpct = mosqinfpct + + # The chance that a human infects a mosquito + self.hm_infpct = hm_infpct + + # The chance that a mosquito infects a human + self.mh_infpct = mh_infpct + + # The chance that a mosquito gets hungry + self.mhungrypct = mhungrypct + + # The chance that an infected human dies + self.hinfdiepct = hinfdiepct + + # The chance that an uninfected/immume human dies + self.humandiepct = humandiepct + + # The chance that a mosquito dies + self.mosqdiepct = mosqdiepct + + # The density of mosquito nets + self.mosqnetdens = mosqnetdens + + maxhumans = round(self.humandens * self.width * self.height) + maxmosquitos = round(self.mosquitodens * self.width * self.height) + maxmosqnets = round(self.mosqnetdens * self.width * self.height) + + # Human: 0=dead, 1=healthy, 2=infected, 3=immume + # Mosquito: [(x, y), infected, hungry] + self.humans = [[HUMAN_DEAD for _ in range(width)] + for _ in range(height)] + self.mosquitos = [] + self.mosqnets = [[False]*width for _ in range(height)] + for _ in range(maxhumans): + self.addhuman() + for _ in range(maxmosquitos): + self.addmosquito(random.uniform(0, 1) < mosqinfpct) + for _ in range(maxmosqnets): + self.addmosqnet() + + def addhuman(self): + x, y = random.choice([(x, y) + for x in range(self.width) for y in range(self.height) + if self.humans[y][x] == HUMAN_DEAD]) + + if random.uniform(0, 1) < self.immumepct: + self.humans[y][x] = HUMAN_IMMUNE + else: + self.humans[y][x] = HUMAN_HEALTHY + + def addmosquito(self, infected=False): + height = len(self.humans) + width = len(self.humans[0]) + self.mosquitos.append([(random.randint(0, width-1), + random.randint(0, height-1)), infected, False]) + + def addmosqnet(self): + x, y = random.choice([(x, y) + for x in range(self.width) for y in range(self.height) + if self.mosqnets[y][x] == False]) + self.mosqnets[y][x] = True + + def move(self, pos): + x, y = pos + possibs = [(x, y) for x, y + in [(x-1, y), (x, y-1), (x+1, y), (x, y+1)] + if x > -1 and x < self.width + and y > -1 and y < self.height + and not self.mosqnets[y][x]] + try: + return random.choice(possibs) + except IndexError: + return (x, y) + + def getstates(self): + humanhealthy = sum(1 for row in self.humans for human in row if human == HUMAN_HEALTHY) + humaninfected = sum(1 for row in self.humans for human in row if human == HUMAN_INFECTED) + humanimmune = sum(1 for row in self.humans for human in row if human == HUMAN_IMMUNE) + mosqinfected = sum(1 for mosq in self.mosquitos if mosq[MOSQ_INFECTED]) + mosqhungry = sum(1 for mosq in self.mosquitos if mosq[MOSQ_HUNGRY]) + return humanhealthy, humaninfected, humanimmune, mosqinfected, mosqhungry + + def step(self): + # Copy the human values + humans = [human_row[:] for human_row in self.humans] + + # Now I am become death, the destroyer of worlds + killed = 0 + for human_row in humans: + for i, human in enumerate(human_row): + if human == HUMAN_INFECTED \ + and random.uniform(0, 1) < self.hinfdiepct: + human_row[i] = HUMAN_DEAD + killed += 1 + elif human != HUMAN_DEAD \ + and random.uniform(0, 1) < self.humandiepct: + human_row[i] = HUMAN_DEAD + killed += 1 + + mosqkilled = 0 + mosquitos = [] + for pos, infected, hungry in self.mosquitos: + # Randomly move the mosquito to a new position + (x, y) = self.move(pos) + + if hungry and self.humans[y][x]: + # If a mosquito is hungry and in the same cell as a mosquito, + # human, it'll bite + hungry = False + human = self.humans[y][x] + if human == HUMAN_INFECTED: + # If the human is infected, become infected + infected = random.uniform(0, 1) < self.hm_infpct + + if infected and human == HUMAN_HEALTHY and \ + random.uniform(0, 1) < self.mh_infpct: + # If the human is healthy and not immune, there's a + # mh_infpct chance it will become infected. + humans[y][x] = HUMAN_INFECTED + + elif not hungry: + # If the mosquito is not hungry, there's a mhungrypct + # chance it'll become hungry + hungry = random.uniform(0, 1) < self.mhungrypct + + # There's a mosqdiepct% chance that a mosq will die + if random.uniform(0, 1) < self.mosqdiepct: + mosqkilled += 1 + else: + mosquitos.append(((x, y), infected, hungry)) + + # Send all new values to the model + self.mosquitos = mosquitos + self.humans = humans + + for _ in range(killed): + # Add a human for each one that had died + self.addhuman() + for _ in range(mosqkilled): + # Add a mosqito for each one that had died + self.addmosquito() + + @staticmethod + def drawhuman(human): + if human == HUMAN_DEAD: + return (31, 31, 31) + elif human == HUMAN_HEALTHY: + return (0, 127, 0) + elif human == HUMAN_INFECTED: + return (255, 0, 0) + elif human == HUMAN_IMMUNE: + return (0, 255, 0) + + @staticmethod + def drawmosq(mosq): + _, hungry, infected = mosq + if hungry and infected: + return (255, 61, 0) + elif hungry: # and not infected + return 191, 128, 0 + elif infected: # and not hungry + return (127, 0, 0) + else: # neither + return (0, 0, 223) + + def printlegend(self): + print('Humans (large blocks): ', end='') + print('\033[38;2;%i;%i;%imDead\033[0m' % + self.drawhuman(HUMAN_DEAD), end=', ') + print('\033[38;2;%i;%i;%imHealthy\033[0m' % + self.drawhuman(HUMAN_HEALTHY), end=', ') + print('\033[38;2;%i;%i;%imInfected\033[0m' % + self.drawhuman(HUMAN_INFECTED), end=', ') + print('\033[38;2;%i;%i;%imImmune\033[0m' % + self.drawhuman(HUMAN_IMMUNE)) + + print('Mosquitos (small blocks): ', end='') + print('\033[38;2;%i;%i;%imHungry and Infected\033[0m' % + self.drawmosq(((0, 0), True, True)), end=', ') + print('\033[38;2;%i;%i;%imHungry\033[0m' % + self.drawmosq(((0, 0), True, False)), end=', ') + print('\033[38;2;%i;%i;%imInfected\033[0m' % + self.drawmosq(((0, 0), False, True)), end=', ') + print('\033[38;2;%i;%i;%imNeither\033[0m' % + self.drawmosq(((0, 0), False, False))) + + print('Mosquito nets: /\\') + + def print(self): + data = [[self.drawhuman(human) for human in row] + for row in self.humans] + + # Draw the mosquitos + mosqs = [[None] * self.width for _ in range(self.height)] + for mosq in self.mosquitos: + x, y = mosq[0] + mosqs[y][x] = self.drawmosq(mosq) + + print('\033[2J\033[0;0H' + '\033[0m\n'.join(''.join([ + '\033[48;2;%i;%i;%im' % data[row][x] + + ('\033[38;2;%i;%i;%im\u2590\u258c' % mosqs[row][x] if mosqs[row][x] + else '\033[38;2;255;255;255m/\\' if self.mosqnets[row][x] + else ' ') + for x in range(self.width)]) + for row in range(0, self.height)) + '\033[0m') + + self.printlegend() + + def gui(self, printsteps=1): + try: + fps = 15 + while True: + prevtime = time.time() + for _ in range(printsteps): + self.step() + self.print() + time.sleep(max(0, (1/fps) - (time.time()-prevtime))) + except KeyboardInterrupt: + return + + +def frange(start, end, steps): + stepsize = (end - start) / (steps-1) + for n in range(steps): + yield start + stepsize * n + + +def statesvstime(tmax=40000): + model = Model(64, 64) + tr = list(range(tmax)) + results = [None]*tmax + for t in tr: + print(t/tmax, end='\r') + model.step() + results[t] = model.getstates() + humanhealthy, humaninfected, humanimmune, mosqinfected, mosqhungry = zip(*results) + + pyplot.plot(tr, humanhealthy, label='Healthy humans') + pyplot.plot(tr, humaninfected, label='Infected humans') + pyplot.plot(tr, humanimmune, label='Immune humans') + pyplot.plot(tr, mosqinfected, label='Infected mosquitos') + pyplot.plot(tr, mosqhungry, label='Hungry Mosquitos') + pyplot.legend() + pyplot.xlabel('time (steps)') + pyplot.ylabel('amount') + pyplot.show() + + +def netvsimmune(tmin=10000, denscnt=200): + dr = list(frange(0, 1, denscnt)) + results = [None]*denscnt + for i, dens in enumerate(dr): + print(dens, end='\r') + model = Model(16, 16, mosqnetdens=dens) + for t in range(tmin): + model.step() + results[i] = sum(1 for row in model.humans for human in row if human == HUMAN_IMMUNE) + + pyplot.plot(dr, results, label='Immunity') + pyplot.legend() + pyplot.xlabel('Mosquito net density') + pyplot.ylabel('Immune humans') + pyplot.show() + + +def main(): + model = Model() + model.gui() + + statesvstime() + netvsimmune() + + +if __name__ == '__main__': + random.seed() + main()