image image


Tutorial: Pandemie-Simulation in Processing.py

Meine gestrige Beschäftigung mit Processing.py, dem Python-Mode von Processing, hat mich angefixt und daran erinnert, daß ich in Zeiten von Corona schon lange eine Pandemie-Simulation erstellen wollte. Und dann war in dem Repositorium mit Beispielen für GameZero.jl auch noch eine Pandemic Sim zu finden, die auf David P. Sanders Workshop »Learn Julia via epidemic modelling« beruht, den er auf der JuliaCon 2020 gehalten hat. Da gab es natürlich kein Halten mehr, so etwas wollte ich in Python realisieren.

image

Das Modell ist extrem einfach. Es gibt eine Population »normaler« Menschen (weiße Kreise), in die ein infizierter Mensch (roter Kreis) eindringt. Kommt er mit noch nichtinfizierten Personen in Kontakt, steckt er sie unweigerlich an (sie werden dann auch als rote Kreise dargestellt und sind genau so ansteckend wie der »Patient 0«). Nach einer gewissen Krankheitsdauer gesundet ein erkrankter Mensch (grüner Kreis) oder er stirbt (transparenter Kreis mit weißer Outline). Die von der Krankheit genesenden können nicht erneut angesteckt werden – sie sind immun.

Wie fast immer bei meinen Processing.py-Experimenten steckt die Hauptarbeit in den Klassen, hier in der Klasse Human:

from random import randint

class Human:
    
    def __init__(self, x, y):
        self.x = x
        self.y = y
        self.radius = 5
        self.state = 0          # State: 0 = normal, 1 = infected, 2 = recovered, 3 = dead
        self.time_to_cure = 2000
        self.chance_to_die = 0.25
        self.movement_rate = 3
    
    def update(self):
        if self.state != 3:      # Only move if not dead
            x_old = self.x       # Store last x position
            y_old = self.y       # Store last y position
            self.x += randint(-self.movement_rate, self.movement_rate)
            self.y += randint(-self.movement_rate, self.movement_rate)
            # Check border
            if (self.x <= self.radius or self.x >= width - self.radius):
                self.x = x_old   # Back to the old x position     
            if (self.y <= self.radius or self.y >= height - self.radius):
                self.y = y_old   # Back to the old y position
            if self.state == 1:  # Only reduce time_to_cure if infected
                self.time_to_cure -= 1
        if self.state == 1 and self.time_to_cure <= 1:  # Recover or die
            if random(1.0) > self.chance_to_die:
                self.state = 2   # Recover
            else:
                self.state = 3   # Die
    
    def show(self):
        stroke(255)
        if self.state == 3:
            noFill()             # Dead, white ring
        elif self.state == 2:
            fill(0, 200, 0)      # Recoverd, green circle
        elif self.state == 1:
            fill(200, 0, 0)      # Infected, red circle
        else:
            fill(255)            # Normal, white circle
        circle(self.x, self.y, 2*self.radius)

    def collision(self, other):
        if self != other:
            distance = dist(self.x, self.y, other.x, other.y)
            if distance <= self.radius + other.radius:
                # One object must be infected and one object must be normal
                if self.state == 1 and other.state == 0:
                    other.state = 1  # Set the normal to infected
                elif self.state == 0 and other.state == 1:
                    self.state == 1  # Set the normal to infected

In der update()-Methode wird unter anderem festgelegt, daß die Population die Umwelt nicht verlassen kann, sie ist daher eher mit einer Petri-Schale oder mit einem abgeschlossenen Internat zu vergleichen1.

Mit den im Quellcode angegebenen Parametern (Population = 100, Genesungszeit 2.000 Frames, Mortalitätsrate 25 Prozent und maximaler Bewegungsradius drei) überlebten einige wenige Probanden (in der Regel zwischen 1 und 4), ohne angesteckt zu werden, daß heißt, sie waren auch am Schluß noch weiße Kreise.

