Simpy - Harjoitustehtävä 3

Harjoitustehtävä 3 - Turvatarkastus

muokkaa

Simulointia käytetään yleisesti tilasteissa, jossa tutkitaan jonon muodostumista. Lentokentän turvatarkastuksen simulointi on klassinen esimerkki jonotuksesta.

Tee ohjelma, joka simuloi lentokentän turvatarkastuksen jonoa kello 08-13 välisenä aikana. Lentokentällä matkustajat saapuvat turvatarkastukseen yhdestä paikasta. Matkustajia saapuu tarkastukseen eri määrä eri kellonaikoina. Saapumisten vaihteluväli sekunteina on seuraavassa taulukossa.

Kellon aika Min Max
Klo 8-10 15 s 40 s
Klo 10-12 10 s 20 s
Klo 12-13 10 s 30 s

Kentällä on 4 turvatarkastuslinjaa. Yksi linjoista on aina auki. Kun jonossa on yli 10 henkilöä, avataan toinen linja, kolmas linja avataan kun jonossa on 15 henkilöä ja neljäs linja kun jonossa on 20 henkilöä. Vastaavasti linja suljetaan, kun jonon pituus laskee alle sen käynnistämisrajan. Jonossa olevien matkustajien määrää tarkastellaan 10 minuutein välein.

Matkustajan turvatarkastuaika vaihtelee 40-90 sekunnin välillä.

Simuloinnin päätteeksi tulee tulostaa kunkin linjan kautta kulkeneiden matkustajien määrä, samoin kokonaismäärä. Lisäksi tulee esittää turvatarkastuksen jonon pituus ja matkustajien keskimääräinen turvatarkastukseen odotusaika kymmenen minuutin aikavälein tarkasteluajalta.

Esimerkkiratkaisu

Huom! Apufunktioiden kello(env) ja kello2(aika) kommentointi lopullisessa ohjelmakoodissa.

Turvatarkastusjono on helpointa simuloida Store-tyyppisenä varastona, johon yksi prosessi generoi matkustajia ja josta turvatarkastusprosessit ottavat sitten matkustajan tarkastukseen.


 
Simulointi prosessin kurvaus: Turvatarkastusjono

Simpyssä Store-tyyppisen varaston luomme

jono=simpy.Store(env)

Tarkastukseen saapuva matkustaja on selvästi olio, jonka saapumisaika (luontiaika) on otettava talteen, koska joudumme laskemaan keskimääräistä odotusaikaa.

class Matkustaja:
   def __init__(self, tunniste, saapumisaika):
       self.__tunniste = tunniste
       self.__saapumisaika = saapumisaika
       self.__odotusaika = 0
   def get_tunniste(self):
       return self.__tunniste
   def get_saapumisaika(self):
       return self.__saapumisaika
   def set_odotusaika(self, odotusaika):
       self.__odotusaika = odotusaika
   def get_odotusaika(self):
       return self.__odotusaika

Nyt Simpyn aikayksikköä tulee käsitellä sekuntina. Muutoin matkustajien generointiprosessi on aika selväpiirteinen.

def Generoi_matkustajia(env, jono):
   i=0
   while True:
       aika = env.now
       if aika< 36000: #Klo 08:0010:00
           saapumisvali = random.randint(min08, max08)
       if aika  >= 36000 and aika < 43200: #Klo 10:00-12:00
           saapumisvali = random.randint(min10, max10)
       if aika >= 43200: #Klo 12:00->
           saapumsivali = random.randint(min12, max12)
       yield env.timeout(saapumisvali)
       matkustaja = Matkustaja(i, aika)
       jono.put(matkustaja)
       i=i+1

Myös itse turvatarkastus on periaatteessa yksinkertainen: otetaan matkustaja jono-varastosta, odotetaan turvatarkastukseen menevä aika ja aloitetaan alusta. Näin toimii ainakin yksi prosessi, joka on toiminnassa koko ajan. Haasteeksi tulee kuinka käynnistämme seuraavat tarkastuslinjat, kun niitä tarvitaan ja pysäytämme ne, kun tarkastusjono on lyhentynyt tarpeeksi.

