Thursday, April 22, 2010

Java Call Center Program

import umontreal.iro.lecuyer.simevents.*;
import umontreal.iro.lecuyer.rng.*;
import umontreal.iro.lecuyer.randvar.*;
import umontreal.iro.lecuyer.probdist.*;
import umontreal.iro.lecuyer.stat.*;
import java.io.*;
import java.util.Scanner;
import java.util.LinkedList;
//http://www.iro.umontreal.ca/~lecuyer/ift6561/java/CallCenter.java
public class CallCenter {
static final double HOUR = 3600.0; // Time is in seconds.

// Data
// Arrival rates are per hour, service and patience times are in seconds.
double openingTime; // Opening time of the center (in hours).
int numPeriods; // Number of working periods (hours) in the day.
int[] numAgents; // Number of agents for each period.
double[] lambda; // Base arrival rate lambda_j for each j.
double alpha0; // Parameter of gamma distribution for B.
double p; // Probability that patience time is 0.
double nu; // Parameter of exponential for patience time.
double alpha, beta; // Parameters of gamma service time distribution.
double s; // Want stats on waiting times smaller than s.

// Variables
double busyness; // Current value of B.
double arrRate = 0.0; // Current arrival rate.
int nAgents; // Number of agents in current period.
int nBusy; // Number of agents occupied;
int nArrivals; // Number of arrivals today;
int nAbandon; // Number of abandonments during the day.
int nGoodQoS; // Number of waiting times less than s today.
double nCallsExpected; // Expected number of calls per day.

Event nextArrival = new Arrival(); // The next Arrival event.
LinkedList waitList = new LinkedList();

RandomStream streamB = new MRG32k3a(); // For B.
RandomStream streamArr = new MRG32k3a(); // For arrivals.
RandomStream streamPatience = new MRG32k3a(); // For patience times.
GammaGen genServ; // For service times; created in readData().

Tally[] allTal = new Tally [4];
Tally statArrivals = allTal[0] = new Tally ("Number of arrivals per day");
Tally statWaits = allTal[1] = new Tally ("Average waiting time per customer");
Tally statGoodQoS = allTal[2] = new Tally ("Proportion of waiting times < s");
Tally statAbandon = allTal[3] = new Tally ("Proportion of calls lost");
Tally statWaitsDay = new Tally ("Waiting times within a day");

public CallCenter (String fileName) throws IOException {
readData (fileName);
// genServ can be created only after its parameters are read.
// The acceptance/rejection method is much faster than inversion.
genServ = new GammaAcceptanceRejectionGen (new MRG32k3a(), alpha, beta);
}

// Reads data and construct arrays.
public void readData (String fileName) throws IOException {
BufferedReader input = new BufferedReader (new FileReader (fileName));
Scanner scan = new Scanner(input);
openingTime = scan.nextDouble(); scan.nextLine();
numPeriods = scan.nextInt(); scan.nextLine();
numAgents = new int[numPeriods];
lambda = new double[numPeriods];
nCallsExpected = 0.0;
for (int j = 0; j < numPeriods; j++) {
numAgents[j] = scan.nextInt();
lambda[j] = scan.nextDouble();
nCallsExpected += lambda[j]; scan.nextLine();
}
alpha0 = scan.nextDouble(); scan.nextLine();
p = scan.nextDouble(); scan.nextLine();
nu = scan.nextDouble(); scan.nextLine();
alpha = scan.nextDouble(); scan.nextLine();
beta = scan.nextDouble(); scan.nextLine();
s = scan.nextDouble();
scan.close();
}

// A phone call.
class Call {
double arrivalTime, serviceTime, patienceTime;

public Call() {
serviceTime = genServ.nextDouble(); // Generate service time.
if (nBusy < nAgents) { // Start service immediately.
nBusy++;
nGoodQoS++;
statWaitsDay.add (0.0);
new CallCompletion().schedule (serviceTime);
} else { // Join the queue.
patienceTime = generPatience();
arrivalTime = Sim.time();
waitList.addLast (this);
}
}

public void endWait() {
double wait = Sim.time() - arrivalTime;
if (patienceTime < wait) { // Caller has abandoned.
nAbandon++;
wait = patienceTime; // Effective waiting time.
} else {
nBusy++;
new CallCompletion().schedule (serviceTime);
}
if (wait < s) nGoodQoS++;
statWaitsDay.add (wait);
}
}

// Event: A new period begins.
class NextPeriod extends Event {
int j; // Number of the new period.
public NextPeriod (int period) { j = period; }
public void actions() {
if (j < numPeriods) {
nAgents = numAgents[j];
arrRate = busyness * lambda[j] / HOUR;
if (j == 0) {
nextArrival.schedule
(ExponentialDist.inverseF (arrRate, streamArr.nextDouble()));
} else {
checkQueue();
nextArrival.reschedule ((nextArrival.time() - Sim.time())
* lambda[j-1] / lambda[j]);
}
new NextPeriod(j+1).schedule (1.0 * HOUR);
} else
nextArrival.cancel(); // End of the day.
}
}

// Event: A call arrives.
class Arrival extends Event {
public void actions() {
nextArrival.schedule
(ExponentialDist.inverseF (arrRate, streamArr.nextDouble()));
nArrivals++;
new Call(); // Call just arrived.
}
}

// Event: A call is completed.
class CallCompletion extends Event {
public void actions() { nBusy--; checkQueue(); }
}

// Start answering new calls if agents are free and queue not empty.
public void checkQueue() {
while ((waitList.size() > 0) && (nBusy < nAgents))
(waitList.removeFirst()).endWait();
}

// Generates the patience time for a call.
public double generPatience() {
double u = streamPatience.nextDouble();
if (u <= p)
return 0.0;
else
return ExponentialDist.inverseF (nu, (1.0-u) / (1.0-p));
}

public void simulateOneDay (double busyness) {
Sim.init(); statWaitsDay.init();
nArrivals = 0; nAbandon = 0;
nGoodQoS = 0; nBusy = 0;
this.busyness = busyness;

new NextPeriod(0).schedule (openingTime * HOUR);
Sim.start();
// Here the simulation is running...

statArrivals.add ((double)nArrivals);
statAbandon.add ((double)nAbandon / nCallsExpected);
statGoodQoS.add ((double)nGoodQoS / nCallsExpected);
statWaits.add (statWaitsDay.sum() / nCallsExpected);
}

public void simulateOneDay () {
simulateOneDay (GammaDist.inverseF (alpha0, alpha0, 8,
streamB.nextDouble()));
}

static public void main (String[] args) throws IOException {
CallCenter cc = new CallCenter ("CallCenter.dat");
for (int i = 0; i < 1000; i++) cc.simulateOneDay();
System.out.println ("\nNum. calls expected = " + cc.nCallsExpected +"\n");
for (int i = 0; i < cc.allTal.length; i++) {
cc.allTal[i].setConfidenceIntervalStudent();
cc.allTal[i].setConfidenceLevel (0.90);
}
System.out.println (Tally.report ("CallCenter:", cc.allTal));
}
}

No comments:

Post a Comment