Data Structures and Algorithms using Python
Copyright © 2023 by Rance Necaise
Table Of Contents
Data Structures and Algorithms using Python
Copyright © 2023
by Rance Necaise

8.8 Simulation Implementation

We can design and implement a discrete event computer simulation to analyze the average time passengers have to wait for service at an airport ticket counter. The simulation will involve multiple ticket agents serving customers who have to wait in line until they can be served. Our design will be an object-oriented solution with multiple classes.

System Parameters

The program will prompt the user for the queuing system parameters:

  Number of minutes to simulate: 25
  Number of ticket agents: 2
  Average service time per passenger: 3
  Average time between passenger arrival: 2

For simplicity we use minutes as the discrete time units. This would not be sufficient to simulate a real ticket counter as multiple passengers are likely to arrive within any given minute. The program will then perform the simulation and produce the following output:

  Number of passengers served =  12
  Number of passengers remaining in line = 1
  The average wait time was 1.17 minutes.

We will also have the program display event information, which can be used to help debug the program. The debug information lists each event that occurs in the system along with the time the events occur. For the input values shown above, the event information displayed will be:

  Time   2: Passenger 1 arrived.
  Time   2: Agent 1 started serving passenger 1.
  Time   3: Passenger 2 arrived.
  Time   3: Agent 2 started serving passenger 2.
  Time   5: Passenger 3 arrived.
  Time   5: Agent 1 stopped serving passenger 1.
  Time   6: Agent 1 started serving passenger 3.
  Time   6: Agent 2 stopped serving passenger 2.
  Time   8: Passenger 4 arrived.
  Time   8: Agent 2 started serving passenger 4.
  Time   9: Agent 1 stopped serving passenger 3.
  Time  10: Passenger 5 arrived.
  Time  10: Agent 1 started serving passenger 5.
  Time  11: Passenger 6 arrived.
  Time  11: Agent 2 stopped serving passenger 4.
  Time  12: Agent 2 started serving passenger 6.
  Time  13: Passenger 7 arrived.
  Time  13: Agent 1 stopped serving passenger 5.
  Time  14: Passenger 8 arrived.
  Time  14: Agent 1 started serving passenger 7.
  Time  15: Passenger 9 arrived.
  Time  15: Agent 2 stopped serving passenger 6.
  Time  16: Agent 2 started serving passenger 8.
  Time  17: Agent 1 stopped serving passenger 7.
  Time  18: Passenger 10 arrived.
  Time  18: Agent 1 started serving passenger 9.
  Time  19: Passenger 11 arrived.
  Time  19: Agent 2 stopped serving passenger 8.
  Time  20: Agent 2 started serving passenger 10.
  Time  21: Agent 1 stopped serving passenger 9.
  Time  22: Agent 1 started serving passenger 11.
  Time  23: Passenger 12 arrived.
  Time  23: Agent 2 stopped serving passenger 10.
  Time  24: Agent 2 started serving passenger 12.
  Time  25: Passenger 13 arrived.
  Time  25: Agent 1 stopped serving passenger 11.

Passenger Class

First, we need a class to store information related to a single passenger. We create a Passenger class for this purpose. The class will contain two data fields. The first is an identification number used in the output of the event information. The second field records the time the passenger arrives. This value will be needed to determine the length of time the passenger waited in line before beginning service with an agent. Methods are also provided to access the two data fields. An instance of the class is illustrated in Figure 8.8.1.

Figure 8.8.1: Sample Passenger object.

The complete implementation of this class is provided in the source listing below.

Program Listing
Program: customer.py
  1. # Used to store and manage information related to an airline passenger.
  2. class Passenger :
  3.    # Creates a passenger object.
  4.   def __init__(self, idNum, arrivalTime) :
  5.     self._idNum = idNum
  6.     self._arrivalTime = arrivalTime
  7.  
  8.    # Gets the passenger's id number.
  9.   def idNum(self) :
  10.     return self._idNum
  11.    
  12.    # Gets the passenger's arrival time.
  13.   def timeArrived(self) :
  14.     return self._arrivalTime

Ticket Agent Class

We also need a class to represent and store information related to the ticket agents. The information includes an agent identification number and a timer to know when the transaction will be completed. This value is the sum of the current time and the average time of the transaction as entered by the user. Finally, we need to keep track of the current passenger being served by the agent in order to identify the specific passenger when the transaction is completed. An instance of the class is shown in Figure 8.8.2

Figure 8.8.2: Sample TicketAgent object.

and the TicketAgent class is implemented in the listing below.

Program Listing
Program: server.py
  1. # Used to store and manage information related to an airline ticket agent.
  2. class TicketAgent :
  3.    # Creates a ticket agent object.
  4.   def __init__(self, idNum) :
  5.     self._idNum = idNum
  6.     self._passenger = None
  7.     self._stopTime = -1
  8.    
  9.    # Gets the ticket agent's id number.
  10.   def idNum(self) :
  11.     return self._idNum
  12.    
  13.    # Determines if the ticket agent is free to assist a passenger.
  14.   def isFree(self) :
  15.     return self._passenger is None
  16.      
  17.    # Determines if the ticket agent has finished helping the passenger.
  18.   def isFinished(self, curTime) :
  19.     return self._passenger is not None and self._stopTime == curTime
  20.    
  21.    # Indicates the ticket agent has begun assisting a passenger.
  22.   def startService(self, passenger, stopTime) :
  23.     self._passenger = passenger
  24.     self._stopTime = stopTime
  25.    
  26.    # Indicates the ticket agent has finished helping the passenger.
  27.   def stopService(self) :
  28.     thePassenger = self._passenger
  29.     self._passenger = None
  30.     return thePassenger

