Spring 2006 CS302 -- Lab 6 -- Banking Simulation


Lab Objectives

This lab is designed to give you experience with


The Bank Simulation

We are confronted with the following problem. We are bank executives, and we need to hire tellers. We want to hire enough tellers that customers don't have to wait in line too long. However, we don't want to hire too many tellers, because we want to save money. Thus, we want to hire an optimal number of tellers.

One way to do this is to write a program to simulate a bank. To do this, we need to know the approximate times when customers arrive and how long their transactions take. Then we can write a simulator that randomly generates people coming into a bank doing transactions, and we can see how the number of tellers impacts how long people wait, and how long tellers are idle. This will let us make a decision about the optimal number of tellers.

Now, suppose we run the following simulation (times are in minutes):

New Person:    0 enters the bank at    1.5. Transaction time:    5.7
New Person:    1 enters the bank at    2.8. Transaction time:    1.9
New Person:    2 enters the bank at    3.3. Transaction time:    8.7
New Person:    3 enters the bank at    9.2. Transaction time:    2.7
We have two tellers, and four people.

What happens is that the simulator generates four random people that enter the bank at 1.5, 2.8, 3.3, and 9.2 minutes. Their transactions take 5.7, 1.9, 8.7, and 2.7 minutes respectively. Given these parameters and two tellers, the simulation will go as follows:

Note, we can easily calculate the average waiting time for people -- zero for persons 0, 1, and 3, and 1.4 minutes for person 2, so the average waiting time is .35 minutes. We can also calculate the tellers' idle time. Teller 1 waits 1.5 minutes for the first person and 2 minutes for the second person. Teller 2 waits 2.8 minutes for the first person and 0 minutes for the second person. Depending on the length of the simulation, we might also say that Teller 1 waits for some amount of time after their second person leaves. In this case let's assume that the bank "closes" at 12.0 minutes (meaning that the simulation will not generate any more persons after 12.0 minutes). Then Teller 1 waits for .1 minutes after the second person leaves. The idle time for teller 1 is 3.6 minutes and for teller 2 is 2.8 minutes. The average idle time is 3.2 minutes.

Suppose we changed the situation so that there are three tellers. Now person 2 will not have to wait as they can be immediately served by teller 3. Persons 0, 1, and 4 still will not have to wait. The average waiting time therefore drops to 0 minutes. However, the teller idle time goes way up. Assuming that Teller 1 handles persons 0 and 3, then Teller 1 is idle from 0 to 1.5, 7.2 to 9.2, and 11.9 to 12.0, for a total of 4.1 minutes. Teller 2 is idle from 0 to 2.8 and then from 4.7 to 12 for a total of 10.1 minutes. Teller 3 is idle from 0 to 3.3 for a total of 3.3 minutes. The average idle time climbs from 3.2 minutes to 5.83 minutes. That's half the time the bank is open and would probably be considered wasteful by the bank's executives.

Hopefully, at this point, you understand the basics of the simulation -- why we're writing it, and what the input and output is like. Now we get into details.


The Program

The goal of the simulation is to determine the minimum number of tellers required in order to ensure that no more than x% of customers have to wait more than y seconds in line. For example, you might want to find the minimum number of tellers required so that no more than 5% of customers have to wait more than 120 seconds in line. Once you have completed the simulation program, you will be able to experimentally increase or decrease the number of tellers in order to determine the minimum number of tellers required.

The program you will write will be named bankSimulator and will take the following six command-line arguments:

     bankSimulator time_limit num_tellers wait_threshold mean_transaction_time dist_file seed
The arguments have the following meaning:

  1. time_limit: How many time units the simulation should run. You can also think of the time_limit as the number of seconds the bank is open for business.
  2. num_tellers: The number of tellers to be used in the simulation.
  3. wait_threshold: The simulation will keep track of what percent of customers have to wait longer than this many seconds.
  4. mean_transaction_time: The mean time, in seconds, for a teller to complete a transaction with a customer.
  5. dist_file: A histogram file used to generate customer arrival times.
  6. seed: An integer seed for the random number generator.