Tässä avuksi tulee Simpyn Interrupt-menettely, jonka opimme kappaleessa 3. Tehtäväannossa määrättiin, että turvajonon pituutta tarkastellaan 10 minuutin välein. Tarvitsemme siis prosessin, joka kymmennen minuutin välein käy tarkastamassa jonon pituuden ja sen mukaan joko käynnistää uuden tarkastusprosessin tai sulkee sen. Se onko tarkastuslinja käytössä vai ei, voidaan helposti hoitaa "lipputietomuuttujalla". Linjojen käynnistämisen/pysäyttämisen raja-arvot (=jonon pituus) voidaan laittaa muuttujien arvoksi, jotta niiden muuttaminen olisi helppo, jos haluamme simuloida malli toisilla arvoilla. Lisäksi meidän pitää ottaa talteen turvajonopituus List-muuttujaan, jotta voimme myöhemmin raportoida jonon pituuksia eri aikoina.

Nimettäköön koko ajan käynnissä olevasta tarkastuslinjaprosessi Linja1'ksi ja muut sitten Linja2, Linja3 ja Linja4. Simpy Interrupt - menettely edellyttää, että Linja-prosessin käynnistyessä prosessi osoitetaan muuttujalle, johon voidaan viitata, kun prosessi halutaan pysäyttää.

Otamme tässä esimerkiksi Interrupt - menettelyyn myös uuden cause - parametrin käyttöön. Cause-paramterin avulla voidaan keskeytettävään prossiin viedä tieto keskeyttämisen syystä.

maxjono1=10
maxjono2=15
maxjono3=20
jonopituus = []


def Linjakontrolli(env, jono):
   linja2_kaynnissa = False
   linja3_kaynnissa=False
   linja4_kaynnissa=False
   while True:
       jonopituus.append(len(jono.items)) #Otetaan talteen jono pituu ko. ajanhetkenä
       '#Käynnistetään lisää linjoja, kun 1) jono ylittää raja-arvon ja 2) ko. linjaprosessi ei ole käynnissä
       if len(jono.items) > maxjono1 and linja2_kaynnissa == False:
           print(kello(env),'Aktivoidaan Linja2. Jonossa:',len(jono.items))
           l2=env.process(Linja2(env, jono))
           linja2_kaynnissa = True
       if len(jono.items) > maxjono2 and linja3_kaynnissa == False:
           print(kello(env),'Aktivoidaan Linja3. Jonossa:',len(jono.items))
           l3=env.process(Linja3(env, jono))
           linja3_kaynnissa = True
       if len(jono.items) > maxjono3 and linja4_kaynnissa == False:
           print(kello(env),'Aktivoidaan Linja4. Jonossa:',len(jono.items))
           l4=env.process(Linja4(env, jono))
           linja4_kaynnissa = True
       '#Keskeytetään linjaprosessi, kun jonon pituus alittaa raja-arvon ja 2) prosessi on käynnissä
       if len(jono.items) < maxjono3 and linja4_kaynnissa == True:
           cause='matkustajien vähäisyyden vuoksi'
           simpy.events.Interruption(l4,cause)
           linja4_kaynnissa=False
       if len(jono.items) < maxjono2 and linja3_kaynnissa == True:
           cause='matkustajien vähäisyyden vuoksi'
           simpy.events.Interruption(l3,cause)
           linja3_kaynnissa=False
       if len(jono.items) < maxjono1 and linja2_kaynnissa == True:
           cause='matkustajien vähäisyyden vuoksi'
           simpy.events.Interruption(l2,cause)
           linja2_kaynnissa=False
       yield env.timeout(600) #600 aikayksikkö = 10 minuuttia