Aber bevor ich daraus weitere Schlüsse ziehe, erst einmal den Quellcode des Hauptprogramms, das, weil die ganze Logik von der Klasse Human abgearbeitet wird, wieder von erfrischender Kürze ist:

from random import randint
from human import Human

num_humans = 100

humans = []

def setup():
    size(600, 600)
    this.surface.setTitle("Pandemie-Simulation")
    for _ in range(num_humans):
        humans.append(Human(randint(20, width - 20), randint(20, height - 20)))
    humans[randint(0, num_humans)].state = 1  # Patient 0

def draw():
    background(0)
    for human in humans:
        human.update()
        for other in humans:
            other.collision(human)
        human.show()

In der setup()-Funktion wird die Bevölkerung (als Liste) initialisiert und dann ein Mensch zufällig ausgewählt, der infiziert ist, also zum Patienten 0 wird, dem Ursprung der Pandemie.

Die draw()-Funktion ruft nun für die gesamte Bevölkerung die Methode update() auf und prüft dann, ob ein Infizierter auf einen (noch) Nichtinfizierten trifft, um ihn anzustecken (Methode collision()).

Caveat: Dies ist ein extrem einfaches Modell und es sagt nichts über die derzeitige Corona-Situation aus. Zum einen ist die Population in sich abgeschlossen (siehe auch die Fußnote), zum anderen ist die Sterberate ungewöhnlich hoch. Außerdem sind Ansteckungszeiten nicht berücksichtigt und es wird davon ausgegangen, daß ein einmal Infizierter für die Zukunft (zumindest für die Dauer der Simulation) immun ist. Dennoch kann man einiges über Pandemien aus diesem einfachen Modell lernen. Wird zum Beispiel die Populationsdichte verringert (versucht es doch einmal mit num_humans = 40, das ergibt einen völlig anderen Verlauf), es gibt (viel) mehr Überlebende (manchmal sogar alle), die während der Dauer der Simulation nicht angesteckt werden. Die anderen Paramter, an denen herumgespielt werden kann, sind die Genesungszeit, die Mortalitätsrate und der Bewegungsradius. Sie alle haben Einfluß auf den Verlauf der Pandemie.

  1. In einer ersten Version war – gemäß der Julia-Vorlage – die Situation noch anders: Verließ ein Kreis das Fenster, wurde er an eine zufällig Position innerhalb des Fensters »teleportiert«. Das ist vielleicht damit zu vergleichen, daß unter Umständen infizierte Personen von außen in die Population eindringen, sei es vom Ballermann oder aus China. Die Folge war, daß – zumindest bei den ausgewählten Parametern – es niemanden gab, der nicht infiziert wurde. 


(Kommentieren) 

image image



Über …

Der Schockwellenreiter ist seit dem 24. April 2000 das Weblog digitale Kritzelheft von Jörg Kantel (Neuköllner, EDV-Leiter Rentner, Autor, Netzaktivist und Hundesportler — Reihenfolge rein zufällig). Hier steht, was mir gefällt. Wem es nicht gefällt, der braucht ja nicht mitzulesen. Wer aber mitliest, ist herzlich willkommen und eingeladen, mitzudiskutieren!

Alle eigenen Inhalte des Schockwellenreiters stehen unter einer Creative-Commons-Lizenz, jedoch können fremde Inhalte (speziell Videos, Photos und sonstige Bilder) unter einer anderen Lizenz stehen.

Der Besuch dieser Webseite wird aktuell von der Piwik Webanalyse erfaßt. Hier können Sie der Erfassung widersprechen.

Diese Seite verwendet keine Cookies. Warum auch? Was allerdings die iframes von Amazon, YouTube und Co. machen, entzieht sich meiner Kenntnis.


Werbung

Diese Spalte wurde absichtlich leergelassen!


Werbung


image  image  image
image  image  image


image