In [2]:
from mytypes import Queue
In [3]:
# A class to represent a single customer in an M/D/1 queue simulation.
# Each customer has three attributes:
#
#  - cid: A customer identifier (can be anything, but we will use consecutive integers)
#  - arrival_time: The time at which the customer arrived at the queue
#  - departure_time: The time at which the customer departed the queue
class Customer(object):
    def __init__(self, cid, arrival_time):
        self.cid = cid
        self.arrival_time = arrival_time
        self.departure_time = None
        
    def get_wait(self):
        if self.departure_time is None:
            return None
        else:
            return self.departure_time - self.arrival_time
        
    def __str__(self):
        return "Customer({}, {})".format(self.cid, self.arrival_time)
    
    def __repr__(self):
        return str(self)
In [6]:
q = Queue()
In [7]:
q.enqueue(Customer(1, 10))
Out[7]:
True
In [8]:
q.enqueue(Customer(2, 15))
Out[8]:
True
In [9]:
q.enqueue(Customer(3, 18))
Out[9]:
True
In [11]:
q
Out[11]:
 --> Customer(3, 18) --> Customer(2, 15) --> Customer(1, 10) -->
In [12]:
import random

# simulate_md1: Simulates an M/D/1 queue.
#
# In an M/D/1 queue que have:
#   
# - Arrivals follow a Markov process (M)
# - The time to service each customer is deterministic (D)
# - There is only one server (1)
#
# The function takes three parameters (plus one optional parameter)
#
# - lambd: The simulation uses an exponential distribution to determine
#          the arrival time of the next customer. This parameters is the
#          lambda parameter to an exponential distribution (specifically,
#          Python's random.expovariate)
# - mu: The rate at which customers are serviced. The larger this value is,
#       the more customers will be serviced per unit of time
# - max_time: The maximum time of the simulation
# - verbosity (optional): Can be 0 (no output), 1 (print state of the queue
#                         at each time), or 2 (same as 1, but also print when
#                         each customer arrives and departs)
#
# The function returns two lists: one with all the customers that were served
# during the simulation, and one with all the customers that were yet to be
# served when the simulation ended.
#
def simulate_md1(lambd, mu, max_time, verbosity = 0):
    md1 = Queue()

    # Our return values: the list of customers that have been
    # served, and the list of customers that haven't been served
    served_customers = []
    unserved_customers = []
    
    # The type of simulation we have implemented in this function
    # is known as a "discrete event simulation"
    # (https://en.wikipedia.org/wiki/Discrete_event_simulation), where
    # we simulate a discrete sequence of events: customer arrivals
    # and customer departures. So, we only need to keep track of when 
    # the next arrival and the next departure will take place (because 
    # nothing interesting happens between those two types of events). 
    # Then, in each step of the simulation, we simply advance the 
    # simulation clock to earliest next event. Note that, because
    # we have a single server, this can be easily done with just
    # two variables.

    next_arrival = random.expovariate(lambd)
    next_service = next_arrival + 1/mu
        
    # We initialize the simulation's time to the earliest event:
    # the next arrival time
    t = next_arrival
    
    # We will number customers starting from 1
    cid = 1
    
    while t < max_time:

        # Process a new arrival
        if t == next_arrival:
            customer = Customer(cid, arrival_time = t)
            cid += 1
            md1.enqueue(customer)

            if verbosity >= 2:
                print("{:10.2f}: Customer {} arrives".format(t, customer.cid))

            next_arrival = t + random.expovariate(lambd)
            
        # The customer at the head of the queue has been served
        if t == next_service:
            done_customer = md1.dequeue()
            done_customer.departure_time = t
            
            served_customers.append(done_customer)

            if verbosity >= 2:
                print("{:10.2f}: Customer {} departs".format(t, done_customer.cid))            
            
            if md1.is_empty():
                # The next service time will be 1/mu after the next arrival
                next_service = next_arrival + 1/mu
            else:
                # We start serving the next customer, so the next service time
                # will be 1/mu after the current time.
                next_service = t + 1/mu
            
        if verbosity >= 1:
            print("{:10.2f}: {}".format(t, "#"*md1.length))
            
        # Advance the simulation clock to the next event
        t = min(next_arrival, next_service)
        
    # Any remaining customers in the queue haven't been served
    while not md1.is_empty():
        unserved_customers.append(md1.dequeue())
        
    return served_customers, unserved_customers
In [13]:
simulate_md1(0.167, 0.15, 100, verbosity=2)
      0.10: Customer 1 arrives
      0.10: #
      3.49: Customer 2 arrives
      3.49: ##
      6.77: Customer 1 departs
      6.77: #
     13.44: Customer 2 departs
     13.44: 
     19.31: Customer 3 arrives
     19.31: #
     23.59: Customer 4 arrives
     23.59: ##
     25.98: Customer 3 departs
     25.98: #
     26.40: Customer 5 arrives
     26.40: ##
     28.07: Customer 6 arrives
     28.07: ###
     32.65: Customer 4 departs
     32.65: ##
     39.31: Customer 5 departs
     39.31: #
     45.98: Customer 6 departs
     45.98: 
     53.44: Customer 7 arrives
     53.44: #
     54.26: Customer 8 arrives
     54.26: ##
     60.11: Customer 7 departs
     60.11: #
     66.77: Customer 8 departs
     66.77: 
     70.43: Customer 9 arrives
     70.43: #
     74.88: Customer 10 arrives
     74.88: ##
     77.10: Customer 9 departs
     77.10: #
     82.19: Customer 11 arrives
     82.19: ##
     83.77: Customer 10 departs
     83.77: #
     90.43: Customer 11 departs
     90.43: 
Out[13]:
([Customer(1, 0.1036267965248641),
  Customer(2, 3.488284952674854),
  Customer(3, 19.313239255083378),
  Customer(4, 23.591705913242922),
  Customer(5, 26.403869696849995),
  Customer(6, 28.072379745541365),
  Customer(7, 53.43959663857426),
  Customer(8, 54.26090858431276),
  Customer(9, 70.43300741049345),
  Customer(10, 74.87835785798578),
  Customer(11, 82.18901165222695)],
 [])