Jatkuvasti toiminnassa oleva tarkastuslinjaprosessi, eli Linja1 on varsin selkeä, kunhan vain muistamme, että meidän pitää laskea ja ottaa talteen käsitellyn matkustajan odotusaika sekä linjan läpi kulkeneiden matkustajien määrä. Lisäksi matkustaja-olio pitää ottaa talteen lista-muuttujaan, jotta ohjelman lopussa voidaan laskea keskimääräiset odotusajat.

matkustajatiedot = []
linjat = [0,0,0,0]
def Linja1(env, jono):
   while True:
       matkustaja = yield jono.get() #Otetaan seuraava matkustaja jonosta
       tarkastusaika = random.randint(minaika,maxaika) #'Arvotaan' tarkastuaaika
       yield env.timeout(tarkastusaika) #Tarkastetaan matkustaja
       #print(kello(env), 'Matkustaja',matkustaja.get_tunniste(),'pääsi turvatarkastuksesta läpi. Linja 1')
       odotusaika = env.now - matkustaja.get_saapumisaika() # Lasketaan odotusaika
       matkustaja.set_odotusaika(odotusaika) #Laitetaan odotussika talteen olioon
       matkustajatiedot.append(matkustaja) #Lisätään matkustaja-olio matkustajatiedot - list-muuttujaan
       linjat[0]=linjat[0]+1 #Kasvatetaan linjalla  läpikulkeneiden matkustajien määrään (huom! Linja1 = linjat[0])

Linjojen 2-4 prosessi on muutoin sama, mutta jotta pystymme sen tarvittaessa lopettamaan, meidän tulee ottaa käyttöön Pythonin try-except - käytäntö, kuten kappalessaa 4 opimme.

def Linja2(env,jono3):
   while True:
       try: #Linja käynnissä
           matkustaja = yield jono.get()
           tarkastusaika = random.randint(minaika,maxaika)
           yield env.timeout(tarkastusaika)
           #print(kello(env), 'Matkustaja',matkustaja.get_tunniste(),'pääsi turvatarkastuksesta läpi. Linja 2')
           odotusaika = env.now - matkustaja.get_saapumisaika()
           matkustaja.set_odotusaika(odotusaika)
           matkustajatiedot.append(matkustaja)
           linjat[1]=linjat[1]+1
       except simpy.Interrupt as i:  #Linjan toiminta keskeytetään
           print(kello(env),'Linja 2 suljettu', i.cause,'Jonossa:',len(jono.items))
           break

Linjojen 3 ja 4 koodi on vastaavanlaiset kuin edellä. Lopuksi tarvitsemme sitten vielä pääohjelman ja tilastointiosuuden.

env=simpy.Environment(28800)
jono=simpy.Store(env)
env.process(Generoi_matkustajia(env,jono))
env.process(Linjakontrolli(env,jono))
l1=env.process(Linja1(env,jono))
env.run(until=50400)
'#Tilastointiosuus
print('Turvatarkaslinjojen läpilukeneet markustajat')
print('Linja1:',linjat[0],'Linja2:',linjat[1],'Linja3:',linjat[2],'Linja4:',linjat[3])
print('Jonon pituus: Kello : Pituus (hlö):')
for i in range(0,int(len(jonopituus))):
   aika = 28800+600*i
   print(kello2(aika),  jonopituus[i])
print('Keskimääräinen jonotusaika:')
for i  in range (0, len(matkustajatiedot),10):
   matkustaja = matkustajatiedot[i]
   aika = matkustaja.get_saapumisaika()
   print(kello2(aika),':', int(matkustaja.get_odotusaika()/60))

Esimerkkiratkaisun ohjelmakoodi kokonaisuudessaan apufunktioineen:

import simpy
import random
matkustajatiedot = []
jonopituus = []
linjat = [0,0,0,0]
'#Matkustajien saapumisvälit (min,max, sekuntia)
'#Kello 08-10, Kello 10-12 ja kello 12->
min08=15
max08=40
min10=10
max10=20
min12=10
max12=30
maxjono1=10
maxjono2=15
maxjono3=20

'#Turvatarkastuksen läpimenoajan rajat, sekuntia