The _passenger field is set to a null reference, which will be used to flag a free agent. The idNum method simply returns the id assigned to the agent when the object is created while the isFree method examines the _passenger field to determine if the agent is free. The isFinished method is used to determine if the passenger currently being served by this agent has completed her transaction. This method only flags the transaction as having been completed. To actually end the transaction, stopService must be called. stopService sets the _passenger field to None to again indicate the agent is free and returns the passenger object. To begin a transaction, startService is called, which assigns the appropriate fields with the supplied arguments.

Simulation Class

Finally, we construct the TicketCounterSimulation class to manage the actual simulation. This class will contain the various components, methods, and data values required to perform a discrete event simulation. A sample instance is illustrated in Figure 8.8.3.

Figure 8.8.3: A sample TicketCounterSimulation object.

The first step in the constructor is to initialize three simulation parameters. Note the _arriveProb is the probability of a passenger arriving during the current time step using the formula described earlier. A queue is created, which will be used to represent the line in which passengers must wait until they are served by a ticket agent. The ticket agents are represented as an array of \class{Agent} objects. The individual objects are instantiated and each is assigned an id number, starting with 1. Two data fields are needed to store data collected during the actual simulation. The first is the summation of the time each passenger has to wait in line before being served, and the second keeps track of the number of passengers in the simulation. The latter will also be used to assign an id to each passenger.

The simulation is performed by calling the run method, which simulates the ticking of the clock by performing a count-controlled loop keeping track of~the current time in curTime. The loop executes until numMinutes have elapsed. The events of the simulation are also performed during the terminating minute, hence, the need for the _numMinutes + 1 in the range function. During each iteration of the loop, the three simulation rules outlined earlier are handled by the respective _handleXYZ helper methods. The _handleArrival method determines if a passenger arrives during the current time step and handles that arrival. _handleBeginService determines if any agents are free and allows the next passenger(s) in line to begin their transaction. The _handleEndService determines which of the current transactions have completed, if any, and flags a passenger departure. A partial implemenation of the Simulation class is provided below. The implementation of the helper methods is left as an exercise.

Program Listing
Program: simulation.py
  1. # Implementation of the main simulation class.
  2. from ezarrays import Array
  3. from llistqueue import Queue
  4. from simpeople import TicketAgent, Passenger
  5.  
  6. class TicketCounterSimulation :
  7.    # Create a simulation object.
  8.   def __init__(self, numAgents, numMinutes, betweenTime, serviceTime) :
  9.      # Parameters supplied by the user.
  10.     self._arriveProb = 1.0 / betweenTime    
  11.     self._serviceTime = serviceTime
  12.     self._numMinutes = numMinutes            
  13.      
  14.      # Simulation components.
  15.     self._passengerQ = Queue()  
  16.     self._theAgents = Array( numAgents )    
  17.     for i in range( numAgents ) :
  18.       self._theAgents[i] = TicketAgent(i+1)
  19.    
  20.      # Computed during the simulation.
  21.     self._totalWaitTime = 0
  22.     self._numPassengers = 0
  23.    
  24.    # Run the simulation using the parameters supplied earlier.
  25.   def run(self) :
  26.     for curTime in range(self._numMinutes + 1) :
  27.       self._handleArrival( curTime )
  28.       self._handleBeginService( curTime )      
  29.       self._handleEndService( curTime )
  30.  
  31.    # Print the simulation results.
  32.   def printResults(self) :
  33.     numServed = self._numPassengers - len(self._passengerQ)
  34.     avgWait = float( self._totalWaitTime ) / numServed
  35.     print("")
  36.     print("Number of passengers served = ", numServed)
  37.     print("Number of passengers remaining in line = %d" %
  38.            len(self._passengerQ))
  39.     print("The average wait time was %4.2f minutes." % avgWait)
  40.    
  41.   # The remaining methods that have yet to be implemented.
  42.   # def _handleArrive( curTime ):        # Handles simulation rule #1.
  43.   # def _handleBeginService( curTime ):  # Handles simulation rule #2.
  44.   # def _handleEndService( curTime ):    # Handles simulation rule #3.

After running the simulation, the printResults method is called to print the results. When the simulation terminates there may be some passengers remaining in the queue who have not yet been assisted. Thus, we need to determine how many passengers have exited the queue, which indicates the number of passenger wait times included in the _totalWaitTime field. The average wait time is simply the total wait time divided by the number of passengers served.

The last component of our program is the driver module, which is left as an exercise. The driver extracts the simulation parameters from the user and then creates and uses a TicketCounterSimulation object to perform the simulation. To produce the same results shown earlier, you will need to seed the random number generator with the value 4500 before running the simulation:

random.seed( 4500 )

In a typical experiment, a simulation is performed multiple times varying the parameters with each execution. Table 8.8.1 illustrates the results of a single experiment with multiple executions of the simulation. Note the significant change in the average wait time when increasing the number of ticket agents by one in the last two sets of experiments.

Num
Minutes
Num
Agents
Average
Service
Time
Between
Average
Wait
Passengers
Served
Passengers
Remaining
100 2 3 2 2.49 49 2
500 2 3 2 3.91 240 0
1000 2 3 2 10.93 490 14
5000 2 3 2 15.75 2459 6
10000 2 3 2 21.17 4930 18
100 2 4 2 10.60 40 11
500 2 4 2 49.99 200 40
1000 2 4 2 95.72 400 104
5000 2 4 2 475.91 2000 465
10000 2 4 2 949.61 4000 948
100 3 4 2 0.51 51 0
500 3 4 2 0.50 240 0
1000 3 4 2 1.06 501 3
5000 3 4 2 1.14 2465 0
10000 3 4 2 1.21 4948 0
Table 8.8.1: Sample results of the ticket counter simulation experiment.
Page last modified on August 29, 2023, at 09:27 PM