Simpy- Process

muokkaa

Kappale 1: Process, Yield, env.timeout

muokkaa

Lähdetään tutustumaan Simpy-simulointiin esimerkin avulla.

Tehtävä


Tehtävänä on simuloida parkkipaikkatoimintaa, jossa parkkipaikalle saapuu autoja satunnaisesti 1-10 minuutin välein ja pysäköityy 5-60 minuutin ajaksi. Sovitaan tässä vaiheessa, että Simpyn aikayksikkö kuvaa yhtä minuuttia. Ohjelman pitää kertoa milloin auto saapuu parkkipaikalle, kuinka pitäksi aikaa se pysäköityy ja milloin se lähtee sieltä pois.

Ohjelman aluksi tuomme tarvittavat kirjastot ohjelmakoodiin:

import simpy 
import random

Pysäköitymään tuleva auto on selvästi olio, joka

  1. luodaan jossain satunnaisena ajanhetkenä
  2. pysäköityy
  3. ja sitten poistuu.

Määrittelemme siten Auto-luokan, joka kuvaa luotavia auto-olioita. Jotta voimme tunnistaa autot toisistaan, annamme niille yksilöivän tunnisteen: numeron. Lisäksi tarvitsemme metodin, jolla saamme halutessa tiedon auton numerosta.


class Auto:
   def __init__(self, numero):  	#Numero= monesko auto ja toimii sen tunnisteena
       self.__numero = numero
   def get_numero(self):       	#Metodilla get_numero saadaan tieto numerosta
       return self.__numero

Miten sitten pysäköintitapahtuma hoidetaan? Sitä varten kirjoitamme prosessin, joka “hoitaa” pysäköinnin. Mitä pysäköinti sitten oikeastaan on? Sehän on oikeastaan vain ajankulua. Tästä juuri simuloinnissa on kysymys – mitä ajankuluessa tapahtuu. No, pysäköinnissä ei mitään muuta kuin ajankulumista

Jotta pysäköintiprosessi kohdistuu juuri haluaamme auto-olioon, tulee se viedä prosessiin parametrina. Prosessin sisällä auto on normaali olio, johon voidaan kohdistaa luokkansa mukaisia metodeja, kuten get_numero(), jolla ko. auton numero saadaan selville.

Ajankuluminen tapahtuu Simpy-simuloinnissa menetelmällä env.timeout(aika), jossa aika kertoo kuinka monta aikayksikkö annetaan ajan kulua (mehän sovimme, että tässä tehtävässä se on minuutteja). Tärkeää on huomata, että Simpyssä ei aika kulu, ellei sitä erikseen niin kommenneta. Tavallinen ohjelmakoodi ei kuluta aikaa tai siirrä kello eteenpäin.

Jotta tuo env.timeout saadaan todella kulumaan aikaa juuri nyt, meidän tulee "vaatia" Simpyltä sitä yield – komennolla: yield env.timeout(aika). Katsomme hieman myöhemmin mitä tapahtuu, jos yield’iä ei käytetä.

Lisäksi tarvitsemme menetelmän, joka kertoo juuri sen hetkisen ajan. Siihen meillä on käytössä simulointimuuttuja env.now

Pysäkointiprosessin koodi voisi siten näyttää vaikka tältä:

 def pysakointi(env,auto): #Varsinainen pysköintiprosessi 
   pysakointiaika = random.randint(5,60) #Satunnainen pysäköintiaika 5-60 välillä
   autonumero = auto.get_numero()	  #auto-olion numero saadaan get_numero()-metodilla
   print('Auto:', autonumero,'pysäköityy', pysakointiaika,'minuutiksi')
   yield env.timeout(pysakointiaika) 	 #Annetaan ajan kulua 
   print('Auto:',autonumero,'poistuu pysäköintialueelta. Aika on:', env.now) 
                                         #env.now kertoo tämän hetkisen ajan


Tässä yhteydessä esiintyy env-käsite. Palaamme siihen hetken kuluttua, mutta sen on hyvin oleellinen osa Simpyä.

Pysäköintiprosessi on siis kunnossa ja nyt pitää saada generoitu niitä autoja, jotka pysäköityvät. Teemme siihen oma generaattoriprosessin.

Generointiprosessissa oleellista on,kuinka saamme sen tuottaman uusia auto-oliota niin kauan, kuin haluamme simulointia pyörittää. Tämän toteutamme while True - rakenteella, joka toimii niin kauan, kuin simulointi kestää. Simuloinnin keston määrittelemme hieman myöhemmin.

while True – muodostaa meille silmukkarakenteen, jonka avulla voimme luoda uusia auto-oliota. Autoja piti tehtäväannon mukaan luoda satunnaisesti 1-10 minuutin välein, joten panemme ajan kulumaan taas yield env.timeout – menetelmällä.

Uuden auto-olion luomme Auto-luokan määritelmän mukaan auto = Auto(i) – käskyllä, jossa i on auton tunniste eli numero. Numeroa varten meillä on silmukkarakenteessa tuttu i – silmukkamuuttuja, jota kasvatetaan aina yhdellä uutta autoa varten.