minaika=40
maxaika=90
class Matkustaja:
   def __init__(self, tunniste, saapumisaika):
       self.__tunniste = tunniste
       self.__saapumisaika = saapumisaika
       self.__odotusaika = 0
   def get_tunniste(self):
       return self.__tunniste
   def get_saapumisaika(self):
       return self.__saapumisaika
   def set_odotusaika(self, odotusaika):
       self.__odotusaika = odotusaika
   def get_odotusaika(self):
       return self.__odotusaika
def kello(env):
   '#Funtio palauttaa env.now ajan muodossa hh:mm - huom. sekunnit on
   '#jätetty pois
   aika=env.now
   minuutit= int(aika/60)
   tunnit = int(minuutit/60)
   minuutit = minuutit-tunnit*60
   if tunnit < 10:
       stunnit='0'+str(tunnit)
   else:
       stunnit = str(tunnit)
   if minuutit < 10:
        sminuutit = '0'+str(minuutit)
   else:
       sminuutit = str(minuutit)
   staika = stunnit+":"+sminuutit
   return staika
def kello2(aika):
   '#Funktio muuttaa sekunteina annetun ajan muotoonh hh:mm
   minuutit= int(aika/60)
   tunnit = int(minuutit/60)
   minuutit = minuutit-tunnit*60
   if tunnit < 10:
       stunnit='0'+str(tunnit)
   else:
       stunnit = str(tunnit)
   if minuutit < 10:
        sminuutit = '0'+str(minuutit)
   else:
       sminuutit = str(minuutit)
   staika = stunnit+":"+sminuutit
   return staika
def Linjakontrolli(env, jono):
   linja2_kaynnissa = False
   linja3_kaynnissa=False
   linja4_kaynnissa=False
   while True:
       jonopituus.append(len(jono.items))
      '#Käynnistetään lisää linjona, kun 1) jono ylittää raja-arvon ja 2) ko. linjaprosessi ei ole käynnissä
       if len(jono.items) > maxjono1 and linja2_kaynnissa == False:
           print(kello(env),'Aktivoidaan Linja2. Jonossa:',len(jono.items))
           l2=env.process(Linja2(env, jono))
           linja2_kaynnissa = True
       if len(jono.items) > maxjono2 and linja3_kaynnissa == False:
           print(kello(env),'Aktivoidaan Linja3. Jonossa:',len(jono.items))
           l3=env.process(Linja3(env, jono))
           linja3_kaynnissa = True
       if len(jono.items) > maxjono3 and linja4_kaynnissa == False:
           print(kello(env),'Aktivoidaan Linja4. Jonossa:',len(jono.items))
           l4=env.process(Linja4(env, jono))
           linja4_kaynnissa = True
       '#Keskeytetään linjaprosessi, kun jonon pituus alittaa raja-arvon ja 2) prosessi on käynnissä
       if len(jono.items) < maxjono3 and linja4_kaynnissa == True:
           cause='matkustajien vähäisyyden vuoksi'
           simpy.events.Interruption(l4,cause)
           linja4_kaynnissa=False
       if len(jono.items) < 15 and linja3_kaynnissa == True:
           cause='matkustajien vähäisyyden vuoksi'
           simpy.events.Interruption(l3,cause)
           linja3_kaynnissa=False
       if len(jono.items) < 10 and linja2_kaynnissa == True:
           cause='matkustajien vähäisyyden vuoksi'
           simpy.events.Interruption(l2,cause)
           linja2_kaynnissa=False
       yield env.timeout(600)
def Generoi_matkustajia(env, jono):
   i=0
   while True:
       aika = env.now
       if aika< 36000:
           saapumisvali = random.randint(min08, max08)
       if aika  >= 36000 and aika < 43200:
           saapumisvali = random.randint(min10, max10)
       if aika >= 43200:
           saapumsivali = random.randint(min12, max12)
       yield env.timeout(saapumisvali)
       matkustaja = Matkustaja(i, aika)
       jono.put(matkustaja)
       i=i+1
