Paul Grogan’s DES Example and Its Implementation in Ciw

Simulation
Discrete-Event-Simulation
Python
Ciw
Author

Galen Seilis

Published

June 13, 2024

I watched a YouTube video over a year ago that showed an implementation of a specific discrete event simulation. Here is the video:

I typed out the implementation in that video, and added some docstrings and type annotations.

import numpy as np


class Simulation:
    def __init__(self):
        """
        Initialize the simulation with default parameters.
        """
        self.num_in_system: int = 0
        self.clock: float = 0.0
        self.t_arrival: float = self.generate_interarrival()
        self.t_depart: float = float("inf")
        self.num_arrivals: int = 0
        self.num_departs: int = 0
        self.total_wait: float = 0.0

    def advance_time(self) -> None:
        """
        Advance the simulation time to the next event (either arrival or departure).
        """
        t_event: float = min(self.t_arrival, self.t_depart)
        self.total_wait += self.num_in_system * (t_event - self.clock)
        self.clock = t_event

        if self.t_arrival <= self.t_depart:
            self.handle_arrival_event()
        else:
            self.handle_departure_event()

    def handle_arrival_event(self) -> None:
        """
        Handle an arrival event in the simulation.
        """
        self.num_in_system += 1
        self.num_arrivals += 1
        if self.num_in_system <= 1:
            self.t_depart = self.clock + self.generate_service()
        self.t_arrival = self.clock + self.generate_interarrival()

    def handle_departure_event(self) -> None:
        """
        Handle a departure event in the simulation.
        """
        self.num_in_system -= 1
        self.num_departs += 1
        if self.num_in_system > 0:
            self.t_depart = self.clock + self.generate_service()
        else:
            self.t_depart = float("inf")

    def generate_interarrival(self) -> float:
        """
        Generate the time until the next arrival event using an exponential distribution.
        """
        return np.random.exponential(1 / 3)

    def generate_service(self) -> float:
        """
        Generate the service time for an entity using an exponential distribution.
        """
        return np.random.exponential(1 / 4)

We can then initialize the simulation and run it for discrete units of time like this:

from .simulation import Simulation


def main() -> None:
    S = Simulation()

    for _ in range(2018):
        S.advance_time()


if __name__ == "__main__":
    main()

Grogan’s implementation is a pedagogical example that can be quickly coded and explained. What can we use for prototyping and using something more fleshed out?

The Ciw package is a ready-to-use discrete event simulation package for queueing networks. It covers a variety of cases, including a similar simulation:

import ciw

# Define the parameters for the arrival and service processes
arrival_rate = 3  # Interarrival rate (lambda)
service_rate = 4  # Service rate (mu)

# Define the parameters for the Ciw network
network = ciw.create_network(
    arrival_distributions=[ciw.dists.Exponential(arrival_rate)],
    service_distributions=[ciw.dists.Exponential(service_rate)],
    number_of_servers=[1],
)

# Create and run the simulation
ciw.seed(2018)
sim = ciw.Simulation(network)
sim.simulate_until_max_time(2018)