// CS 1538 Fall 2009 // This version of the GrocerySim calculates the long run length of the // queue using "ensemble averages" (see p. 404 in text) import java.util.*; import java.io.*; public class GrocerySimD { private PriorityQueue FEL; private Queue checkLine; private int numCustomers; private double runTime; private Customer currCust; private CheckOut cashier; private double lambdaCust, lambdaServe; private Random r; private PrintWriter ofile; public double wwtot; public int runs, batches, start, iQLength; private double [] batchYj; private double batchLength, aveave; public GrocerySimD(int time, int b, double meanCust, double meanServe, int ru, int st, int iQ) throws Exception { FEL = new PriorityQueue(); checkLine = new LinkedList(); runTime = time; batches = b; batchLength = runTime/batches; lambdaCust = 1/meanCust; lambdaServe = 1/meanServe; batchYj = new double[batches]; runs = ru; start = st; iQLength = iQ; cashier = null; r = new Random((new Date()).getTime()); ofile = new PrintWriter(new FileOutputStream("output.csv"), true); wwtot = 0; } public void runSimulation() throws ClassNotFoundException { int batch, oldBatch; boolean border; aveave = 0.0; for (int i = 0; i < runs; i++) { cashier = new CheckOut(); double clock = 0.0, oldClock = 0.0; border = false; double delta_T = 0.0; // used to store time between events double L_tot = 0.0, L_t = 0; // used to calculate average number in system double L_hat = 0.0; double Lq_tot = 0.0; // and the average number in the queue double Lq_hat = 0.0; double w_tot = 0.0, w_hat = 0.0; // used to calculate average time double wq_tot = 0.0, wq_hat = 0.0; // in system and in the queue per customer double rho_tot = 0.0, rho_hat = 0.0; // used to calculate server utilization boolean done = false; initQueue(); int custNum = iQLength; FEL.add(new ArrivalEventFloat(0.0)); while (!done) { SimEventFloat e = FEL.remove(); if (e.get_e_time() >= runTime) { delta_T = runTime - 0.01 - clock; clock = runTime - 0.01; batch = (int) (clock/batchLength); batchYj[batch] += delta_T*checkLine.size(); Lq_tot += delta_T*checkLine.size(); done = true; } else { delta_T = e.get_e_time() - clock; oldClock = clock; clock = e.get_e_time(); // Since the time between events that update the queue could cross // over two ensembles, we need to subdivide the amount added to // the cumulative totals between them. This situation can be // easily detected by seeing if the batch integer from the previous // event time and the current event time are the same. batch = (int) (clock/batchLength); oldBatch = (int) (oldClock/batchLength); if (batch != oldBatch) border = true; if (Class.forName("ArrivalEventFloat").isInstance(e)) { custNum++; //System.out.println("Customer " + custNum + " arrives at time " + clock); L_tot = L_tot + (delta_T)*L_t; // For the previous delta_T minutes, // number of customers was L_t L_t++; // Now there is one more Lq_tot = Lq_tot + (delta_T)*checkLine.size(); // For the previous // delta_T minutes, number in the queue was checkLine.size(); // If the time crosses two batches, subdivide as shown if (border) { double oldBDelta = batch * batchLength - oldClock; double newBDelta = clock - batch * batchLength; batchYj[oldBatch] += oldBDelta * checkLine.size(); batchYj[batch] += newBDelta * checkLine.size(); border = false; } else batchYj[batch] += delta_T*checkLine.size(); currCust = new Customer(custNum, e.get_e_time()); currCust.serviceT = serviceTime(lambdaServe); if (!cashier.isBusy()) { //System.out.println("Customer " + custNum + " served at time " + clock); currCust.startServiceT = clock; cashier.addCust(currCust); FEL.add(new CompletionEventFloat(clock + currCust.serviceT)); rho_tot = rho_tot + currCust.serviceT; // Server is busy for next // currCust.serviceT minutes } else { checkLine.add(currCust); } FEL.add(new ArrivalEventFloat(clock + deltaCustomer(lambdaCust))); } else { L_tot = L_tot + (delta_T)*L_t; // For the previous delta_T minutes, // number of customers was L_t; L_t--; // Now there is one less Lq_tot = Lq_tot + (delta_T)*checkLine.size(); // For the previous // delta_T minutes, number in the // queue was checkLine.length(); if (border) { double oldBDelta = batch * batchLength - oldClock; double newBDelta = clock - batch * batchLength; batchYj[oldBatch] += oldBDelta * checkLine.size(); batchYj[batch] += newBDelta * checkLine.size(); border = false; } else batchYj[batch] += delta_T*checkLine.size(); //System.out.print("Customer "); currCust = cashier.removeCust(); //System.out.println(currCust.id + " leaving at time " + clock); currCust.endServiceT = clock; currCust.waitT = clock - currCust.arrivalT - currCust.serviceT; currCust.inSystemT = clock - currCust.arrivalT; w_tot = w_tot + currCust.inSystemT; // Add time in system of current // customer to the total wq_tot = wq_tot + currCust.waitT; if (!checkLine.isEmpty()) { currCust = (Customer) checkLine.remove(); currCust.startServiceT = clock; //System.out.println("Customer " + custNum + " served at time " + clock); cashier.addCust(currCust); FEL.add(new CompletionEventFloat(clock + currCust.serviceT)); rho_tot = rho_tot + currCust.serviceT; // Server is busy for next // currCust.serviceT minutes } } } } L_hat = L_tot/clock; // Calculate average number in system Lq_hat = Lq_tot/clock; // Calculate average number in queue aveave += Lq_hat; System.out.println("Run " + i + ": Average Number in Queue: " + Lq_hat); } aveave = aveave / runs; System.out.println("\nAverage of averages: " + aveave); if (lambdaCust >= lambdaServe) { System.out.println("No steady state value for Lq possible"); } else { // Now use the formula for comparison purposes. This formula only applies // because both the arrival and service rates are exponentially distributed double Lq = (lambdaCust*lambdaCust/(lambdaServe*(lambdaServe-lambdaCust))); System.out.println("Formula Steady State Lq: " + Lq); } } public void initQueue() { for (int i = 0; i < iQLength; i++) { checkLine.add(new Customer(i, 0.0)); } } // As in most object-oriented programs, the main here is very simple public static void main (String [] args) throws Exception { int time = Integer.parseInt(args[0]); int batches = Integer.parseInt(args[1]); double meanCust = Double.parseDouble(args[2]); // mean interarrival time for customers double meanServe = Double.parseDouble(args[3]); // mean service time for customers int runs = Integer.parseInt(args[4]); // How many runs to do? int start = Integer.parseInt(args[5]); // Which batch do we start // processing from? int iQ = Integer.parseInt(args[6]); // How many people are initially // in the queue? if (time % batches != 0) { System.out.println("Batches must divide time"); System.exit(1); } GrocerySimD sim = new GrocerySimD(time, batches, meanCust, meanServe, runs, start, iQ); sim.runSimulation(); sim.giveResults(); //sim.showResults(); } public void giveResults() { double total = 0.0; System.out.println("Results: "); System.out.println("Ens. Length\tBatch j\t Ave. Batch Mean\t Cum. Ave"); System.out.println("----------\t-------\t ---------------\t --------"); for (int i = 0; i < batches; i++) { System.out.print(batchLength + "\t\t"); System.out.print(i + "\t "); //batchYj[i] = batchYj[i]/batchLength; total += batchYj[i]/batchLength; //batchYj[i] = batchYj[i]/runs; System.out.print(batchYj[i]/(batchLength*runs) + "\t"); System.out.println(total/((i+1)*runs)); } total = 0.0; System.out.println(); for (int i = start; i < batches; i++) { System.out.print(batchLength + "\t\t"); System.out.print(i + "\t "); //batchYj[i] = batchYj[i]/batchLength; total += batchYj[i]/batchLength; //batchYj[i] = batchYj[i]/runs; System.out.print(batchYj[i]/(batchLength*runs) + "\t"); System.out.println(total/((i+1-start)*runs)); } } // Method to provide the interarrival time distribution public double deltaCustomer(double lambda) { double zeroOne = r.nextDouble(); double mean = 1/lambda; double nextran = -mean * Math.log(zeroOne); return nextran; } // Method to provide the service time distribution -- now it is exponentially // distributed with lamba passed in public double serviceTime(double lambda) { double zeroOne = r.nextDouble(); double mean = 1/lambda; double nextran = -mean * Math.log(zeroOne); return nextran; } // Simple class to represent the cashier. It simply stores the current // customer and a busy flag. Note that in the text (see p. 75) it states // that the cashier and customers do not have to be explicitly modeled. // However, with an object-oriented programming approach, private class CheckOut { private boolean busy; private Customer currentCust; public CheckOut() { busy = false; currentCust = null; } public boolean isBusy() { return busy; } public void addCust(Customer c) { currentCust = c; busy = true; } public Customer removeCust() { Customer t = currentCust; currentCust = null; busy = false; return t; } } // Simple class to represent a customer. Its main purpose is to store all // of the data associated with each customer during the simulation. private class Customer { public int id; public double arrivalT, serviceT, startServiceT, waitT, endServiceT, inSystemT; public Customer(int newid, double arr) { id = newid; arrivalT = arr; } } }