Kun uusi auto-olio on luotu, käynnistetään kyseisen auton pysaköintiprosessi env.process – käskyllä, jonka parametrina on käynnistettävä prosessi eli meidän tapauksessa edellä kirjoittamamme pysakointi. Tässä on nyt huomattava, että emme käytä yield –määritettä. Kun prosessi käynnistetään env.process - ilman yield’iä käynnitetty prosessi toimii itsenäisesti jossain simulointiympäristön taustalla ja sen käynnistänyt ohjelma voi jatkaa omaa toimintaansa.

def Generoiautoja(env):  		#Tällä generoidaan autoja pysäköintiin
   i = 1   				#Ensinmäisen auton numero
   while True:   			#Generaattori toimii env.run(until=) asti
       saapumisvali = random.randint(1,10) #Autoja saapuu satunnaisin 1-10 aikavälein
       yield env.timeout(saapumisvali)    #Annetaan ajan kulua ennen seuraavan auton luomista
       auto = Auto(i) 			# Luodaan auto-olio Auto-luokan ohjeen mukaisesti 
       print('Auto:', i,' saapuu pysäöintialueelle. Aika on:', env.now)
       env.process(pysakointi(env,auto)) #Auto-olion pysäköintiprosessi
       i = i +1			 #Annetaan vielä seuraavalle autolle numero valmiiksi

Eli nyt meillä on kaikki simulointikoodi valmiina ja enää tarvitsee luoda ja käynnistää itse simulointiympäristö. Tämä tahtuu seuraavasti.

env=simpy.Environment() 		 #Tällä luodaan simulointiympäristö nimeltään "env"
env.process(Generoiautoja(env)) 	#Käynnistetään autojen generointi
env.run(until=100) 			#Tämä kertoo kuinka kauan simulointia ajataan 

Simulointiympäristö luodaan env=simpy.Environment() – komennolla. Tässä on siis selitys tuolle env-muutujalle, jota pitää kuljettaa kaikissa prosesseissa mukana parametrina. Autojen generoinnin käynnistämme env.process – komennolla, jonka parametrina on Generoiautoja-prosessi, jonka parametrina taas on env-ympäristömuuttuja. Lopuksi kerromme vain env-ympöristölle .run – metodilla ja (until= ) – parametrilla kuinka monta aikayksikköä (aika-askelta) haluamme simuloinnin kestävän.

Simuloinnin prosessikaavio on siis seuraava:

 
Simuloinnin prosessikaavio

Kun kaikki koodinpätkät laitetaan yhteen saamme seuraavan kokonaisuuden:

import simpy 
import random class Auto: def __init__(self, numero): #Numero= monesko auto ja toimii sen tunnisteena self.__numero = numero def get_numero(self): #Metodilla get_numero saadaan tieto numerosta return self.__numero def pysakointi(env,auto): #Varsinainen pysköintiprosessi pysakointiaika = random.randint(5,60) #Satunnainen pysäköintiaika 5-60 välillä autonumero = auto.get_numero() #auto-olion numero saadaan get_numero()-metodilla print('Auto:', autonumero,'pysäköityy', pysakointiaika,'minuutiksi') yield env.timeout(pysakointiaika) #Annetaan ajan kulua print('Auto:',autonumero,'poistuu pysäköintialueelta. Aika on:', env.now) #env.now kertoo tämän hetkisen ajan def pysakointi(env,auto): #Varsinainen pysköintiprosessi pysakointiaika = random.randint(5,60) #Satunnainen pysäköintiaika 5-60 välillä autonumero = auto.get_numero() #auto-olion numero saadaan get_numero()-metodilla print('Auto:', autonumero,'pysäköityy', pysakointiaika,'minuutiksi') yield env.timeout(pysakointiaika) #Annetaan ajan kulua print('Auto:',autonumero,'poistuu pysäköintialueelta. Aika on:', env.now) #env.now kertoo tämän hetkisen ajan env=simpy.Environment() #Tällä luodaan simulointiympäristö nimeltään "env" env.process(Generoiautoja(env)) #Käynnistetään autojen generointi env.run(until=100) #Tämä kertoo kuinka kauan simulointia ajataan

Mitä sitten saadaan, kun tuo koodi ajataan IDLEssä. No, tätä siitä tulee:

