It is often the case that we want to evaluate the behavior of a system before we actually build it. For example, the design of a new aircraft or computer is studied extensively before it is built, since prototypes can cost hundreds of millions of dollars to create.
Computers are used extensively as simulation tools. That is, a computer program is written that simulates, in great detail, the behavior of a system being designed. Many simulations are event-driven--they model the behavior of a systems over time as various events occur. For example, an engineer might simulate the effect of an automobile crash, second by second, estimating the severity of the impact on passengers.
We'll focus on a simpler, yet important simulation problem--ticket distribution at the Kohl Center. Assume that there are two ticket sellers on duty. There are two ways of organizing ticket sales. We may decide to have one waiting line. You enter at the back of the line, and when you reach the front, your order is handled by whichever ticket seller is free. Alternatively, we may decide to have two waiting lines, one for each ticket seller. When you enter, you choose the waiting line with fewer people. If both lines have the same number of people in them, you choose line #1 rather than line #2 (because line #1 is closer to the door). When you reach the head of the line you are in, you are served by the ticket seller assigned to your line.
Your task is to decide, using event-driven simulation, which organization is superior (or whether the two schemes are equivalent). The scheme that produces the shorter average waiting time, from arrival to service by a ticket seller, will be selected.
To implement your simulation you'll need two major data structures. The first, the event queue will be a priority queue, ordered by the time a given event occurs. The head of the event queue will be the next event to occur in the simulation. The time associated with the current event (at the head of the event queue) will be the current time. The other data structure you will need is a waiting line implemented as an ordinary queue. The waiting line (you'll use one or two depending on the organization you're simulating) will hold customers waiting for service by a ticket seller.
There will be two kinds of events in our simulation, an arrival event and a departure event. An arrival event represents a ticket buyer arriving at the ticket office. A departure event represents the situation in which a buyer has completed a ticket purchase; a seller becomes free and can handle the next buyer.
To start things off, you'll enter an arrival event in the event queue at time 0. This represents the first buyer who arrives when the ticket office opens. The simulation proceeds by removing the next event to be processed, which is at the head of the event queue (the event queue never becomes empty, so the next event always exists). The current time is always the time associated with the event currently being processed.
If the current event is an arrival event, we must simulate a customer entering the ticket office. The buyer will be handled immediately if there is a ticket seller free. This is the ideal case, since the buyer sees a zero waiting time. The buyer is assigned to the free ticket seller. A departure event is created for the buyer, to simulate his departure when the transaction is completed. We don't know exactly how long the transaction will take, so we call a method service(mean) in class NextEvent. This method will return a double that represents how many minutes it will take to service the current ticket purchase. The departure time for the current customer will be currentTime + NextEvent.service(mean). The parameter mean is a double that represents the mean (or average) service time for transactions. That is, service(mean) will return random values, but over many calls these values will average to the parameter mean.
If no ticket seller is free when a buyer arrives, that buyer is added to a waiting line. The arrival time of the buyer is recorded in the waiting line (so that we can later determine how long the buyer had to wait). The buyer will eventually be handled when he arrives at the head of the waiting line and a ticket seller becomes free.
Whenever an arrival event is removed from the event queue, a new arrival event is added to the event queue (to simulate the next buyer to arrive). The time the next arrival event occurs is determined as follows. Recall that the current time is the time associated with the event currently being processed. We'll call a method arrival(mean) in class NextEvent. This method will return a double that represents how many minutes from now the next ticket buyer will arrive. Hence the next arrival time is currentTime + NextEvent.arrival(mean). The parameter mean is a double that represents the mean (or average) time between arrivals. That is, arrival(mean) will return random values, but over many calls these values will average to the parameter mean.
When a departure event is removed from the event queue, you will choose the next customer to be served (from the head of a waiting line). The waiting time for the customer selected is the current time (when the ticket seller became free) minus the time the buyer arrived and was placed in a waiting line. You will create a departure event for this buyer at a time equal to currentTime + NextEvent.service(mean), and add this event to the event queue.
If there are no customers waiting when a departure event is processed, you do nothing. The ticket seller will remain idle until the next customer arrives.
During a simulation, a variety of events must be recognized and simulated, each at the correct time. In order to see what is happening (for testing, debugging or grading purposes), it is essential that you include a "trace" option. When tracing is enabled, you should produce a detailed, step by step, trace of the events that have occurred. For example, the following illustrates the first few steps of a simulation of ticket selling using one waiting line:
1 arrives at 0.0 1 is served immediately by server #1 2 arrives at 0.97215474 2 is served immediately by server #2 3 arrives at 0.98274976 3 is added to waiting line. 2 departs at 1.1292366; server 2 is free. 3 is assigned to #2; wait time was 0.14648677 3 departs at 1.2134275; server 2 is free. 1 departs at 1.734944; server 1 is free. 4 arrives at 2.4977942 4 is served immediately by server #1 5 arrives at 2.8812723 5 is served immediately by server #2 5 departs at 2.896563; server 2 is free. 4 departs at 3.4023006; server 1 is free.Each customer is numbered and the time at which each event occurs is shown. Using such a trace, it is possible to verify that simulation steps are properly implemented (or to find bugs). You need not follow the above format exactly, but your trace should be clear and easily readable; don't clutter it unnecessarily. Before you hand in final results, be sure to look at least at a portion of your trace to be sure that individual events look reasonable and that all expected events are properly processed. A significant fraction of your grade on this assignment will be based on the quality of your trace.
Our goal is to determine whether it is better to organize ticket sales at the Kohl Center using one waiting line or two (we'll have two ticket sellers in either case). We will measure average waiting time for customers: the time a customer spends waiting in line, averaged over many customers. As you might expect, the more customers we simulate, the more accurate our estimate of average waiting time will be. We'll terminate each of our simulations after 2500 customers have been processed. This number is large enough that random variations in arrival and service times will be "averaged out" and yet is small enough to require only a few seconds of execution time.
In all simulations, we'll assume that the average service time for a ticket purchase is two minutes. Therefore NextEvent.service(2.0) will always be called to simulate the time it takes to purchase a ticket. We'll want to get a sense of how well each organization of the ticket office behaves under a variety of customer loads. To simulate a light customer load, we'll assume an average time between customer arrivals of 3 minutes. That is, NextEvent.arrival(3.0) will be called to simulate when the next arrival will occur.
To simulate a moderate customer load, we'll assume an average time between customer arrivals of 2 minutes. Therefore, NextEvent.arrival(2.0) will be called to simulate when the next arrival will occur. To simulate a heavy customer load, we'll assume an average time between customer arrivals of 1.1 minutes. For heavy loads NextEvent.arrival(1.1) will be called to simulate when the next arrival will occur. Note that when arrivals occur at one per minute (or more frequently), average waiting times cannot be accurately simulated, since the ticket sellers will be unable to keep up with buyers, and waiting lines will grow longer and longer (sound familiar?).
We'll also want to see examples of your tracing capabilities. If your simulator is called with the test option on the command line:
java KohlCenterSimulation testthen you should run two short simulations with tracing enabled: a one line simulation for 10 customers using a 1.1 minute mean arrival time and a two line simulation for 10 customers using a 1.1 minute mean arrival time.
We will provide a classs Queue that implements the following operations
public void enqueue(Object x); public Object getFront() throws Underflow; public Object dequeue() throws Underflow; public boolean isEmpty(); public int length(); // How many Objects are in the Queue?as well as classs Underflow.
We will also provide a class PriorityQueue that implements the following operations
public void insert(Object x, double priority); public Object findMin() throws Underflow; public double findMinPriority() throws Underflow; public Object deleteMin() throws Underflow; public boolean isEmpty();Note that findMin returns the Object at the head of the PriorityQueue without removing it; deleteMin removes (and returns) the Object at the head of the PriorityQueue. findMinPriority returns the priority of the Object at the head of the PriorityQueue. In our simulations priority will correspond to time, so the Object with the smallest priority will correspond to the event with the smallest time (and hence the next event to simulate). If two objects are entered into the PriorityQueue with the same priority, the objects will be handled in "first in, first out" order. This is exactly the same way that objects in ordinary queues are handled.
Finally, we will also provide the class NextEvent that contains the methods
public double arrival(double meanArrivalTime); public double service(double meanServiceTime);
Hand in your simulation program electronically.