def Linja1(env, jono):
   while True:
       matkustaja = yield jono.get() #Otetaan seuraava matkustaja jonosta
       tarkastusaika = random.randint(minaika,maxaika) #'Arvotaan' tarkastuaaika
       yield env.timeout(tarkastusaika) #Tarkastetaan matkustaja
       #print(kello(env), 'Matkustaja',matkustaja.get_tunniste(),'pääsi turvatarkastuksesta läpi. Linja 1')
       odotusaika = env.now - matkustaja.get_saapumisaika() # Lasketaan odotusaika
       matkustaja.set_odotusaika(odotusaika) #Laiktetaan odotussika talteen olioon
       matkustajatiedot.append(matkustaja) #Lisätään matkustaja-olio matkustajatiedot - list-muuttujaan
       linjat[0]=linjat[0]+1 #Kasvatetaan linjalla  läpikulkeneiden matkustajien määrään (huom! Linja1 = linjat[0])
def Linja2(env,jono3):
   while True:
       try:
           matkustaja = yield jono.get()
           tarkastusaika = random.randint(minaika,maxaika)
           yield env.timeout(tarkastusaika)
           #print(kello(env), 'Matkustaja',matkustaja.get_tunniste(),'pääsi turvatarkastuksesta läpi. Linja 2')
           odotusaika = env.now - matkustaja.get_saapumisaika()
           matkustaja.set_odotusaika(odotusaika)
           matkustajatiedot.append(matkustaja)
           linjat[1]=linjat[1]+1
        except simpy.Interrupt as i:
           print(kello(env),'Linja 2 suljettu', i.cause,'Jonossa:',len(jono.items))
           break
def Linja3(env,jono3):
   while True:
       try:
           matkustaja = yield jono.get()
           tarkastusaika = random.randint(minaika,maxaika)
           yield env.timeout(tarkastusaika)
           #print(kello(env), 'Matkustaja',matkustaja.get_tunniste(),'pääsi turvatarkastuksesta läpi. Linja 3')
           odotusaika = env.now - matkustaja.get_saapumisaika()
           matkustaja.set_odotusaika(odotusaika)
           matkustajatiedot.append(matkustaja)
           linjat[2]=linjat[2]+1
       except simpy.Interrupt as i:
           print(kello(env),'Linja 3 suljettu', i.cause,'Jonossa:',len(jono.items))
           break
def Linja4(env,jono3):
   while True:
       try:
           matkustaja = yield jono.get()
           tarkastusaika = random.randint(minaika,maxaika)
           yield env.timeout(tarkastusaika)
           #print(kello(env), 'Matkustaja',matkustaja.get_tunniste(),'pääsi turvatarkastuksesta läpi. Linja 4')
           odotusaika = env.now - matkustaja.get_saapumisaika()
           matkustaja.set_odotusaika(odotusaika)
           matkustajatiedot.append(matkustaja)
           linjat[3]=linjat[3]+1
       except simpy.Interrupt as i:
           print(kello(env),'Linja 4 suljettu', i.cause,'Jonossa:',len(jono.items))
           break
env=simpy.Environment(28800)
jono=simpy.Store(env)
env.process(Generoi_matkustajia(env,jono))
env.process(Linjakontrolli(env,jono))
l1=env.process(Linja1(env,jono))
env.run(until=50400)
print('Turvatarkaslinjojen läpilukeneet markustajat')
print('Linja1:',linjat[0],'Linja2:',linjat[1],'Linja3:',linjat[2],'Linja4:',linjat[3])
print('Jonon pituus: Kello : Pituus (hlö):')
for i in range(0,int(len(jonopituus))):
   aika = 28800+600*i
   print(kello2(aika),  jonopituus[i])
print('Keskimääräinen jonotusaika:')
for i  in range (0, len(matkustajatiedot),10):
   matkustaja = matkustajatiedot[i]
   aika = matkustaja.get_saapumisaika()
   print(kello2(aika),':', int(matkustaja.get_odotusaika()/60))