Einführung in die diskrete Ereignissimulation mit Python # 1

Einführung

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.

Was ist diskrete Ereignissimulation?

Grundlegender Mechanismus der diskreten Ereignissimulation

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.

Implementierungsbeispiel für Ereignis und Ereigniskalender

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.

Skelett des Simulationsmodellkörpers

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.

Ein einfaches Beispiel

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()

Protokollaufnahme und einfache Grafikanzeige

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()

Übungsproblem

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.

Zusammenfassung

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).

Verknüpfung

Recommended Posts

Einführung in die diskrete Ereignissimulation mit Python # 1
Einführung in die diskrete Ereignissimulation mit Python # 2
Einführung in die Python-Sprache
Einführung in OpenCV (Python) - (2)
Mit Python auf Twitter posten
Starten Sie mit Python zu Selen
Einführung in die serielle Kommunikation [Python]
[Einführung in Python] <Liste> [Bearbeiten: 22.02.2020]
Einführung in Python (Python-Version APG4b)
Eine Einführung in die Python-Programmierung
[Einführung in Python] Wie stoppe ich die Schleife mit break?
Einführung in discord.py (3) Verwenden von Stimme
[Einführung in Python] So schreiben Sie sich wiederholende Anweisungen mit for-Anweisungen
Einführung in Python For, While
[Technisches Buch] Einführung in die Datenanalyse mit Python -1 Kapitel Einführung-
[Einführung in Python] Wie man bedingte Verzweigungen mit if-Anweisungen schreibt
[Python] Einführung in die Diagrammerstellung mit Corona-Virendaten [Für Anfänger]
[Einführung in die Udemy Python3 + -Anwendung] 58. Lambda
[Einführung in die Udemy Python3 + -Anwendung] 31. Kommentar
Einführung in die Python Numerical Calculation Library NumPy
Trainieren! !! Einführung in Python Type (Type Hints)
[Einführung in Python3 Tag 1] Programmierung und Python
[Einführung in Python] <numpy ndarray> [edit: 2020/02/22]
[Einführung in die Udemy Python3 + -Anwendung] 57. Decorator
Einführung in Python Hands On Teil 1
[Einführung in Python3 Tag 13] Kapitel 7 Zeichenfolgen (7.1-7.1.1.1)
[Einführung in Python] So analysieren Sie JSON
[Einführung in die Udemy Python3 + -Anwendung] 56. Abschluss
[Einführung in Python3 Tag 14] Kapitel 7 Zeichenfolgen (7.1.1.1 bis 7.1.1.4)
Einführung in Protobuf-c (C-Sprache ⇔ Python)
[Einführung in die Udemy Python3 + -Anwendung] 59. Generator
Verwenden von Cloud-Speicher aus Python3 (Einführung)
[Einführung in Python3 Tag 15] Kapitel 7 Zeichenfolgen (7.1.2-7.1.2.2)
[Einführung in Python] Verwenden wir Pandas
[Einführung in Python] Verwenden wir Pandas
[Einführung in die Udemy Python3 + -Anwendung] Zusammenfassung
Einführung in die Bildanalyse opencv python
[Einführung in Python] Verwenden wir Pandas
Erste Schritte mit Python für Nicht-Ingenieure
Einführung in Python Django (2) Mac Edition
[AWS SAM] Einführung in die Python-Version
[Einführung in Python3 Tag 21] Kapitel 10 System (10.1 bis 10.5)
[Python Tutorial] Eine einfache Einführung in Python
Python: Einführung in Flask: Erstellen einer Nummernidentifizierungs-App mit MNIST
[Einführung in die Udemy Python3 + -Anwendung] 18. Listenmethode
[Einführung in die Udemy Python3 + -Anwendung] 63. Notation zur Einbeziehung des Generators
[Python] Fluidsimulation: Von linear zu nichtlinear
[Einführung in die Udemy Python3 + -Anwendung] 28. Kollektiver Typ
[Einführung in Python] Wie verwende ich eine Klasse in Python?
Von Python bis zur Verwendung von MeCab (und CaboCha)
[Einführung in die Udemy Python3 + -Anwendung] 25. Wörterbuchmethode
[Einführung in die Udemy Python3 + -Anwendung] 33. if-Anweisung
[Einführung in die Udemy Python3 + -Anwendung] 13. Zeichenmethode
[Einführung in Python3, Tag 17] Kapitel 8 Datenziele (8.1-8.2.5)
[Einführung in die Udemy Python3 + -Anwendung] 55. In-Function-Funktionen
[Einführung in die Udemy Python3 + -Anwendung] 48. Funktionsdefinition
[Einführung in Python3, Tag 17] Kapitel 8 Datenziele (8.3-8.3.6.1)
Python Bit Arithmetic Super Einführung
[Einführung in die Udemy Python3 + -Anwendung] 10. Numerischer Wert
Einführung in das Auffüllen von Python-Bildern Auffüllen von Bildern mit ImageDataGenerator
Web-WF Python Tornado Teil 3 (Einführung in Openpyexcel)