#!/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
Fantasiamaailman eliöt kuuluvan luokkaan Fantasmi
Fantasmin terveydentila on joko
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()