Introduction à la simulation d'événements discrets à l'aide de Python # 1

introduction

Les systèmes tels que les usines, les centres de distribution et les magasins qui fournissent certains services aux clients doivent fonctionner dans un environnement incertain qui comprend des fluctuations probabilistes (telles que des arrivées aléatoires de clients et des pannes d'équipement). .. Par conséquent, lors de l'évaluation de ses performances, il est nécessaire de suivre l'évolution de l'état du système placé dans un environnement probabiliste donné au fil du temps. En collectant un grand nombre de ces données de transition d'état du système, il devient possible d'évaluer statistiquement les performances du système.

À l'heure actuelle, il est généralement difficile de réaliser un grand nombre d'expériences dans un système tel qu'une usine ou un magasin réel, de sorte qu'une expérience de simulation utilisant un modèle est efficace. En particulier, une technique appelée simulation d'événements discrets est souvent utilisée pour des expériences de simulation de systèmes de production, de distribution et de fourniture de services. Dans cet article, comprenons les bases de cette simulation d'événements discrets et acquérons les compétences nécessaires pour créer un modèle de simulation d'événements discrets simple à l'aide du module SimPy de Python.

Cette fois, comme première étape à cette fin, nous visons à comprendre le mécanisme de base de la simulation d'événements discrets.

Qu'est-ce que la simulation d'événements discrets?

Mécanisme de base de la simulation d'événements discrets

On peut dire que la simulation d'un système simule la façon dont l'état du système change dans le temps, c'est-à-dire l'évolution temporelle de l'état du système. À ce stade, dans la simulation d'événement discret, on considère que l'état du système cible change (uniquement) lorsqu'un événement se produit. Cette façon de penser les changements d'état est la caractéristique de base de la simulation d'événements discrets.

Par conséquent, lors de l'exécution d'une simulation d'événement discret,

Doit être clairement défini. Plus précisément, ceux-ci sont déterminés en fonction du type de système visé, de l’aspect du système qui vous intéresse, etc. On peut dire que la modélisation pour la simulation d'événements discrets consiste exactement à les définir de manière appropriée et claire.

Le moment de l'occurrence d'un événement probabiliste est souvent modélisé en considérant l'intervalle d'occurrence de l'événement comme une variable stochastique et en spécifiant sa distribution. Par exemple, rappelez-vous que les intervalles entre les événements aléatoires suivent une distribution exponentielle. En outre, le cas où une activité comprenant une incertitude sur la durée se termine est lorsque la distribution de la durée de l'activité est modélisée, par exemple, par une distribution normale, et un nombre aléatoire qui suit la distribution de la durée est ajouté à l'heure de début. Il serait bon que cela se produise.

La méthode la plus élémentaire pour mettre en œuvre la fonction de simulation d'événements discrets consiste à disposer d'une liste d'événements classés par ordre croissant de leur synchronisation d'occurrence, à supprimer les événements un par un depuis le début et à les provoquer en séquence. Est. Cette liste s'appelle un calendrier d'événements. Chaque fois qu'un événement est extrait du calendrier des événements, le temps de simulation est avancé au moment de l'occurrence de l'événement. Ensuite, les valeurs des variables qui représentent l'état du système sont mises à jour selon les règles qui décrivent comment l'état du système change lorsque l'événement se produit.

Exemple de mise en œuvre de calendrier d'événements et d'événements

Voyons maintenant une implémentation (très simple) du calendrier des événements et des événements.

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

L'événement est une classe d'événements. Pour le moment, cette classe contient deux informations, l'heure d'occurrence («time») et le type d'événement («kind»). Si nécessaire, vous pouvez également ajouter d'autres paramètres.

Calendar est une classe de calendrier d'événements. Il y a une liste nommée queue, et un événement du type end (événement de fin) y est ajouté. Il s'agit d'un événement pour terminer la simulation, et la longueur de la période de simulation doit être passée à l'argument «horizon», qui indique le moment de son occurrence, lorsque l'instance est créée. ʻAppend (e) est une méthode qui ajoute un nouvel événement ʻe au calendrier. Après avoir ajouté l'événement «e» à la liste «queue», on peut voir qu'il est réorganisé par ordre croissant de synchronisation d'occurrence.

trigger () est une méthode pour récupérer le premier événement de la liste queue. Dans ce cas, l'événement est simplement retiré de la liste et «renvoyé», et divers processus associés à l'occurrence de l'événement doivent être mis en œuvre séparément. De plus, par souci de simplicité, la gestion des exceptions lorsque la `file 'est vide est omise.

De plus, j'ai expliqué ci-dessus que le calendrier des événements est une liste, et même dans cet exemple d'implémentation, il est en fait implémenté sous forme de liste, mais bien sûr, si les événements peuvent être stockés et peuvent être récupérés un par un dans l'ordre du moment de l'occurrence, du tas, etc. , D'autres structures de données peuvent être utilisées.

Squelette du corps du modèle de simulation

Ensuite, obtenons une image du montage du corps principal du modèle de simulation à l'aide de ces pièces. A titre d'exemple, un squelette simple est montré.

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 est une classe de modèles de simulation (squelettes) du système cible. Le temps de simulation est géré par la variable "now" (la valeur initiale est 0). On peut également voir que «cal» est une instance du calendrier des événements et est généré en héritant de l'argument «horizon» passé au moment de la génération du modèle. count est une variable factice de la variable d'état du système, qui compte le nombre d'occurrences d'un événement. Lors de la mise en œuvre d'un modèle pratique, il est judicieux de le remplacer par les variables d'état requises.

ʻAdd_event () est une méthode qui prend le temps et le type jusqu'au moment de l'occurrence comme arguments et ajoute le nouvel événement au calendrier des événements. De plus, une méthode ʻadd_some_event () ʻ est également définie qui ajoute un événement factice du type some_event donné par un nombre aléatoire qui suit une distribution exponentielle avec un temps d'apparition. priint_state ()` est une méthode qui écrit l'état du système sur la console.

Le final run () est la méthode pour lancer la simulation. Dans la boucle while, on peut voir qu'après la première écriture de l'état du système, l'événement est extrait du calendrier des événements, le type de l'événement est écrit, le temps de simulation est avancé et le nombre d'occurrences de l'événement count est incrémenté. Si l'événement qui s'est produit est terminé, la simulation se termine par la boucle while, mais sinon, un nouvel événement some_event est ajouté au calendrier des événements et le processus passe à la boucle suivante.

Déplaçons en fait ce modèle de simulation (squelette). Pour ce faire, créez d'abord une instance, puis exécutez-la avec la méthode run () (n'oubliez pas d'exécuter également le code pour les classes Event, Calendar et Model ci-dessus. Choses à garder).

model = Skelton(200)
model.run()

On peut voir que les événements se produisent un par un au hasard, et le temps de simulation progresse en conséquence. Ce flux simple est la structure de base de l'algorithme de simulation d'événements discrets, gardons-le donc à l'esprit ici.

Un exemple simple

Ensuite, étoffons un peu le squelette ci-dessus et créons un modèle de simulation concret (ce qui est très simple). Modélisons une situation dans laquelle les clients visitent au hasard un magasin qui gère l'inventaire d'un certain article par une méthode de commande à quantité fixe et achètent l'article. On suppose que le client achète un article s'il est en stock au magasin lorsqu'il arrive au magasin et le retourne, et s'il est en rupture de stock, une perte d'opportunité se produit. Supposons également que l'intervalle d'arrivée moyen des clients soit de 1.

Nous définirons la classe Model en utilisant la classe Event et la classe Calendar telles quelles, en héritant de la classe Skelton et en étoffant un peu selon cet exemple concret. Le code ci-dessous est un exemple.

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

En plus de la période de simulation (horizon), le point de commande (ʻop), la quantité de commande (ʻoq), le délai de réapprovisionnement ( lt) et la quantité de stock initiale sont spécifiés comme arguments lors de la création d'une instance du modèle de simulation. Quatre (ʻinit`) ont été ajoutés. De plus, la variable d'état «count» a été supprimée, et à la place, une liste de l'inventaire en vente libre («à la main»), le nombre de pertes d'opportunité («loss») et les commandes non réapprovisionnées (commandes en souffrance) («commandes») a été ajoutée. A été fait. De plus, on peut voir qu'un événement d'arrivée est ajouté au calendrier des événements au début. Il s'agit de l'événement correspondant à la première visite du client.

En regardant les méthodes, nous pouvons voir que ʻadd_arrival () et ʻadd_fill_up () jouent un rôle dans l'ajout de nouveaux événements d'arrivée et fill_up au calendrier des événements, respectivement. sell_or_apologize () décrit comment traiter avec les clients qui viennent au magasin (en vendre un si en stock, ajouter une perte d'opportunité sinon). Par contre, fill_up () est une méthode correspondant au remplissage. stocktake () permet de vérifier le niveau de stock (total y compris la commande en souffrance), de décider de passer une commande selon les règles de la méthode de commande à quantité fixe et de commander une quantité prédéterminée si nécessaire. Cela correspond.

Dans la méthode run (), le traitement lorsqu'un événement se produit est légèrement modifié en fonction du système cible cette fois. Plus précisément, si l'événement qui s'est produit est un événement fill_up, la méthode fill_up () est simplement appelée. S'il s'agit d'un événement d'arrivée, effectuez le support client (méthode sell_or_apologize ()), ajoutez l'événement d'arrivée suivant au calendrier des événements, puis vérifiez le niveau de stock et déterminez la commande (méthode stocktake ()). Vous pouvez voir qu'il y en a. Si une commande est passée ici, l'événement fill_up correspondant à la commande est ajouté au calendrier des événements.

Par exemple, exécutez ce modèle avec la période de simulation horizon = 200, le point de commande ʻop = 10, la quantité de commande ʻoq = 20, le délai de réapprovisionnement lt = 10 et la quantité initiale de stock ʻinit = 20`. Pour le voir, exécutez le code ci-dessous.

model = Model(200, 10, 20, 10, 20)  # horizon, op, oq, lt, init
model.run()

Acquisition de journal et affichage graphique simple

Comme il est difficile de comprendre si le résultat de la simulation s'écoule simplement à l'écran sous forme de chaîne de caractères, envisagez de l'afficher dans un graphique. À cette fin, introduisons d'abord une classe de journal simple. Ce type de classe sera également utile lorsque vous souhaitez écrire les résultats de la simulation dans un fichier csv ou autre.

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

Pour le moment, pour chaque fois que l'événement se produit, les valeurs du montant de l'inventaire du magasin (ʻat_hand), le nombre d'occurrences de perte d'opportunité (loss) et le niveau de stock total ( total) à ce moment-là sont stockés dans le journal. J'irai. Préparez une liste pour stocker les valeurs de chacune de ces variables et implémentez la méthode ʻextend (model) pour ajouter la valeur de chaque variable de model à ce moment-là à la liste correspondante. Vous pouvez voir qu'il y en a. La méthode plot_log () est une méthode pour afficher le résultat sous forme de graphique linéaire, mais les détails sont omis ici.

Ajoutons ceci au modèle créé ci-dessus. J'ai ajouté seulement 3 lignes, mais la classe Model4Plot modifiée est affichée ci-dessous (elle hérite de la classe Model ci-dessus, et les 3 lignes ajoutées reçoivent un commentaire pour le clarifier).

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

Avec cela, l'état du système pendant la simulation sera enregistré dans le journal (model.log) en séquence. En conséquence, après l'exécution de la simulation, il est devenu possible d'utiliser ces données pour afficher la transition de l'inventaire du magasin dans un graphique en traits interrompus, par exemple, avec le code suivant.

import matplotlib.pyplot as plt

model = Model4Plot(200, 10, 20, 10, 20)  # horizon, op, oq, lt, init
model.run()
model.log.plot_log()

Problème de pratique

Enfin, essayons de créer un modèle de simulation de système simple basé sur le squelette présenté ci-dessus. L'objectif de cette fois est le système suivant.

Sommaire

Cette fois, nous avons présenté le mécanisme de base de la simulation d'événements discrets et un exemple de mise en œuvre simple. Comme mentionné dans "Introduction", je voudrais vous présenter le module SimPy de la prochaine fois (qui n'apparaît pas cette fois).

Lien

Recommended Posts

Introduction à la simulation d'événements discrets à l'aide de Python # 1
Introduction à la simulation d'événements discrets à l'aide de Python # 2
Introduction au langage Python
Introduction à OpenCV (python) - (2)
Publier sur Twitter en utilisant Python
Commencez à Selenium en utilisant python
Introduction à la communication série [Python]
[Introduction à Python] <liste> [modifier le 22/02/2020]
Introduction à Python (version Python APG4b)
Une introduction à la programmation Python
[Introduction à Python] Comment arrêter la boucle en utilisant break?
Introduction à discord.py (3) Utilisation de la voix
[Introduction à Python] Comment écrire des instructions répétitives à l'aide d'instructions for
Introduction à Python pour, pendant
[Livre technique] Introduction à l'analyse de données avec Python -1 Chapitre Introduction-
[Introduction à Python] Comment écrire des branches conditionnelles en utilisant des instructions if
[Python] Introduction à la création de graphiques à l'aide de données de virus corona [Pour les débutants]
[Présentation de l'application Udemy Python3 +] 58. Lambda
[Présentation de l'application Udemy Python3 +] 31. Commentaire
Introduction à la bibliothèque de calcul numérique Python NumPy
Entraine toi! !! Introduction au type Python (conseils de type)
[Introduction à Python3 Jour 1] Programmation et Python
[Introduction à Python] <numpy ndarray> [modifier le 22/02/2020]
[Présentation de l'application Udemy Python3 +] 57. Décorateur
Introduction à Python Hands On Partie 1
[Introduction à Python3 Jour 13] Chapitre 7 Chaînes de caractères (7.1-7.1.1.1)
[Introduction à Python] Comment analyser JSON
[Présentation de l'application Udemy Python3 +] 56. Clôture
[Introduction à Python3 Jour 14] Chapitre 7 Chaînes de caractères (7.1.1.1 à 7.1.1.4)
Introduction à Protobuf-c (langage C ⇔ Python)
[Présentation de l'application Udemy Python3 +] 59. Générateur
Utiliser Cloud Storage depuis Python3 (Introduction)
[Introduction à Python3 Jour 15] Chapitre 7 Chaînes de caractères (7.1.2-7.1.2.2)
[Introduction à Python] Utilisons les pandas
[Introduction à Python] Utilisons les pandas
[Introduction à l'application Udemy Python3 +] Résumé
Introduction à l'analyse d'image opencv python
[Introduction à Python] Utilisons les pandas
Premiers pas avec Python pour les non-ingénieurs
Introduction à Python Django (2) Édition Mac
[AWS SAM] Présentation de la version Python
[Introduction à Python3 Day 21] Chapitre 10 Système (10.1 à 10.5)
[Tutoriel Python] Une introduction facile à Python
Python: Introduction à Flask: création d'une application d'identification de numéro à l'aide de MNIST
[Introduction à Udemy Python3 + Application] 18. Méthode List
[Introduction à Udemy Python3 + Application] 63. Notation d'inclusion du générateur
[Python] Simulation de fluide: de linéaire à non linéaire
[Introduction à l'application Udemy Python3 +] 28. Type collectif
[Introduction à Python] Comment utiliser la classe en Python?
De Python à l'utilisation de MeCab (et CaboCha)
[Introduction à Udemy Python3 + Application] 25. Méthode de type dictionnaire
[Introduction à l'application Udemy Python3 +] 33. instruction if
[Introduction à Udemy Python3 + Application] 13. Méthode de caractères
[Introduction à Python3, jour 17] Chapitre 8 Destinations de données (8.1-8.2.5)
[Introduction à l'application Udemy Python3 +] 55. Fonctions intégrées
[Introduction à l'application Udemy Python3 +] 48. Définition des fonctions
[Introduction à Python3, jour 17] Chapitre 8 Destinations de données (8.3-8.3.6.1)
Super Introduction Arithmétique Bit Python
[Introduction à l'application Udemy Python3 +] 10. Valeur numérique
Introduction au remplissage d'image Python Remplissage d'image à l'aide d'ImageDataGenerator
Web-WF Python Tornado Partie 3 (Introduction à Openpyexcel)