An example invocation of the simulation program would look as follows:

cetus3> bankSimulator 72000 3 100 120 expon_120 53

Number of customers = 615
Average customer waiting time = 3
Maximum customer wait = 150
Percentage of customers who waited longer than 100 seconds for a teller:  0.5

Teller   Idle Time    Idle 
     0       47358    65.8
     1       47882    66.5
     2       45889    63.7

The binary for the simulation can be found in /home/parker/Courses/CS302-Fall06/Labs/Lab6/bankSimex. The format and output of your program should correspond to this binary. (The only difference is that you'll name your program BankSimulator.) This binary has an optional additional argument (which we call the doprint option), which goes at the end of the argument list. By default it is 'no' and you do not have to include it. When set to 'yes', then the simulator will print out certain information about the events as they are processed. This information might help you in creating and debugging your simulation program. For example, to invoke the doprint option, you would say:

cetus3> bankSimulator 72000 3 100 120 expon_120 53 yes
Try this on the bankSimex binary to see the debugging information you can get.


Output

Each execution of your simulation program should produce five outputs, formatted as shown above:

  1. Number of customers: This count is the total number of customers processed during the simulation.
  2. Average customer waiting time: This is the average time, in seconds, that a customer had to spend waiting for a teller (i.e., waiting in line).
  3. Maximum customer wait: This number is the maximum time a customer spent waiting for a teller.
  4. Percentage of customers who waited longer than 'wait_threshold' seconds for a teller: This is the percentage of customers that had to wait longer than the wait_threshold (which was entered as a command-line argument) for a teller.

  5. The idle time for each teller in seconds and in percent. The percent idle time is the idle time for each teller divided by the length of the simulation.

Although some of these statistics are not required to answer the minimal teller question, they would be useful to a bank executive trying to make decisions about the number of tellers to hire. For example, a bank executive probably would also want to factor into his or her decision the average time that a customer spends waiting for a teller and the maximum time a customer has to wait for a teller.


Details

Pages 227-228 of the Weiss textbook give an overview of an implementation scheme for the bank teller problem. Like this description, you will be using a priority queue that is ordered by timestamped events. Rather than write your own priority queue class, you will be using the STL priority queue class.

Specifically you will need to write the following routines:

  1. A routine to read in the arguments and check their validity.
  2. A routine to initialize the bank simulation (e.g., creating the tellers, creating the arrival events, and initializing the statistics).
  3. A routine for executing the event loop.
  4. A routine for printing the statistics.
  5. Routines for handling the processing of arrival and departure events.
  6. Routines for generating random numbers from a histogram file and for a uniform distribution.
  7. Anything else that comes up as you write your program.

Some of the routines might be lumped together into the main procedure. For example routines 2-4 might go into the main procedure.


What Has Already Been Done For You

In order to help get you started, the following code can be found at /home/parker/Courses/CS302-Fall06/Labs/Lab6:


Generating Random Events

Now, in our simulation we're going to generate the times that people enter a bank according to an exponential distribution. We're also going to generate their transactions' durations according to a uniform distribution. These are going to be implemented using the approach we discussed in class, as outlined in these notes. To make this easier, we're going to define two event generator classes, histogramEventGenerator and uniformEventGenerator, which generate events from a histogram and from a uniform distribution with a given mean. The declarations for these classes can be found in EventGen.h:
    #include <string>

     using namespace std;

     class histogramEventGenerator {
       public:
         histogramEventGenerator(string filename);
         long next();   // produce the next random number
       protected:
         // YOUR VARIABLES GO HERE
     };

     class uniformEventGenerator {
       public:
         uniformEventGenerator(long mean);
         long next();   // produce the next random number
       protected:
         // YOUR VARIABLES GO HERE
     };

You will create a uniform event generator by giving it the mean of a uniform distribution. You will create a histogram event generator by giving it the name of a histogram file. Your uniform event generator can be implemented using the built-in C++ random() function. For the histogram event generator, you should use algorithm described in class and in these notes.

A note on random numbers

Note that in the EventGen.h class declarations, the next() method returns a long random number, hence we will use the a built-in random() function, which returns a number between 0 and RAND_MAX (a very large number). Other built-in C functions, such as drand48() and srand48(), return a double between 0 and 1.

Take the code below as an example:

     randomNumber = random() % (2*mean + 1)
If mean is 50 this formula will generate random numbers between 0 and 100, including both 0 and 100. This will be useful for the next() function of your uniform event generator.

Writing the Simulator

OK -- now we have enough information to write our simulator. The arrival times for people entering the bank will be according to a histogram file. The one we'll use is expon_120, which is an exponential whose mean is 120. The transaction times will be according to a uniform distribution.

The simulator will revolve around three classes: the Teller class, the Person class, and the Event class. There will be one Teller for each teller in the bank, and we will number them starting with zero. There will be one Person for each person that enters the bank. Again, we will number them starting with zero. An Event can be one of two types:

You should use five main data structures in your program.
  1. An STL queue called line of people waiting in line at the bank. A person must wait in line if he/she is in the bank and all the tellers are busy with transactions.
  2. An STL queue called free_tellers. A teller is placed in this queue if he or she is free and if line is empty.
  3. An STL priority_queue called eventQueue. The data structure within this priority queue should be a vector of Event objects, which should be compared by time.
  4. An event generator called arrivalTimeGenerator that generates times from an exponential distribution each time its next() method is called. This event generator is an instance of the histogramEventGenerator class.
  5. An event generator called transactionTimeGenerator that generates times from a uniform distribution each time its next() method is called. This event generator is an instance of the uniformEventGenerator.
What the program does is the following:

Classes

The following five classes are the heart of the simulation:
class Teller {
  public:
    int id;
    int idleTime;
};

class Person {
  public:
    int id;
    int entertime;
    int servicetime;
};

class Event {
  public:
    virtual void processEvent(); // Performs the necessary actions to process
                                 // the event.
    int getTime();				 
  protected:
    int time;        /* Time of the event */
};

class arrivalEvent : Event {
  public:
    arrivalEvent(Person *person, int time);
    void processEvent();
  protected:
    Person *p;       /* points to the person */
}

class departureEvent : Event {
  public:
    departureEvent(Person *person, Teller *teller, int time);
    void processEvent();
  protected:
    Person *p;       /* points to the person */
    Teller *t;       /* points to the teller */

Notice that events are defined using inheritance. Both arrivalEvents and departureEvents are types of Events. The reason inheritance is used is so our priority queue can be defined using just Event objects, giving it the capability to hold both arrivals and departures. By defining the processEvent() method to be virtual, we are saying that it is up to each subclass to define its own version of that method. At runtime, the appropriate method will be called based on whether a particular Event is an arrivalEvent or a departureEvent. Here are some notes on inheritance.

Other Helpful Information

  1. random() is declared in stdlib.h. This is the random number generator you should use.

  2. You should not have to modify uniformTest.cpp or histogramTest.cpp. They should compile with your code.

  3. For this lab you are being given the latitude to create your own names for your .cpp files. The only naming requirement is that your executable be named bankSimulator.

  4. STL priority_queues are a wrapper around other STL containers. Using them can be a little tricky. Here are sample codes that we've seen before that use priority_queues (example 2 is a priority queue that contains a vector):
    Priority queue example 1
    Priority queue example 2 using vectors
    Priority queue inheritance example


What to Submit

You need to submit the following for this lab:

If you fail to complete the lab then also submit a README file that describes what you did complete. It is also a good idea to have a doprint option that prints out information on arrival/departure events as they occur and the assignment of people to tellers or the waiting queue. Again doing so will allow the TAs to give you partial credit if your program does not always produce the correct output.