#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Mon Dec 13 17:45:06 2021

@author: puudeli
"""

import random
from Tilasto import tilasto
import paramsPandemia as pars


Fantasmit

Fantasiamaailman eliöt kuuluvan luokkaan Fantasmi

Fantasmin terveydentila on joko

altis
Altis saamaan tartunnan
alku
Tauti on itämisvaiheessa, jolloin se ei aiheuta oireita eikä ole havaittavissa testaamalla
sairas
Tauti havaittavissa testeillä ja potilas väsynyt — hidastaa — vauhtia, mutta kuleksii muiden joukossa ja aiheuttaa tartuntoja
karanteenissa
Potilas on karanteenissa — joko sairaalassa tai kotikolossaan. Ei liiku eikä tartuta muita.
immuuni
Sairaus antaa immuniteetin. Ei kuitenkaan pysyvää.
rokotettu
Vähentää tartunnan todennäköisyyttä merkittävästi.
kuollut
Näinkin voi käydä

Simulointi tulisi mielenkiintoisemmaksi, jos fantasmiin lisäisi tiedon, mitä viruksen mutaatiota se kantaa. Immuniteetti olisi mutaatiokohtainen, joten viruksen uusi mutaatio leviäisi vanhaa helpommin ja ...

Mutta en nyt jaksa innostua koodaamaan.

Fantasmin tila alussa. (= Simuloinnin alkaessa, fantasiamaailman luomisen hetkellä, ...)

Annetaan kullekin fantasmille satunnainen alkunopeus. (random.uniform(l, h) palauttaa tasan jakautuneen satunnaisluvun väliltä l..h)


class Fantasmi:
    def __init__(self):
        # Terveyden tila: alussa altiita tartunnalle
        self.tila = 'altis'
        self.uusiTartunta = 0
        # Tulevaisuuden päivä, jona sairauden nykyvaihe on ohi
        self.ohi = 0
        # satunnainen kotinurkkaus/kansalaisuus
        self.koti = random.choice(['A', 'B', 'C', 'D'])
        # Paikka fantasiamaailmassa, jossa tarkastelun alkaessa
        self.paikka = self.kotipaikka()
        # Nopeus, jolla fantsmi aluksi liikkuu
        self.nopeus = (random.uniform(-pars.max_speed, pars.max_speed),
                       random.uniform(-pars.max_speed, pars.max_speed))


Fantasmien suorakaiteen muotoisessa maailmassa on oma kotinurkkaus kullekin fantasmien kansoista. Kotipaikkojen välissä on rajavyöhyke.

Simuloinnin aluksi fantasmit asetetaan satunnaiseen paikkaan omaan kotinurkkaukseensa.


    def kotipaikka(self):
        low = 0.5 - pars.rajavyohyke
        high = 0.5 + pars.rajavyohyke
        if self.koti == 'A':
            return (random.uniform(0.01, low*pars.x_max),
                    random.uniform(high*pars.y_max, pars.y_max-0.01))
        if self.koti == 'B':
            return (random.uniform(high*pars.x_max, pars.x_max-0.01),
                    random.uniform(high*pars.y_max, pars.y_max-0.01))
        if self.koti == 'C':
            return (random.uniform(0.01, low*pars.x_max),
                    random.uniform(0.01, low*pars.y_max))
        if self.koti == 'D':
            return (random.uniform(high*pars.x_max, pars.x_max-0.01),
                    random.uniform(0.01, low*pars.y_max))


Jokaisella simulointiaskeleella päivitetään fantasmin paikka ja nopeus sen mukaan, paljonko päivän aikaan liikkuvat. Kiihtyvyys x- ja y-suuntaan on satunnaisluku. Fantasmin liike on siis nk. Brownin liikettä.

Karanteenipotilas ei liiku. Eikä kuollut. Sairaat liikkuvat muita hitaammin.

Koska fantasmit ovat sosiaalisia olioita, olisi jännä ohjelmoida niiden välille vetovoima. Yrittäisivät pitää turvaetäisyyttä, mutta tuntisivat vetoa toisiinsa. Ehkä joskus kokeilen tuollaistakin.

(Tein helpon mutta huonon ratkaisun ja laitoin fantasmit liikahtamaan kerran päivässä. Tämän takia nopeus pitää sovittaa niin, että fantasmit nytkähtävät näytöllä eteenpäin sopivan mittaisin askelin. Samalla taudin vaiheiden kestot pitää valita niin, että simulaatio etenee sopivaa vauhtia. Lisävaivalla fantasmien askeleen kestoksi voisi laittaa DT:n, joita mahtuisi päivään vapaasti valittava määrä. Silloin voisi myös laskeskella kohtaamisten kestoa: Mitä pidempään ja mitä lähempänä, sitä vaarallisempaa.)

Fantasmien liikkeen kiihtyvyys on satunnaisluku. Oman kotinurkkauksensa ulkopuolella fantasmit potevat koti-ikävää niin, että kiihtyvyys painottuu pikkuisen kotiinpäin.


    def liike(self):
        # Kuolleet eivät liiku, karanteenipotilaita ei päästetä liikkeelle
        if self.tila not in ['karanteenissa', 'kuollut']:
            (x, y) = self.paikka
            (vx, vy) = self.nopeus
            # Kiihtyvyys: Fantasmit vaihtelevat nopeuttaan.
            # Töytäilevät sinne tänne
            # Tähän lisäksi koti-ikävä
            da_x, da_y = self.koti_ikava()
            ax = pars.kiihtyvyys*random.uniform(-1.0, 1.0) + da_x
            ay = pars.kiihtyvyys*random.uniform(-1.0, 1.0) + da_y
            # Sairaat ei töytäile yhtä innokkasti kuin terveet
            if self.tila == 'sairas':
                ax = 0.5*ax
                ay = 0.5*ay
            vx += ax
            vy += ay
            # Fantasmien maailmasta (= näytöltä) ei saa paeta
            vx = self.reunat(x, pars.x_max, vx)
            vy = self.reunat(y, pars.y_max, vy)
            self.paikka = (x+ohjaus.jarru*vx, y+ohjaus.jarru*vy)
            self.nopeus = (vx, vy)

    def koti_ikava(self):
        wb_l = 0.5 - pars.rajavyohyke
        wb_h = 0.5 + pars.rajavyohyke
        (x1, y1) = self.paikka
        if x1 > wb_l*pars.x_max and self.koti in ['A', 'C']:
            da_x = -pars.da_raja
        elif x1 < wb_h*pars.x_max and self.koti in ['B', 'D']:
            da_x = pars.da_raja
        else:
            da_x = 0.0

        if y1 > wb_l*pars.y_max and self.koti in ['C', 'D']:
            da_y = -pars.da_raja
        elif y1 < wb_h*pars.y_max and self.koti in ['A', 'B']:
            da_y = pars.da_raja
        else:
            da_y = 0.0

        return da_x, da_y


Jos fantasmi törmää fantasiamaailman (=näytön) reunaan, se pomppaa takaisin = se käännytetään takaisiin muuttamalla sen nopeus vastaluvukseen.


    def reunat(self, z, z_max, vz):
        if z > 0.99*z_max and vz > 0:
            return -vz  # min(-0.8*vz, -0.02*pars.max_speed)
        elif z < 0.01*z_max and vz < 0:
            return -vz  # max(-0.8*vz, 0.02*pars.max_speed)
        else:
            return vz


Taudin kulku eli siirtymät taudin vaiheesta toiseen. Tarkistetaan päivittäin.


    def taudin_kulku(self):
        # oireeton --> karanteenissa tai sairas
        if self.tila == 'oireeton' and tilasto.paiva > self.ohi:
            # poimitaan satunnaisesti testaukseen osa fantasmeista
            # 'simu.testaus':n arvoa säädetään 'kojetaulusta'
            if random.uniform(0.0, 1.0) < ohjaus.testaus:
                self.tila = 'karanteenissa'
                self.nopeus = (0.0, 0.0)
                # tauti ohi t0 — t1 päivän kuluttua tästä päivästä
                (t0, t1) = pars.kestot['karanteenissa']
                self.ohi = tilasto.paiva + random.uniform(t0, t1)
            else:  # ei testattu
                self.tila = 'sairas'
                # Sairastunut hidastaa vauhtia. Väsyttää.
                self.nopeus = (0.5*self.nopeus[0], 0.5*self.nopeus[1])
                (t0, t1) = pars.kestot['sairas']
                self.ohi = tilasto.paiva + random.uniform(t0, t1)

        # karanteenissa --> immuuni
        elif self.tila == 'karanteenissa' and tilasto.paiva > self.ohi:
            self.tila = 'immuuni'
            (t0, t1) = pars.kestot['immuuni1']
            self.ohi = tilasto.paiva + random.uniform(t0, t1)
            # self.nopeus = self.init_v(pars.max_speed)

        # sairas --> immuuni
        elif self.tila == 'sairas' and tilasto.paiva > self.ohi:
            self.tila = 'immuuni'
            (t0, t1) = pars.kestot['immuuni0']
            self.ohi = tilasto.paiva + random.uniform(t0, t1)
            # self.nopeus = self.init_v(pars.max_speed)

        # immuuni --> altis
        elif self.tila == 'immuuni' and tilasto.paiva > self.ohi:
            self.tila = 'altis'

        # Karanteenissa tai sairaana voi kuolla juuri ennen parantumistaan
        elif self.tila in ('sairas', 'karanteenissa') \
                and tilasto.paiva >= self.ohi - 1.0:
            if random.uniform(0.0, 100.0) < pars.kuolleisuus:
                self.tila = 'kuollut'
                tilasto.N_kuolleet += 1
                tilasto.N_vaesto -= 1


Aluksi ohjailin testaamista, liikkumisrajoituksia ja rokotuksia käsin komentokeskuksen kojetaululta, mutta kyllästyin, koska vanha koneeni simuloi pandemiaa tuskastuttavan hitaasti. Tein automaatin, joka demonstroi eri vaihtoehtoja rajoittaa pandemian etenemistä.

Funktiot ylaraja ja alaraja kertovat, milloin automaatin sopii siirtyä vaiheesta seuraavaan. Jos sairastuneiden lukumäärä on todella suuri, pitää ryhtyä toimenpiteisiin. Jos saiastuneiden lukumäärä on todella pieni, toimenpiteistä voidaan luopua. Jos tilanne jatkuu pitkään ennallaan ja sairaita on melko paljon, ryhdytään toimenpiteisiin. Vastaaasti niistä luovutaan, jos pandemia kytee melko pienellä tasolla pitkään.

Vapaana kulkevien sairastuneiden lukumäärä on paras mittari päättää, mitä tehdä seuraavaksi. 'Elävässä elämässä' sitä ei kuitenkaan voida mitata, joten tässä(kin) kohtaa fuskaan.


def ylaraja(sairaat, deadline):
    H2 = 0.6*pars.limH
    return (sairaat > pars.limH) or \
        (tilasto.paiva > deadline and sairaat > H2)


def alaraja(sairaat, deadline):
    L2 = 1.75*pars.limL
    return (sairaat < pars.limL) or \
        (tilasto.paiva > deadline and sairaat < L2)


Ohjausautomaattini käy läpi seuraavat tilat


class Ohjaus:
    def __init__(self):
        self.vaihe = 'alku'
        self.jarru = 1.0
        self.testaus = 0.0
        self.rokota = False
        self.deadline = tilasto.paiva + pars.vaihe_kesto
        self.muuttunut = False
        self.loppu = False

    def uusi_vaihe(self):
        if tilasto.N_sairas + tilasto.N_karanteenissa +\
                tilasto.N_alku < 1:
            self.loppu = True

        tartuttajat = tilasto.N_sairas + tilasto.N_alku

        if self.vaihe == 'alku':
            if ylaraja(tartuttajat, self.deadline):
                # if tartuttajat > pars.limH or tilasto.paiva > self.deadline:
                self.vaihe = 'testaus'
                self.testaus = 0.4
                self.muuttunut = True
                self.deadline = tilasto.paiva + pars.vaihe_kesto
        elif self.vaihe == 'testaus':
            if alaraja(tartuttajat, self.deadline):
                # if tartuttajat < pars.limL or tilasto.paiva > self.deadline:
                self.vaihe = 'vapaa1'
                self.testaus = 0.0
                self.muuttunut = True
                self.deadline = tilasto.paiva + pars.vaihe_kesto
        elif self.vaihe == 'vapaa1':
            if ylaraja(tartuttajat, self.deadline):
                # if tartuttajat > pars.limH or tilasto.paiva > self.deadline:
                self.vaihe = 'jarru'
                self.jarru = 0.4
                self.muuttunut = True
                self.deadline = tilasto.paiva + pars.vaihe_kesto
        elif self.vaihe == 'jarru':
            if alaraja(tartuttajat, self.deadline):
                # if tartuttajat < pars.limL or tilasto.paiva > self.deadline:
                self.vaihe = 'vapaa2'
                self.jarru = 1.0
                self.muuttunut = True
                self.deadline = tilasto.paiva + pars.vaihe_kesto
        elif self.vaihe == 'vapaa2':
            if ylaraja(tartuttajat, self.deadline):
                # if tartuttajat > pars.limH or tilasto.paiva > self.deadline:
                self.vaihe = 'rokota'
                self.rokota = True
                self.muuttunut = True
                self.deadline = tilasto.paiva + pars.vaihe_kesto
        elif self.vaihe == 'rokota':
            if tilasto.N_rokotetut/tilasto.N_vaesto > 0.75 or \
                  tilasto.paiva > self.deadline:
                self.vaihe = 'odotellaan'
                self.rokota = False
                self.muuttunut = True
                self.deadline = tilasto.paiva + pars.vaihe_kesto
        elif self.vaihe == 'odotellaan':
            if tilasto.paiva > self.deadline:
                self.vaihe = 'hatajarru'
                self.testaus = 0.5
                self.jarru = 0.4
                self.muuttunut = True
                self.deadline = tilasto.paiva + pars.vaihe_kesto
        elif self.vaihe == 'hatajarru':
            if tilasto.paiva > self.deadline:
                self.testaus = 0.9
                self.jarru = 0.1


selitykset = {
    'alku': 'Pandemia \nsaa edetä \nvapaasti',
    'testaus': 'Testataan ja \nlaitetaan \nkaranteeniin',
    'vapaa1': 'Pandemia \nsaa edetä \nvapaasti',
    'jarru': 'Liikuntarajoituksia, \n hidastetaan \nfantasmeja',
    'vapaa2': 'Pandemia \nsaa edetä \nvapaasti',
    'rokota': 'rokotuskampanja',
    'odotellaan': 'Toivotaan, \nettä rokotukset \nhävittävät \nviruksen',
    'hatajarru': 'Tiukka testaus \n + tiukat \nliikuntarajoitukset'
              }

# Luodaan ohjausautomaatti
ohjaus = Ohjaus()