Systeme wie Fabriken, Vertriebszentren und Geschäfte, die Kunden einen gewissen Service bieten, müssen in einer unsicheren Umgebung funktionieren, die wahrscheinliche Schwankungen enthält (wie zufällige Kundenankünfte und Geräteausfälle). .. Daher ist es bei der Bewertung der Leistung erforderlich zu verfolgen, wie sich der Zustand des Systems in einer bestimmten probabilistischen Umgebung im Laufe der Zeit ändert. Durch das Sammeln einer großen Anzahl solcher Systemzustandsübergangsdaten wird es möglich, die Leistung des Systems statistisch zu bewerten.
Derzeit ist es im Allgemeinen schwierig, eine große Anzahl von Experimenten in einem System wie einer tatsächlichen Fabrik oder einem Geschäft durchzuführen, sodass ein Simulationsexperiment unter Verwendung eines Modells effektiv ist. Insbesondere wird eine Technik, die als diskrete Ereignissimulation bezeichnet wird, häufig für Simulationsexperimente von Produktions-, Verteilungs- und Dienstleistungssystemen verwendet. In diesem Artikel wollen wir die Grundlagen dieser diskreten Ereignissimulation verstehen und die Fähigkeiten erwerben, um mit dem SimPy-Modul von Python ein einfaches diskretes Ereignissimulationsmodell zu erstellen.
Dieses Mal wollen wir als ersten Schritt zu diesem Zweck den grundlegenden Mechanismus der diskreten Ereignissimulation verstehen.
Man kann sagen, dass das Simulieren eines Systems simuliert, wie sich der Zustand des Systems im Laufe der Zeit ändert, dh die zeitliche Entwicklung des Zustands des Systems. Zu diesem Zeitpunkt wird in der diskreten Ereignissimulation berücksichtigt, dass sich der Status des Zielsystems (nur) ändert, wenn ein Ereignis auftritt. Diese Art, über Zustandsänderungen nachzudenken, ist das Grundmerkmal der diskreten Ereignissimulation.
Wenn Sie daher eine diskrete Ereignissimulation ausführen,
Muss klar definiert sein. Insbesondere werden diese abhängig davon bestimmt, auf welche Art von System abgezielt wird, welcher Aspekt des Systems von Interesse ist und so weiter. Man kann sagen, dass die Modellierung für die diskrete Ereignissimulation genau dazu dient, diese angemessen und klar einzustellen.
Der Zeitpunkt des Auftretens eines probabilistischen Ereignisses wird häufig modelliert, indem das Auftrittsintervall des Ereignisses als stochastische Variable betrachtet und seine Verteilung angegeben wird. Denken Sie beispielsweise daran, dass die Intervalle zwischen zufällig auftretenden Ereignissen einer Exponentialverteilung folgen. Darüber hinaus endet eine Aktivität, einschließlich der Unsicherheit in der Dauer, wenn die Verteilung der Dauer der Aktivität beispielsweise durch eine Normalverteilung modelliert wird und eine Zufallszahl, die der Verteilung der Dauer folgt, zur Startzeit addiert wird. Es wäre gut, es so einzustellen, dass es in auftritt.
Die grundlegendste Möglichkeit, die Funktion der diskreten Ereignissimulation zu implementieren, besteht darin, eine Liste von Ereignissen in aufsteigender Reihenfolge ihres Auftrittszeitpunkts anzuordnen, die Ereignisse von Anfang an einzeln herauszunehmen und nacheinander zu verursachen. Ist. Diese Liste wird als Veranstaltungskalender bezeichnet. Jedes Mal, wenn ein Ereignis aus dem Ereigniskalender abgerufen wird, wird die Simulationszeit auf den Zeitpunkt des Auftretens des Ereignisses vorverlegt. Anschließend wird der Wert der Variablengruppe, die den Systemstatus darstellt, gemäß der Regel aktualisiert, die beschreibt, wie sich der Systemstatus ändert, wenn das Ereignis auftritt.
Schauen wir uns nun eine (sehr einfache) Implementierung des Ereignisses und des Veranstaltungskalenders an.
class Event:
def __init__(self, time, kind):
self.time = time # when this event occurs
self.kind = kind # the type of this event
def __str__(self):
return self.kind
class Calendar:
def __init__(self, horizon):
self.queue = [Event(horizon, 'end')] # list of events
def append(self, e): # add a new event to the list
self.queue.append(e)
self.queue.sort(key=lambda x: x.time) # sort events chronologically
def trigger(self): # trigger the first event in the list
e = self.queue.pop(0)
return e
Ereignis ist eine Klasse von Ereignissen. Derzeit enthält diese Klasse zwei Informationen, den Zeitpunkt des Auftretens ("Zeit") und den Ereignistyp ("Art"). Bei Bedarf können Sie auch andere Parameter hinzufügen.
Kalender ist eine Ereigniskalenderklasse. Es gibt eine Liste mit dem Namen "Warteschlange", und ein Ereignis vom Typ "Ende" (Endereignis) wird hinzugefügt. Dies ist ein Ereignis zum Beenden der Simulation, und die Länge des Simulationszeitraums sollte an das Argument "Horizont" übergeben werden, das den Zeitpunkt ihres Auftretens angibt, wenn die Instanz erstellt wird. append (e)
ist eine Methode, die dem Kalender ein neues Ereignis e
hinzufügt. Nach dem Hinzufügen des Ereignisses "e" zur Liste "Warteschlange" ist ersichtlich, dass es in aufsteigender Reihenfolge des Auftrittszeitpunkts neu angeordnet wird.
trigger ()
ist eine Methode zum Abrufen des ersten Ereignisses in der Liste queue
. In diesem Fall wird das Ereignis einfach aus der Liste extrahiert und "zurückgegeben", und verschiedene Prozesse, die mit dem Auftreten des Ereignisses verbunden sind, müssen separat implementiert werden. Der Einfachheit halber wird auch die Ausnahmebehandlung weggelassen, wenn "Warteschlange" leer ist.
Darüber hinaus habe ich oben erklärt, dass der Ereigniskalender eine Liste ist, und selbst in diesem Implementierungsbeispiel wird er tatsächlich als Liste implementiert, aber natürlich, wenn Ereignisse in der Reihenfolge des Auftrittszeitpunkts, des Heaps usw. gespeichert und einzeln abgerufen werden können. Andere Datenstrukturen können verwendet werden.
Als nächstes erhalten Sie ein Bild der Montage des Hauptteils des Simulationsmodells unter Verwendung dieser Teile. Als Beispiel wird ein einfaches Skelett gezeigt.
import random
class Skelton:
def __init__(self, horizon):
self.now = 0 # simulation time
self.cal = Calendar(horizon) # event calendar
self.add_some_event() # an initial event is added
self.count = 0 # an example state variable (the number of events triggered)
def add_event(self, dt, kind):
e = Event(self.now +dt, kind)
self.cal.append(e)
def add_some_event(self):
self.add_event(random.expovariate(1), 'some_event')
def print_state(self):
print('{} th event occurs at {}'.format(self.count, round(self.now)))
def run(self):
while True:
self.print_state()
e = self.cal.trigger()
print(e)
self.now = e.time # advance the simulation time
self.count += 1 # increment the event counter
if e.kind == 'end': # time is up
break
else:
self.add_some_event() # next event is added
Skelton ist eine Klasse von Simulationsmodellen (Skeletten) des Zielsystems. Die Simulationszeit wird von der Variablen "now" (Anfangswert ist 0) verwaltet. Es ist auch ersichtlich, dass "cal" eine Instanz des Ereigniskalenders ist und durch Erben des Arguments "Horizont" erzeugt wird, das zum Zeitpunkt der Modellgenerierung übergeben wurde. count
ist ein Dummy der Systemstatusvariablen, der die Anzahl der Vorkommen eines Ereignisses zählt. Bei der Implementierung eines praktischen Modells empfiehlt es sich, es durch die erforderlichen Zustandsvariablen zu ersetzen.
add_event ()
ist eine Methode, die die Zeit und den Typ bis zur Auftrittszeit als Argumente verwendet und das neue Ereignis dem Ereigniskalender hinzufügt. Zusätzlich haben wir eine Methode add_some_event ()
definiert, die ein Dummy-Ereignis vom Typ some_event hinzufügt, das durch eine Zufallszahl gegeben ist, die einer Exponentialverteilung mit einer Zeit bis zum Auftreten folgt. priint_state () ist eine Methode, die den Systemstatus in die Konsole schreibt.
Das letzte "run ()" ist die Methode zum Ausführen der Simulation. In der while-Schleife ist zu sehen, dass nach dem ersten Ausschreiben des Systemstatus das Ereignis aus dem Ereigniskalender herausgenommen, der Typ des Ereignisses ausgeschrieben, die Simulationszeit vorverlegt und die Anzahl der Vorkommen des Ereignisses "count" erhöht wird. Wenn das aufgetretene Ereignis beendet ist, endet die Simulation über die while-Schleife. Wenn nicht, wird dem Ereigniskalender ein neues some_event-Ereignis hinzugefügt, und der Prozess fährt mit der nächsten Schleife fort.
Lassen Sie uns dieses (Skelett-) Simulationsmodell tatsächlich verschieben. Erstellen Sie dazu zuerst eine Instanz und führen Sie sie dann mit der Methode run ()
aus (vergessen Sie nicht, den Code auch für die oben genannten Klassen Event, Calendar und Model auszuführen. Dinge zu behalten).
model = Skelton(200)
model.run()
Es ist ersichtlich, dass Ereignisse nacheinander zufällig auftreten und die Simulationszeit entsprechend fortschreitet. Dieser einfache Ablauf ist die Grundstruktur des Algorithmus für die Simulation diskreter Ereignisse. Denken wir also hier daran.
Als nächstes wollen wir das obige Skelett ein wenig ausarbeiten und ein konkretes Simulationsmodell erstellen (was sehr einfach ist). Lassen Sie uns eine Situation modellieren, in der Kunden zufällig ein Geschäft besuchen, das den Bestand eines bestimmten Artikels nach einer Bestellmethode mit fester Menge verwaltet, und den Artikel kaufen. Es wird davon ausgegangen, dass der Kunde einen Artikel kauft, wenn er im Geschäft vorrätig ist, wenn er in das Geschäft kommt und zurückkehrt, und wenn er nicht vorrätig ist, tritt ein Opportunitätsverlust auf. Nehmen Sie außerdem an, dass das durchschnittliche Ankunftsintervall der Kunden 1 beträgt.
Wir werden die Modellklasse definieren, indem wir die Ereignisklasse und die Kalenderklasse so wie sie sind verwenden, die Skelton-Klasse erben und gemäß diesem konkreten Beispiel ein wenig konkretisieren. Der folgende Code ist ein Beispiel.
class Model(Skelton):
def __init__(self, horizon, op, oq, lt, init):
self.now = 0 # simulation time
self.cal = Calendar(horizon) # event calendar
self.add_arrival() # an arrival event is added
self.op = op # ordering point
self.oq = oq # order quantity
self.lt = lt # replenishment lead time
self.at_hand = init # how many items you have at hand
self.loss = 0 # opportunity loss
self.orders = [] # list of back orders
@property
def total(self): # total inventory level including back orders
return sum(self.orders) +self.at_hand
def add_arrival(self): # arrival of a customer
self.add_event(random.expovariate(1), 'arrival')
def add_fill_up(self): # replenishment of ordered items
self.add_event(self.lt, 'fill_up')
def sell_or_apologize(self):
if self.at_hand > 0:
self.at_hand -= 1 # an item is sold
else:
self.loss += 1 # sorry we are out of stock
def fill_up(self): # receive the first order in the list
if len(self.orders) > 0:
self.at_hand += self.orders.pop(0)
def stocktake(self):
if self.total <= self.op:
self.orders.append(self.oq)
return True # ordered
return False # not ordered
def print_state(self):
print('[{}] current level: {}, back order: {}, lost sales: {} '.format(round(self.now), self.at_hand, self.orders, self.loss))
def run(self):
while True:
self.print_state()
e = self.cal.trigger()
print(e)
self.now = e.time # advance the simulation time
if e.kind == 'end': # time is up
break
elif e.kind == 'fill_up':
self.fill_up()
elif e.kind == 'arrival':
self.sell_or_apologize()
self.add_arrival()
ordered = self.stocktake()
if ordered:
self.add_fill_up()
Zusätzlich zum Simulationszeitraum ("Horizont") werden beim Erstellen einer Instanz des Simulationsmodells der Bestellpunkt ("op"), die Bestellmenge ("oq"), die Nachschubvorlaufzeit ("lt") und die anfängliche Bestandsmenge als Argumente angegeben. Vier (init
) wurden hinzugefügt. Außerdem wurde die Statusvariable "count" gelöscht und stattdessen eine Liste mit außerbörslichen Beständen ("at_hand"), Anzahl der Opportunitätsverluste ("Verlust") und nicht nachgefüllten Aufträgen (Nachbestellungen) ("Aufträge") hinzugefügt. Wurde getan. Außerdem ist zu sehen, dass zu Beginn ein Ankunftsereignis zum Veranstaltungskalender hinzugefügt wird. Dies ist die Veranstaltung, die dem ersten Kundenbesuch entspricht.
Wenn wir uns die Methoden ansehen, können wir sehen, dass "add_arrival ()" und "add_fill_up ()" eine Rolle beim Hinzufügen neuer Ankunfts- und Füllereignisse zum Ereigniskalender spielen. sell_or_apologize ()
beschreibt den Umgang mit Kunden, die in den Laden kommen (verkaufen Sie einen, wenn er auf Lager ist, fügen Sie einen Opportunity-Verlust hinzu, wenn nicht). Andererseits ist fill_up ()
eine Methode, die dem Füllen entspricht. stocktake ()
dient zur Überprüfung des Lagerbestands (Gesamtbetrag einschließlich Nachbestellung), zur Entscheidung, ob eine Bestellung gemäß den Regeln der Bestellmethode für feste Mengen aufgegeben werden soll, und zur Bestellung einer vorgegebenen Menge, falls erforderlich. Es entspricht.
Bei der Methode run ()
wird die Verarbeitung beim Auftreten eines Ereignisses diesmal je nach Zielsystem geringfügig geändert. Insbesondere wenn das aufgetretene Ereignis ein fill_up-Ereignis ist, wird die Methode fill_up () einfach aufgerufen. Wenn es sich um ein Ankunftsereignis handelt, führen Sie den Kundensupport durch (sell_or_apologize ()
-Methode), fügen Sie das nächste Ankunftsereignis zum Ereigniskalender hinzu, überprüfen Sie den Lagerbestand und bestimmen Sie die Bestellung (stocktake ()
-Methode). Sie können sehen, dass es gibt. Wenn hier eine Bestellung aufgegeben wird, wird das der Bestellung entsprechende fill_up-Ereignis zum Ereigniskalender hinzugefügt.
Führen Sie dieses Modell beispielsweise mit dem Simulationszeitraum "Horizont = 200", dem Bestellpunkt "op = 10", der Bestellmenge "oq = 20", der Nachschubvorlaufzeit "lt = 10" und der anfänglichen Lagermenge "init = 20" aus. Um es zu sehen, führen Sie den folgenden Code aus.
model = Model(200, 10, 20, 10, 20) # horizon, op, oq, lt, init
model.run()
Es ist schwer zu verstehen, ob das Ergebnis der Simulation einfach als Zeichenfolge auf dem Bildschirm angezeigt wird. Ziehen Sie daher in Betracht, es in einem Diagramm anzuzeigen. Zu diesem Zweck führen wir zunächst eine einfache Protokollklasse ein. Diese Art von Klasse ist auch nützlich, wenn Sie die Simulationsergebnisse in eine CSV-Datei oder dergleichen schreiben möchten.
class Log:
def __init__(self):
self.time = []
self.at_hand = []
self.loss = []
self.total = []
def extend(self, model):
self.time.append(model.now)
self.at_hand.append(model.at_hand)
self.loss.append(model.loss)
self.total.append(model.total)
def plot_log(self):
plt.plot(self.time, self.at_hand, drawstyle = "steps-post")
plt.xlabel("time (minute)")
plt.ylabel("number of items")
plt.show()
Derzeit werden für jedes Mal, wenn das Ereignis eintritt, die Werte des Lagerbestands ("at_hand"), die Anzahl der Opportunitätsverluste ("Verlust") und der Gesamtbestand ("gesamt") zu diesem Zeitpunkt im Protokoll gespeichert. Ich werde gehen. Bereiten Sie eine Liste vor, um die Werte jeder dieser Variablen zu speichern, und implementieren Sie die Methode "verlängern (Modell)", um die Werte jeder der "Modell" -Variablen zu diesem Zeitpunkt zur entsprechenden Liste hinzuzufügen. Sie können sehen, dass es gibt. Die Methode plot_log ()
ist eine Methode zum Anzeigen des Ergebnisses als Liniendiagramm, Details werden hier jedoch weggelassen.
Fügen wir dies dem oben erstellten Modell hinzu. Ich habe nur 3 Zeilen hinzugefügt, aber die modifizierte Model4Plot-Klasse wird unten gezeigt (sie erbt die Model-Klasse oben und die hinzugefügten 3 Zeilen haben einen Kommentar, um dies zu verdeutlichen).
class Model4Plot(Model):
def __init__(self, horizon, op, oq, lt, init):
super().__init__(horizon, op, oq, lt, init)
self.log = Log() # <-- added
self.log.extend(self) # <-- added
def run(self):
while True:
self.print_state()
self.log.extend(self) # <-- added
e = self.cal.trigger()
print(e)
self.now = e.time
if e.kind == 'end':
break
elif e.kind == 'fill_up':
self.fill_up()
elif e.kind == 'arrival':
self.sell_or_apologize()
self.add_arrival()
ordered = self.stocktake()
if ordered:
self.add_fill_up()
Damit wird der Zustand des Systems während der Simulation nacheinander im Protokoll (model.log
) gespeichert. Infolgedessen wurde es nach Ausführung der Simulation möglich, diese Daten zu verwenden, um den Übergang des Geschäftsinventars in einem gestrichelten Liniendiagramm anzuzeigen, beispielsweise mit dem folgenden Code.
import matplotlib.pyplot as plt
model = Model4Plot(200, 10, 20, 10, 20) # horizon, op, oq, lt, init
model.run()
model.log.plot_log()
Lassen Sie uns abschließend versuchen, ein eigenes Simulationsmodell eines einfachen Systems zu erstellen, das auf dem oben eingeführten Skelett basiert. Das Ziel dieser Zeit ist das folgende System.
= cap
).= ub
) ist, stellt sich der Kunde nicht in der Spalte auf und gibt auf und kehrt zurück (Opportunitätsverlust tritt auf).Dieses Mal haben wir den grundlegenden Mechanismus der diskreten Ereignissimulation und ein einfaches Implementierungsbeispiel vorgestellt. Wie unter "Einführung" erwähnt, möchte ich das SimPy-Modul ab dem nächsten Mal vorstellen (das diesmal nicht erschien).
Recommended Posts