CS302 -- Lab 6 -- Banking Simulation


Lab Objective

This lab is designed to give you experience implementing and using priority queues and to give you experience writing simulations. This lab will use the doubly-linked list library (Dllist.h) and the Fields library (Fields.h).


Part I: Implementing a Priority Queue

In the first part of this lab, you are to write pQueue.cpp. pQueue.cpp will implement a priority queue based on a heap. The implementation should support the operations defined as follows:

const int DEFAULT_SIZE = 2;

class pQueue;

class HeapNode {
  friend class pQueue;
  protected:
    int key;
    Jval val;
   
    HeapNode(int k, Jval v) : key(k), val(v) {}
};

class pQueue {
  public:
    pQueue(int capacity = DEFAULT_SIZE);
    ~pQueue();

    // Insert new node into priority queue.  Expand the heap if it is full.
    void insert(int key, Jval val);

    // Delete smallest item; return the smallest key and 
    //     the value associated with this key
    void deleteMin(int &key, Jval &val); 
    bool isEmpty();
    void print();   // prints the keys in the heap
    void printEvents();  // prints the events in the heap
  protected:
    void percolateDown(int);
  protected:
    HeapNode **heap;
    int heapSize;
    int heapCapacity;
};

Note that this interface is somewhat different from the one we discussed in class. Here, the deleteMin method returns the minimum key and associated value in the parameters, so there aren't separate functions for those. Also note that the insert method must explicitly expand the heap if it is full.

You should implement the pQueue class using heaps as described in the book. You should use an array as your base heap. This means that you have to worry about what happens when the array gets full (you'll have to allocate a new bigger one, and copy the first to the second).

You will notice that the this interface contains a declaration for a HeapNode class. That is because in this lab, you must bundle the keys and values into an object and store a pointer to the object in your heap array.

Start with an array of 2 entries, and then double it whenever it grows too big. You will probably find it useful to store a sentinel in array entry 0 for insertions.


Testing your Priority Queue implementation

Test your priority queue implementation code by compiling it with sortem.cpp. The invocation for sortem is:
	sortem < input
	
Later in this lab you will further test your code by using it in the simulation described below.


Part II: Simulation

In the remainder of this lab you will implement the banking simulation described in class and in these notes. The class notes serve as an important resource for this lab so whenever you have questions about design or implementation, the class notes are a good place to look. The goal of running the simulation will be to determine the minimal number of tellers required in order to ensure that no more than x% of customers have to wait more than y minutes 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 minimal number of tellers.

The program you will write will be named bankSimulator and will take the following 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 maximum time, in seconds, that a customer should have to wait.
  4. mean_transaction_time: The mean time, in seconds, for a teller to complete a transaction with a customer.
  5. dist_file: The histogram file used to generate 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 simulation can be found in /home/parker/courses/cs302/labs/lab6/bankSimulator. If you have any questions about how your program should execute, the way the format of the output should look, or what the correct values for the output are, you should execute the simulation binary. The bankSimulator in the course directory has an optional additional argument, called doprint which goes at the end of the argument list. By default it is 'no' and you do not have to include it. If you say '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.


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 for a teller. Note that 'wait_threshold' should be replaced by the actual wait_threshold.

  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

You should use the implementation scheme described in class for the bank teller problem (also see pages 224-225 of Weiss). In other words, you should have a priority queue that is ordered by timestamped events. You should also use the classes shown in class and you should use inheritance to handle the events.

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 first arrival event, 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.
  8. You should test your program with both the binary heap priority queue and the dlist priority queue you wrote earlier in lab to ensure that they are working properly.

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, some code has already been prepared for you. This code can be found in the usual location (i.e., /home/parker/courses/cs302/labs/lab6), as follows:


A note on random numbers

Just to keep changing things on you (keeps you on your toes!), you'll note that in the EventGen.h class declarations, we have defined the "next()" method to return a long random number instead of a double. So, this means that you'll need to use a random number generator that returns a long instead of a double. In class, we talked about using drand48() and srand48(). For this simulation, to generate a long, you'll want to use random() and srandom(). This means you'll need to change your code slightly for using random instead of drand48(). In particular the simulation notes indicate that you can generate a uniform random number using the code:
randomNumber = drand48() * 2 * mean
This works since drand48() returns a number between 0 and 1. However, random() returns a number between 0 and RAND_MAX, which is a very large number. So, your random number generator for this lab should use the following definition instead:
randomNumber = random() % (2*mean + 1)
For example, if mean is 50 this formula will cause you to generate random numbers between 0 and 100.

Other Helpful Information

  1. random() and srandom() are declared in stdlib.h. These are the random number generators you should use. (See note in previous subsection.)

  2. If you declare a global variable as a template class, do not try to allocate it statically. You will get completely unintelligable linker messages if you do. For example:
         Dlist<Event *> a;
         
    will get the unintelligable linker messages if a is a global variable. However, the following declaration and initialization will work just fine:
         Dlist<Event *> *a = new Dlist<Event *>();
         

  3. The only dependencies your makefile has to concern itself with are the dependencies among the .cpp files that you create and the .h files you modify. The .cpp files that we have provided are assumed to be never changing and therefore you do not have to worry about writing makefile dependencies for them.

  4. For this lab you are being given the latitude to create your own names for your .cpp files.

  5. Process all customers that arrive before or at the time limit.

  6. When a teller becomes free add the teller to the end of the free teller queue.

What to Submit

You need to submit two different things for this lab:

  1. The .h files we created for you, your .cpp files, your makefile, the expon_120 histogram file, the input file and the answers file described below. If you fail to complete the lab then also submit a README file that describes what you did complete; if you do not do so you will not receive partial credit. 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.

  2. The minimal number of tellers required to assure that fewer than x% of customers have to wait more than y seconds for a teller where x, y, the mean transaction time and the seed have the following values:
         x = 5, y = 180, mean_transaction_time = 120, seed = 53
         x = 5, y = 180, mean_transaction_time = 240, seed = 137
         x = 2, y = 180, mean_transaction_time = 300, seed = 333
         x = 2, y = 300, mean_transaction_time = 240, seed = 217
         
    All simulations should be run for 144000 seconds and your answers should be placed in a file called answers.