#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Mon Dec 13 17:46:38 2021
@author: puudeli
"""
# import random
import matplotlib.pyplot as plt
from matplotlib.patches import Rectangle
from matplotlib.widgets import Slider, Button
import itertools as itr
from Tilasto import tilasto
import paramsPandemia as pars
import Fantasmi
Piirretään fantasmit 'kartalle', piirretään väestön tilaa ajan funktiona ja rakennetaan 'ohjauskeskus' toimenpiteiden ohjaamista varten.
markers = {'A': 'o', 'B': 'v', 'C': 'D', 'D': 's'}
colors = {'altis': 'green', 'oireeton': 'salmon', 'sairas': 'red',
'karanteenissa': 'firebrick',
'immuuni': 'deepskyblue', 'rokotettu': 'lime', 'kuollut': 'silver'}
Luokkaan Kartta kuuluva objekti piirtää näytölle fantasiamaailman olioiden liikkeet ja kunkin olion sairauden tilan. Lisäksi se piirtää käyrinä tilastotietoa fantasiamaailman väestön tautitilanteesta.
Kuva kunkin päivän tilanteesta talletetaan tiedostoon.
Simuloinnin jälkeen ne kootaan videoksi seuraavalla
komentotiedostolla
class Kartta_c:
Taiteilijakielellä voisi sanoa, että alla viritetään kangas kuvan maalaamista varten.
Lisäksi viritetään maalaukseen viereen kojetaulu simuloinnin ohjaamiseen
Piirtelykomennot ovat mystisiä. Minulta kului toinenkin päivä googlaillessa, miten kuvia piirrellään, vaikka suurinpiirtein tiesin, mitä hakea.
Kun kyllästyin käsin ohjaamisen, jätin jäljelle vain osan toiminnoista. Sangen sotkuista nyt. Sorry siitä.
def __init__(self):
# Maailman koko näytöllä pikseleinä
x_max, y_max = pars.dim
self.loppu = False
# ohjaustapahtumat: testaus, liikkumisen jarruttaminen, rokotukset
self.tapahtumat = [[pars.x_max, 'alku', 0]]
plt.ioff()
plt.rcParams.update({'font.size': 14})
# Tällä syntyy näytölle 'kangas'/ikkuna
# (x_max, y_max) pikseliä, jolle piirrellä
# dpi = dots per inch
fig_siz = [int(x_max/100.0), int(y_max/100.0)]
self.fig, self.ax1 = plt.subplots(1, figsize=fig_siz,
dpi=100, tight_layout=False)
self.ax2 = self.ax1.twinx()
# Naksautetaan ikkuna fullscreen moodiin
manager = plt.get_current_fig_manager()
manager.full_screen_toggle()
# Tehdään tilaa liukusäätimille ja ohjausnappuloille
# näytön oikeaan laitaan.
sli_x0 = 0.85
sli_x1 = 0.9
sli_y0 = 0.4
sli_w = 0.015
plt.subplots_adjust(left=0.02, right=sli_x0, bottom=0.02, top=0.95)
Seuraava pätkä on sotkuista. Vaihdoin käsiohjauksesta automaattiohjaukseen. Olisi pitänyt koodata lisää, että sekä käsi- että automaattiohjaus olisivat mahdollisia, mutta en jaksanut vaan kommentoin ylimääräisen pois odottamaan mahdollista inspiraatiota.
Ohjausnappuloita ja liukusäätimiä piirtämään ja hoitamaan luodaan ensin 'artistit' plt.axes()-komennoilla. (Dokumentaatiossa kirjoittavat artisteista ja axes kommennoista samassa lauseessa. En ymmärrä sen enempää.)
Button ja slider -komennoilla piirretään ohjaimet.
on_clicked ja on_changed -komennoilla kerrotaan, mikä funktio suoritetaan, kun ohjaimiin kosketaan.
# Liukusäädin fantasmien nopeuden säätämiseen
self.v_ax = plt.axes([sli_x1, sli_y0,
sli_w, 1.0-sli_y0-4.0*sli_w])
# Liukusäädin testausten määrään ohjailemiseen
self.testi_ax = plt.axes([sli_x1+3.0*sli_w, sli_y0, sli_w,
1.0-sli_y0-4.0*sli_w])
# Nappula, jota painamalla kaupungille tupsahtaa
# itämisvaiheessa oleva taudinkantaja
# infect_ax = plt.axes([sli_x1, 0.04 + 3*sli_w, 3.5*sli_w, 2.0*sli_w])
# self.infect = Button(
# infect_ax, '*', hovercolor='magenta')
# Nappula simuloinnin hallittuun pysäyttämiseen
stop_ax = plt.axes([sli_x1, 0.04, 3.5*sli_w, 2.0*sli_w])
self.stop = Button(stop_ax, 'stop', color='red')
self.rokota_ax = plt.axes(
[sli_x1, 0.04 + 6.0*sli_w, 3.5*sli_w, 2.0*sli_w])
# Tällaisetkin vielä tarvitaan, että ohjelma reagoi, kun säätimiin
# ja nappuloihin kosketaan
# self.v_set.on_changed(self.upd_v)
# self.testi_set.on_changed(self.upd_testi)
# self.infect.on_clicked(self.infectf)
self.stop.on_clicked(self.stop_simu)
# self.rokota_b.on_clicked(self.rokotaf)
self.ohjaimet()
def ohjaimet(self):
"""
Tällä automaattiohjaus päivittää liukusäädinten ja
ohjausnappuloiden tilan.
Päivitys hoituu automaattisesti, jos ohjaimiin kosketaan
käsiohjauksella.
"""
self.v_set = Slider(
self.v_ax, label='liike', valinit=Fantasmi.ohjaus.jarru,
valmin=0.0, valmax=1.0, orientation='vertical')
self.testi_set = Slider(
self.testi_ax, label='testaus', valinit=Fantasmi.ohjaus.testaus,
valmin=0.0, valmax=1.0, orientation='vertical')
if Fantasmi.ohjaus.rokota:
vari = 'green'
else:
vari = 'gray'
self.rokota_b = Button(self.rokota_ax, 'rokotus', color=vari)
Piirretään näytölle käppyröitä kertomaan fantasiamaailman väestön terveydentilasta.
def historiat(self):
self.ax2.plot(tilasto.paivat, tilasto.karanteenissa, zorder=1.0,
color='black', linestyle='dotted', linewidth=2,
label='karanteenissa')
ab = [a+b for (a, b) in zip(tilasto.karanteenissa, tilasto.sairas)]
self.ax2.plot(tilasto.paivat, ab, zorder=1.0,
color='darkviolet', linestyle='dotted', linewidth=2,
label='+ sairas vapaana')
abc = [a+c for (a, c) in zip(ab, tilasto.alussa)]
self.ax2.plot(tilasto.paivat, abc, zorder=1.0,
color='goldenrod', linestyle='dotted', linewidth=2,
label='+ itämisvaihe')
self.ax2.legend(loc='lower left')
self.ax2.set_ylim(ymin=0.0)
def rajat(self):
"""
Piirretään fantasmien kotinurkkausten rajat
"""
self.ax1.add_patch(Rectangle(
(0, 0), (0.5-pars.rajavyohyke)*pars.x_max,
(0.5-pars.rajavyohyke)*pars.y_max,
edgecolor='lightgray', linestyle='dashed', facecolor='none'))
self.ax1.add_patch(Rectangle(
(0, (0.5+pars.rajavyohyke)*pars.y_max),
(0.5-pars.rajavyohyke)*pars.x_max,
(0.5-pars.rajavyohyke)*pars.y_max,
edgecolor='lightgray', linestyle='dashed', facecolor='none'))
self.ax1.add_patch(Rectangle(
((0.5+pars.rajavyohyke)*pars.x_max, 0),
(0.5-pars.rajavyohyke)*pars.x_max,
(0.5-pars.rajavyohyke)*pars.y_max,
edgecolor='lightgray', linestyle='dashed', facecolor='none'))
self.ax1.add_patch(Rectangle(
((0.5+pars.rajavyohyke)*pars.x_max,
(0.5+pars.rajavyohyke)*pars.y_max),
(0.5-pars.rajavyohyke)*pars.x_max,
(0.5-pars.rajavyohyke)*pars.y_max,
edgecolor='lightgray', linestyle='dashed', facecolor='none'))
Piirretään fantasmit kartalle: sijainti, 'kansallisuus' ja taudin vaihe
Fantasmit voisi piirtää yksi kerrallaan näytölle, mutta on nopeampi piirtää kaikki samanlaiset yhdellä piirtokomennolla. Tätä varten joudun keräämään väestön ryhmiksi. Näyttänee hämärältä, mutta oli tosi mielenkiintoista löytää ratkaisu ryhmittelyyn.
def kartalle(self, vaesto):
legends_a = []
legends_b = []
vaesto.sort(key=kansa_tila)
for kotipaikka, kansa in itr.groupby(vaesto, kansa_f):
l_kansa = list(kansa)
legends_b = []
for vaihe, ryhma in itr.groupby(l_kansa, tila_f):
lr = list(ryhma)
axn = self.plottaa(kotipaikka, vaihe, lr)
legends_b.append(axn)
legends_a.append(legends_b)
legends_a.sort(key=len, reverse=True)
self.ax1.legend(handles=legends_a[0], loc='upper left')
def plottaa(self, kotipaikka, vaihe, ryhma):
"""
Läiskäistään kaikki yhdenlaiset fantasmit kartalla
"""
xy = [nn.paikka for nn in ryhma]
lx = [x for (x, y) in xy]
ly = [y for (x, y) in xy]
axx = self.ax1.scatter(
lx, ly, c=colors[vaihe], marker=markers[kotipaikka], s=40,
zorder=1.0, label=vaihe)
if vaihe == 'oireeton':
self.tartunnat_f(ryhma)
return(axx)
Jotta pandemian etenemistä olisi helpompi seurata, väläytetään tartunnan sattuessa näytöllä oranssinpunaista tähteä.
Kun fantasmi N saa tartunnan, sijoitetaan N.uusiTartunta = 3
Alla piirretään tähti kartalle
Lopputulos: Tähti välähtää videolla kolmen kuvan ajan, ensin pienenä, sitten isompana ja lopuksi pienenä Jos videon nopeudeksi laitetaan 10 kuvaa sekunnissa, tähti välähtää 3/10 sekunnin ajan.
(Tässä kartta-modulissa piirretään yhtä päivää vastaavaa pandemian tilannetta. Lopuksi kuva talletaan tiedostoon ja simuloinnin päätyttyä kuvat laitetaan peräkkäin videoksi.)
def tartunnat_f(self, ryhma):
s_koko = (0, pars.suoja_etaisyys,
2.0*pars.suoja_etaisyys,
pars.suoja_etaisyys)
sairastuneet = [sairastunut for sairastunut in ryhma
if sairastunut.uusiTartunta > 0]
if len(sairastuneet) > 0:
sairastuneet.sort(key=tartunta_f)
for indx, tautiset in itr.groupby(sairastuneet, tartunta_f):
tautiset = list(tautiset)
lx = [sairastunut.paikka[0] for sairastunut in tautiset]
ly = [sairastunut.paikka[1] for sairastunut in tautiset]
self.ax1.scatter(lx, ly, c='orangered', marker='*',
s=s_koko[indx])
for sairastunut in sairastuneet:
sairastunut.uusiTartunta -= 1
def hautausmaa(self, vaesto):
"""
Simuloinnin päätteeksi näytetään, minne pandemian uhrit tuupertuivat.
"""
kuolleet = [fantasmi for fantasmi in vaesto
if fantasmi.tila == 'kuollut']
kuolleet.sort(key=kansa_f)
kansat = []
for kotipaikka, kansa in itr.groupby(kuolleet, kansa_f):
kansat.append((kotipaikka, list(kansa)))
for i in range(100):
for kotipaikka, kansa in kansat:
self.plottaa(kotipaikka, 'kuollut', kansa)
self.tallenna()
tilasto.paiva += 1
def infot(self):
"""
tekstimuotoista tietoa näytölle
"""
rokot_osuus = tilasto.N_rokotetut/tilasto.N_vaesto*100.0
# ohjaus = f'ohjaus : {Fantasmi.ohjaus.vaihe} | '
tartunnat = f' tartunnat: {tilasto.N_tartunnat} | '
rokotetut = f' rokotetut: {rokot_osuus:.1f}% | '
kuolleet = f' kuolleet: {tilasto.N_kuolleet}'
txt1 = tartunnat + rokotetut + kuolleet
self.ax1.text(0.4*pars.x_max, 1.01*pars.y_max, txt1)
txt2 = 'päivä: ' + str(tilasto.paiva)
self.ax1.text(1.0*pars.x_max, 1.01*pars.y_max, txt2, ha='right')
for [paikka, vaihe, paiva] in self.tapahtumat:
txt = f'päivä: {paiva} \n {Fantasmi.selitykset[vaihe]}'
self.ax1.text(paikka+5.0, 0.9*pars.y_max, txt, alpha=0.5)
def tapahtumat_f(self):
"""
Piirretään näytölle pystyviiva, kun ohjausautomaatin tila vaihtuu
"""
for i in range(len(self.tapahtumat)):
self.tapahtumat[i][0] -= pars.points_day
for i in range(len(self.tapahtumat)):
if self.tapahtumat[i][0] <= 0:
self.tapahtumat.pop(i)
break
xx = [tapahtuma[0] for tapahtuma in self.tapahtumat]
self.ax1.vlines(xx, 0, pars.y_max)
def sijainnit(self, vaesto):
self.kartalle(vaesto)
self.tapahtumat_f()
self.historiat()
self.rajat()
self.infot()
if Fantasmi.ohjaus.muuttunut:
Fantasmi.ohjaus.muuttunut = False
self.ohjaimet()
self.tapahtumat.append(
[pars.x_max, Fantasmi.ohjaus.vaihe, tilasto.paiva])
self.tallenna()
def tallenna(self):
self.ax1.set_xlim(xmin=0.0, xmax=pars.x_max)
self.ax1.set_ylim(ymin=0.0, ymax=pars.y_max)
self.ax1.axis('off')
plt.pause(0.01)
# Talletetaan kunkin päivän tilanne tiedostoon kuvaksi, joista voi
# koota videon
figno = '{:05d}'.format(tilasto.paiva)
plt.savefig('/home/heikki/maalaus/fantasy21_' +
figno + '.png', format='png', optimize=True)
# pyyhitään taulu puhtaaksi seuraavan päivän kuvaa varten
self.ax1.cla()
self.ax2.cla()
def rokotaf(self, _):
if self.rokota:
self.rokota = False
self.rokota_b.label = 'rokota'
self.rokota_b.color = 'gray'
else:
self.rokota = True
self.rokota_b.label = 'lopeta'
self.rokota_b.color = 'green'
'''
Kommennot, jotka suoritetaan, kun liukusäätimiin tai nappuloihin kosketaan
'''
# def upd_v(self, val):
# Fantasmi.ohjaus.jarru = val
# def upd_testi(self, val):
# Fantasmi.ohjaus.testaus = val
def stop_simu(self, _):
print('stopping ')
self.loppu = True
# def infectf(self, _):
# uusitapaus()
def lopeta(self):
plt.close('all')
# Tätä tarvitaan sortatessa vaestoa järjestykseen
# kotipaikan ja sairauden vaiheen mukaan
# vaesto.sort(key=kansa_tila)
def kansa_tila(e):
return (e.koti, e.tila)
# Järjestetään kotipaikan mukaan
# for kotipaikka, kansa in itr.groupby(vaesto, kansa_f):
def kansa_f(e):
return e.koti
# Arvaa itse
def tila_f(e):
return e.tila
def tartunta_f(e):
return e.uusiTartunta
kartta = Kartta_c()