import umontreal.iro.lecuyer.simevents.*; import umontreal.iro.lecuyer.rng.*; import umontreal.iro.lecuyer.randvar.*; import umontreal.iro.lecuyer.stat.*; import java.util.Comparator; import java.util.Collections; import java.util.LinkedList; import java.util.Random; public class BicycleSim { //************************************************DATA STRUCTURE CLASSES************************************************ class Bike { int bikeID; //unique identifier, set to position in bikelist boolean needRepair; //true if the bike needs repair double totalDist; //total distance the bicycle has traveled in miles } class Station { int coord[]; //2 item array that stores the x,y coordinates of the station (intersection) Post[] postlist; //array of the posts at a station int bikesAvailable; int postsAvailable; } class Post { boolean inUse; //true if a bike is parked at the post int bike; //the ID of the bicycle parked there, -1 if no bike } class Customer implements Comparable { double arriveTime; double travelTime; //travel time (founds by calculating how much time to travel distance) double servTime; //time the departure event should happen **THIS IS HOW CUSTOMER IS SORTED IN LIST** double travelDist; //travel distance in BLOCKS double travelDistMiles; //travel distance in MILES int bike; //the bicycle the customer takes int stationStart; //the originating location for the customer, refers to station int destStation; //destination station @Override public int compareTo(Customer cust) { //sort Customer based on servTime in ascending order return (int) (this.servTime - cust.servTime); } } //************************************************DATA MEMBERS************************************************ //simulating in a 100 street, "40" avenue setting //only avenues that are multiples of 3 are used as "true" avenues (1 ave = 3 city blocks in Manhattan) int maxX = 40; int maxY = 100; int numStations; int postsPerStation; int bikesPerStation; double totalCustomersGen = 0; double repairThreshold = 200.0; //number of miles a bike can travel before needing repair double timePerBlock = 0.5; //one minute per block double blockLength = .05; //a block is 1/20 of a mile double rideCharge = 9.95; //cost of 24hr pass double timeLimit = 30; //30 minutes before extra fees apply double overageCharge[] = {4.0,9.0,12.0}; //overage charges for going over thirty minutes //$4 first half hour over, $9 for third, $12 for each after double grossProfit = 0.0; //total accumulated profit double repairCosts = 0.0; //total costs associated with repair double netProfit = 0.0; //grossProfit - repairCosts double avgRepairCost = 50.0; int numBikesNeedRepair = 0; //the number of bikes that need repair int numRedirects = 0; //number of times a customer's destination station must be changed int lostCustomers = 0; //number of customer's lost due to lack of bikes int overageCounter = 0; //number of customers charged overages RandomVariateGen genArr; Random rng = new Random(); Tally custDistTraveled = new Tally("Distances traveled"); Tally custTime = new Tally("Service times"); LinkedList customerList = new LinkedList(); //list of active customers Station stationlist[]; //array of all stations Bike bikelist[]; //array of all bikes //********************************************SIM FUNCTIONS and CONSTRUCTOR********************************************* //constructor public BicycleSim(double first, int posts, int bikes) { this.numStations = 10; this.postsPerStation = posts; this.bikesPerStation = bikes; genArr = new ExponentialGen(new MRG32k3a(), first); this.stationlist = new Station[10]; for(int i=0; i<10; i++) { this.stationlist[i] = new Station(); this.stationlist[i].postlist = new Post[posts]; } int totalBikes = bikes*numStations; //total bikes is bikes per station multiplied by number of stations this.bikelist = new Bike[totalBikes]; //populate bikelist for(int i=0; i< totalBikes; i++) { Bike b = new Bike(); b.bikeID = i; b.needRepair = false; b.totalDist = 0.0; this.bikelist[i] = b; } int counter = 0; //populate station's postlist w/ correct number of posts and bikes for(int i=0; i< 10; i++) { for(int j=0; j 0) { //assign a bike boolean flag = false; for(int i=0; (ip) { xtravel = x-p; } else { xtravel = p-x; } if(y>q) { ytravel = y-q; } else { ytravel = q-y; } cust.travelDist = xtravel+ytravel; cust.travelDistMiles = cust.travelDist*blockLength; cust.travelTime = cust.travelDist*timePerBlock; cust.servTime = cust.arriveTime + cust.travelTime; //schedule the expected departure event new Departure().schedule (cust.servTime); //add the customer to the customerList and resort it customerList.addLast(cust); Collections.sort(customerList); //sorted in ascending order of servTime } else { lostCustomers++; } //if no bikes, lose customer } } class Departure extends Event { public void actions() { Customer cust = customerList.removeFirst(); //check for available post to park bike if(stationlist[cust.destStation].postsAvailable > 0) { //park the bike boolean parked = false; for(int i=0; (i repairThreshold) { bikelist[cust.bike].needRepair = true; //if bike has reached repair threshold, flag it numBikesNeedRepair++; repairCosts += avgRepairCost; } else { stationlist[cust.destStation].bikesAvailable++; } } } //update the tallies custTime.add (cust.travelTime); custDistTraveled.add(cust.travelDistMiles); //update profits grossProfit+=rideCharge; //if gone over the 30 minute mark, apply overage charges double halfHours = (cust.travelTime - cust.arriveTime)/30; if(halfHours > 1.0) { halfHours = halfHours - 1.0; if(halfHours <= 1.0) { grossProfit+=overageCharge[0]; overageCounter++; } else if((halfHours > 1.0)&&(halfHours <= 2.0)) { grossProfit = grossProfit+overageCharge[0]+overageCharge[1]; overageCounter++; } else { double x = Math.ceil(halfHours); grossProfit = grossProfit+overageCharge[0]+overageCharge[1]+(overageCharge[2]*x); overageCounter++; } } netProfit = grossProfit - repairCosts; } else { //redirect and reschedule //find nearest station and reset destination numRedirects++; cust.stationStart = cust.destStation; cust.destStation = findNearestStation(cust.destStation); //calculate travel time and distance and add to existing int x = stationlist[cust.stationStart].coord[0]; int y = stationlist[cust.stationStart].coord[1]; int p = stationlist[cust.destStation].coord[0]; int q = stationlist[cust.destStation].coord[1]; int xtravel, ytravel; if(x>p) { xtravel = x-p; } else { xtravel = p-x; } if(y>q) { ytravel = y-q; } else { ytravel = q-y; } cust.travelDist = cust.travelDist+xtravel+ytravel; cust.travelDistMiles = cust.travelDist*blockLength; cust.travelTime = cust.travelDist*timePerBlock; cust.servTime = cust.arriveTime + cust.travelTime; //update servTime //schedule the expected new departure event new Departure().schedule (cust.servTime); //add the customer back to the customerList and resort it customerList.addLast(cust); Collections.sort(customerList); //sorted in ascending order of servTime } } } //*****************************************************MISC FUNCTIONS****************************************************** //function for Euclidean distance, returns distance from [x1,y1] to [x2,y2] public double getDistance(int x1, int y1, int x2, int y2) { return Math.sqrt((Math.pow(x2-x1,2)+Math.pow(y2-y1, 2))); } //function that finds the nearest station to a station public int findNearestStation(int station) { int stationID = -1; double bestDistance = 9999; for(int i=0; i