Python 3.4.4 (v3.4.4:737efcadf5a6, Dec 20 2015, 19:28:18) [MSC v.1600 32 bit (Intel)] on win32
Type "copyright", "credits" or "license()" for more information.
Auto: 1 saapuu pysäöintialueelle. Aika on: 10
Auto: 1 pysäköityy 21 minuutiksi
Auto: 2 saapuu pysäöintialueelle. Aika on: 16
Auto: 2 pysäköityy 31 minuutiksi
Auto: 3 saapuu pysäöintialueelle. Aika on: 24
Auto: 3 pysäköityy 26 minuutiksi
Auto: 4 saapuu pysäöintialueelle. Aika on: 26
Auto: 4 pysäköityy 20 minuutiksi
Auto: 5 saapuu pysäöintialueelle. Aika on: 30
Auto: 5 pysäköityy 47 minuutiksi
Auto: 1 poistuu pysäköintialueelta. Aika on: 31
Auto: 6 saapuu pysäöintialueelle. Aika on: 33
Auto: 6 pysäköityy 36 minuutiksi
Auto: 7 saapuu pysäöintialueelle. Aika on: 41
Auto: 7 pysäköityy 55 minuutiksi
Auto: 4 poistuu pysäköintialueelta. Aika on: 46
Auto: 2 poistuu pysäköintialueelta. Aika on: 47
Auto: 8 saapuu pysäöintialueelle. Aika on: 49
Auto: 8 pysäköityy 58 minuutiksi
Auto: 3 poistuu pysäköintialueelta. Aika on: 50
Auto: 9 saapuu pysäöintialueelle. Aika on: 52
Auto: 9 pysäköityy 38 minuutiksi
Auto: 10 saapuu pysäöintialueelle. Aika on: 55
Auto: 10 pysäköityy 37 minuutiksi
Auto: 11 saapuu pysäöintialueelle. Aika on: 56
Auto: 11 pysäköityy 37 minuutiksi
Auto: 12 saapuu pysäöintialueelle. Aika on: 57
Auto: 12 pysäköityy 10 minuutiksi
Auto: 13 saapuu pysäöintialueelle. Aika on: 60
Auto: 13 pysäköityy 22 minuutiksi
Auto: 14 saapuu pysäöintialueelle. Aika on: 66
Auto: 14 pysäköityy 27 minuutiksi
Auto: 12 poistuu pysäköintialueelta. Aika on: 67</nowiki>

Katsotaanpas sitten mitä tapahtuu, jos pysakointi(env,auto) prosessista jätämme yield – määreen pois env.timeout(pysakointiaika) – käskystä eli:

    env.timeout(pysakointiaika) 	#Annetaan ajan kulua 

Ohjelma kyllä käynnistyy, mutta sitten käy näin:

Python 3.4.4 (v3.4.4:737efcadf5a6, Dec 20 2015, 19:28:18) [MSC v.1600 32 bit (Intel)] on win32
Type "copyright", "credits" or "license()" for more information.
Auto: 1 saapuu pysäöintialueelle. Aika on: 5
Auto: 1 pysäköityy 24 minuutiksi
Auto: 1 poistuu pysäköintialueelta. Aika on: 5
Traceback (most recent call last):
File "C:/Users/jussi/Documents/Similointi/Parkkialue.py", line 24, in Generoiautoja
env.process(pysakointi(env,auto)) #Käynnistetään luodulle auto-oliolle pysäköintiprosessi
File "C:\Python34\lib\site-packages\simpy-3.0.8-py3.4.egg\simpy\events.py", line 272, in __init__
raise ValueError('%s is not a generator.' % generator)
ValueError: None is not a generator.

Auto saapuu pysäköintialueelle ajassa 5, pysäköityy 24 minuutiksi, mutta poistuu myös ajan hetkellä 5. Eli aikaa ei kulunut lainkaan, vaikka koodissa oli env.timeout(pysakointaika) pyynto. Pyyntö ei vain toiminut, koska sitä ei oltu vaadittu heti toimimaan yield’illä. Myös Simpy huomaa se ja ilmoitaa, ettei prosessissa ole yhtäänn generaattoria, joka saisi ajan kulumaan.

Laitetaan siis yield takaisin ja lisätään se sitten vielä Generoiautoja-moduuliin env.process(pysakointi(env,auto)) kohtaan yield env.process(pysakointi(env,auto)). Simuloinnin tulos on nyt seuraava:

Python 3.4.4 (v3.4.4:737efcadf5a6, Dec 20 2015, 19:28:18) [MSC v.1600 32 bit (Intel)] on win32
Type "copyright", "credits" or "license()" for more information.
>>>
Auto: 1 saapuu pysäöintialueelle. Aika on: 8
Auto: 1 pysäköityy 40 minuutiksi
Auto: 1 poistuu pysäköintialueelta. Aika on: 48
Auto: 2 saapuu pysäöintialueelle. Aika on: 58
Auto: 2 pysäköityy 48 minuutiksi
>>>

Eli vain kaksi auto tuli simuloitua. Tämä johtuu siitä, että yield env.process käskee Simpyä suorittamaan Auton 1 pysäköinnin loppuun ennenkuin se jatkaa koodin toteuttamisesta auto numero 2 kohdalta. Luodun auton pysököintiprosessi env.process(pysakointi(env,auto)) ei siis käynnisty itsenäisenä prosessina, vaan sen suoritus jää riippumaan käynnistävästä ohjelman (Generoiautoja) jatkosuorittamisesta. Yield’iä ei siis saa käyttää, jos jonkun simuloinnin prosessin halutaan toimivan itsenäisesti (palaamme itsenäisten prosessien keskeyttämiseen myöhemmin...).