package cz.agents.agentpolis.darptestbed.siminfrastructure.logger.statistics; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import java.io.Writer; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import org.apache.log4j.Logger; import com.google.inject.Inject; import com.google.inject.Singleton; import cz.agents.agentpolis.darptestbed.global.GlobalParams; import cz.agents.agentpolis.darptestbed.global.Utils; import cz.agents.agentpolis.darptestbed.siminfrastructure.logger.VehicleMoveLogger; import cz.agents.agentpolis.darptestbed.siminfrastructure.logger.data.RequestState; import cz.agents.agentpolis.darptestbed.simmodel.agent.data.Request; import cz.agents.agentpolis.darptestbed.simmodel.agent.data.TimeWindow; import cz.agents.agentpolis.darptestbed.simmodel.environment.model.TestbedModel; import cz.agents.agentpolis.simmodel.environment.model.AgentPositionModel; import cz.agents.agentpolis.simmodel.environment.model.citymodel.transportnetwork.AllNetworkNodes; import cz.agents.agentpolis.simmodel.environment.model.citymodel.transportnetwork.elemets.Node; import cz.agents.agentpolis.simmodel.environment.model.query.AgentPositionQuery; import cz.agents.agentpolis.simmodel.environment.model.sensor.PositionUpdated; import cz.agents.alite.common.event.EventProcessor; /** * Saves summary data of the whole taxi simulation to evaluate the algorithm. * Also, logs every move of vehicles using the callback of PositionSensor. * * @author Lukas Canda */ @Singleton @Deprecated public class StatisticsLogger implements PositionUpdated { private static final Logger LOGGER = Logger.getLogger(StatisticsLogger.class); /** * A storage to manage all data concerning the current state of environment. */ protected TestbedModel serviceModel; /** * The model that takes care of position sensors during the simulation. */ protected AgentPositionModel agentPositionModel; /** * Checks out an agent's current position. */ protected final AgentPositionQuery positionQuery; /** * All nodes in the map */ protected final Map<Long, Node> allNetworkNodes; /** * A storage to save all data concerning taxi drivers and passengers */ protected final TestbedModel taxiModel; /** * A set of useful methods for searching paths, distances etc. */ protected final Utils utils; /** * Logs vehicle movement */ protected final VehicleMoveLogger vehicleMoveLogger; /** * My instance to use after the simulation is finished */ protected static StatisticsLogger instance = null; @Inject public StatisticsLogger(EventProcessor eventProcessor, TestbedModel serviceModel, AgentPositionModel agentPositionModel, AgentPositionQuery positionQuery, AllNetworkNodes allNetworkNodes, TestbedModel taxiModel, Utils utils, VehicleMoveLogger vehicleMoveLogger) { this.serviceModel = serviceModel; this.agentPositionModel = agentPositionModel; this.positionQuery = positionQuery; this.allNetworkNodes = allNetworkNodes.getAllNetworkNodes(); this.taxiModel = taxiModel; this.utils = utils; this.vehicleMoveLogger = vehicleMoveLogger; } /** * Registers a taxi driver to check out his position changes all the time * (for logging purposes). * * @param driverId */ public void addVehicleForSensor(String driverId) { this.agentPositionModel.addSensingPositionNode(driverId, this); } /** * Change of a taxi driver's position. */ @Override public void newEntityPosition(String entityId, long nodeId) { vehicleMoveLogger.logVehicleMove(taxiModel.getVehicleId(entityId), nodeId); } private String removeQuotation(String strWithQuo) { return strWithQuo.substring(1, strWithQuo.length() - 1); } /** * Writes out the summary report about the whole simulation into the report * file. It uses log file (CSV) as the input to read data from. * * @param fileReport * output file * @param fileCSV * input file */ public void writeReport(File fileReport, File fileCSV) { BufferedReader reader = null; String inputRow; String[] arrSplitRow; // the type of the logged event String strType; final int PRICE_PER_KM = GlobalParams.getPricePerKilometer(); // the last position of each taxi driver (node number) Map<String, Long> mapOfLastNodes = new HashMap<String, Long>(); // the number of kilometers traveled with x passengers on board List<Double> listOfDistancesWithPassengers = new ArrayList<Double>(); listOfDistancesWithPassengers.add(0.0); // the map of logged requests and their state Map<Request, RequestState> reqs = new HashMap<Request, RequestState>(); // the map of requests and the time they had to wait Map<Request, Long> reqsWaitingTime = new HashMap<Request, Long>(); // the map of taxis with the list of passengers currently on board Map<String, List<String>> taxisWithPassengers = new HashMap<String, List<String>>(); // the map of passengers with total distance they've traveled Map<String, Double> passengersAndDistTraveled = new HashMap<String, Double>(); // the map of passengers with total cost they've paid Map<String, Double> passengersAndPricePaid = new HashMap<String, Double>(); // variables read from log file String vehicleId; String passengerId; long nodeNumber; long time; // time windows long nodeFrom; long nodeTo; long timeWinOpen; long timeWinClose; String timeWinOpenStr; String timeWinCloseStr; // variables counted from the previous ones int numOfPassengers; long totalWaitingTime = 0; double prevDistance = 0; double tmpDistance; double tmpPrice; Request tmpReq; try { // read CSV file (log file) reader = new BufferedReader(new FileReader(fileCSV)); } catch (FileNotFoundException e1) { e1.printStackTrace(); } try { inputRow = reader.readLine(); // read the log file row by row while (inputRow != null && inputRow.length() > 0) { arrSplitRow = inputRow.split(","); strType = removeQuotation(arrSplitRow[2]); if (strType.equals("VEHICLE_MOVEMENT")) { // a taxi has moved to a new node nodeNumber = Long.parseLong(removeQuotation(arrSplitRow[3])); vehicleId = removeQuotation(arrSplitRow[1]); // initial taxi position if (!mapOfLastNodes.containsKey(vehicleId)) { mapOfLastNodes.put(vehicleId, nodeNumber); taxisWithPassengers.put(vehicleId, new ArrayList<String>()); } else { // current number of passengers on board numOfPassengers = taxisWithPassengers.get(vehicleId).size(); while (listOfDistancesWithPassengers.size() <= numOfPassengers) { // initiate listOfDistancesWithPassengers.add(0.0); } prevDistance = listOfDistancesWithPassengers.get(numOfPassengers); tmpDistance = utils.computeDistance(mapOfLastNodes.get(vehicleId), nodeNumber); listOfDistancesWithPassengers.set(numOfPassengers, prevDistance + tmpDistance); // the price paid by each passenger on board for this // piece of trip tmpPrice = PRICE_PER_KM * tmpDistance / 1000 / numOfPassengers; // passengers' counters for (String passId : taxisWithPassengers.get(vehicleId)) { passengersAndDistTraveled.put(passId, passengersAndDistTraveled.get(passId) + tmpDistance); passengersAndPricePaid.put(passId, passengersAndPricePaid.get(passId) + tmpPrice); } mapOfLastNodes.put(vehicleId, nodeNumber); } } else if (strType.equals("PASSENGER_GOT_IN_TAXI")) { time = Long.parseLong(removeQuotation(arrSplitRow[0])); passengerId = removeQuotation(arrSplitRow[1]); vehicleId = removeQuotation(arrSplitRow[4]); // init counters passengersAndDistTraveled.put(passengerId, 0.0); passengersAndPricePaid.put(passengerId, 0.0); // remember the passenger on board List<String> passOnBoard = taxisWithPassengers.get(vehicleId); passOnBoard.add(passengerId); // TODO: this might be useless... taxisWithPassengers.put(vehicleId, passOnBoard); // count the waiting time tmpReq = findRequest(passengerId, reqs); if (tmpReq != null && tmpReq.getTimeWindow() != null) { long earliestDeparture = tmpReq.getTimeWindow().getEarliestDeparture(); if (time > earliestDeparture) { reqsWaitingTime.put(tmpReq, time - earliestDeparture); } } } else if (strType.equals("PASSENGER_GOT_OFF_TAXI")) { time = Long.parseLong(removeQuotation(arrSplitRow[0])); passengerId = removeQuotation(arrSplitRow[1]); vehicleId = removeQuotation(arrSplitRow[4]); // remove the passenger from the board list List<String> passOnBoard = taxisWithPassengers.get(vehicleId); passOnBoard.remove(passengerId); // TODO: this might be useless... taxisWithPassengers.put(vehicleId, passOnBoard); // requests tmpReq = findRequest(passengerId, reqs); long delay = 0; if (tmpReq.getTimeWindow() != null) { delay = tmpReq.getTimeWindow().getArrivalDelay(time); } // if the delay is less than a minute, we'll ignore it if (utils.toMinutes(delay) == 0) { reqs.put(tmpReq, RequestState.SERVED_ON_TIME); Long waitTime = reqsWaitingTime.get(tmpReq); if (waitTime != null) { totalWaitingTime += waitTime; } } else { reqs.put(tmpReq, RequestState.SERVED_WITH_DELAY); } } else if (strType.equals("PASSENGER_SENT_REQUEST")) { passengerId = removeQuotation(arrSplitRow[1]); nodeFrom = Long.parseLong(removeQuotation(arrSplitRow[5])); nodeTo = Long.parseLong(removeQuotation(arrSplitRow[6])); timeWinOpenStr = removeQuotation(arrSplitRow[7]); timeWinCloseStr = removeQuotation(arrSplitRow[8]); // if there are no time windows if (timeWinOpenStr.length() == 0 || timeWinCloseStr.length() == 0) { reqs.put(new Request(passengerId, nodeFrom, nodeTo, new HashSet<String>()), RequestState.SENT); } else { timeWinOpen = Long.parseLong(timeWinOpenStr); timeWinClose = Long.parseLong(timeWinCloseStr); reqs.put(new Request(passengerId, nodeFrom, nodeTo, new TimeWindow(timeWinOpen, timeWinClose), new HashSet<String>()), RequestState.SENT); } } inputRow = reader.readLine(); } reader.close(); } catch (IOException e1) { e1.printStackTrace(); } // temporary variables Double totalTravel; Double totalPersonKilometers; String newLine = System.getProperty("line.separator"); StringBuilder sb = new StringBuilder(); // count all statistics int simulTime = utils.toSeconds(taxiModel.getSimulationRuntime()); totalTravel = 0.0; for (Double travel : listOfDistancesWithPassengers) { totalTravel += travel; } long totalDistTravel = Math.round(totalTravel); long distTravelEmptyVeh = Math.round(listOfDistancesWithPassengers.get(0)); int distTravelEmptyVehPerc = (int) Math.round(100 * listOfDistancesWithPassengers.get(0) / totalTravel); int maxPassenOnBoard = listOfDistancesWithPassengers.size() - 1; totalPersonKilometers = 0.0; for (int i = 1; i < listOfDistancesWithPassengers.size(); i++) { totalPersonKilometers += i * listOfDistancesWithPassengers.get(i); } double avgVehOccupancy = totalPersonKilometers / totalTravel; int totalReqs = countNumOfReqs(reqs); int reqsOnTime = countNumOfReqs(reqs, RequestState.SERVED_ON_TIME); int reqsOnTimePerc = (int) Math.round((double) 100 * reqsOnTime / totalReqs); int reqsNotServed = totalReqs - reqsOnTime; int reqsNotServedPerc = 100 - reqsOnTimePerc; int avgWaitingTime = utils.toMinutes(Math.round((double) totalWaitingTime / reqsOnTime)); double totalDirectTripLen = 0.0; double totalDetourLen = 0.0; double totalSaving = 0.0; double tmpDirectDistance; List<String> passenServedOnTime = getPassengersOnTime(reqs); for (String passId : passenServedOnTime) { tmpReq = findRequest(passId, reqs); tmpDirectDistance = utils.computeDistance(tmpReq.getFromNode(), tmpReq.getToNode()); totalDirectTripLen += tmpDirectDistance; totalDetourLen += passengersAndDistTraveled.get(passId) - tmpDirectDistance; totalSaving += PRICE_PER_KM * tmpDirectDistance / 1000 - passengersAndPricePaid.get(passId); } // every passenger wanted to go this far on average long avgDirectTripLen = Math.round(totalDirectTripLen / passenServedOnTime.size()); // every passenger's trip was this much longer than it was necessary long avgDetour = Math.round(totalDetourLen / passenServedOnTime.size()); // (percentage compared to the total trip length) int avgDetourPerc = (int) Math.round(100 * totalDetourLen / totalDirectTripLen); // every passenger saved this amount on average int avgCostSaving = (int) Math.round(totalSaving / passenServedOnTime.size()); // (percentage compared to the direct trip price) int avgCostSavingPerc = (int) Math.round((double) 100 * avgCostSaving / PRICE_PER_KM * avgDirectTripLen / 1000); // write statistics into the result file if (GlobalParams.isUseResultsFile()) { sb.append("Simulation time: "); sb.append(simulTime); sb.append(" s"); sb.append(newLine); sb.append("Total distance traveled: "); sb.append(totalDistTravel); sb.append(" m"); sb.append(newLine); sb.append("Distance traveled with empty vehicles: "); sb.append(distTravelEmptyVeh); sb.append(" m ("); sb.append(distTravelEmptyVehPerc); sb.append("%)"); sb.append(newLine); sb.append("Max passengers on board: "); sb.append(maxPassenOnBoard); sb.append(newLine); sb.append("Average vehicle occupancy: "); sb.append(avgVehOccupancy); sb.append(" passengers per km"); sb.append(newLine); sb.append(newLine); sb.append("Total number of requests: "); sb.append(totalReqs); sb.append(newLine); sb.append("The number of requests served on time: "); sb.append(reqsOnTime); sb.append(" ("); sb.append(reqsOnTimePerc); sb.append("%)"); sb.append(newLine); sb.append("The number of requests unserved or served with delay: "); sb.append(reqsNotServed); sb.append(" ("); sb.append(reqsNotServedPerc); sb.append("%)"); sb.append(newLine); sb.append(newLine); sb.append("---All following statistics are counted per served passenger---"); sb.append(newLine); sb.append(newLine); sb.append("Average waiting time: "); sb.append(avgWaitingTime); sb.append(" min"); sb.append(newLine); sb.append("Average direct trip length: "); sb.append(avgDirectTripLen); sb.append(" m"); sb.append(newLine); sb.append("Average detour length: "); sb.append(avgDetour); sb.append(" m ("); sb.append(avgDetourPerc); sb.append("%)"); sb.append(newLine); sb.append("Average cost saving: "); sb.append(avgCostSaving); sb.append(" ("); sb.append(avgCostSavingPerc); sb.append("%)"); sb.append(newLine); Writer writer = null; try { writer = new BufferedWriter(new FileWriter(fileReport)); writer.write(sb.toString()); } catch (IOException e) { e.printStackTrace(); } finally { try { writer.close(); } catch (IOException e) { e.printStackTrace(); } } LOGGER.info("The result output has been written into the file " + fileReport.getName()); } else { // use standard output instead LOGGER.info("results: " + "| simul_time total_dist dist_empty_veh d_e_v_perc " + "| max_pass_on_board avg_veh_occupancy total_reqs reqs_on_time " + "| r_o_t_perc reqs_not_served r_n_s_perc avg_wait_time " + "| avg_direct_trip avg_detour a_d_perc avg_cost_saving " + "| a_c_s_perc"); LOGGER.info("| " + simulTime + " " + totalDistTravel + " " + distTravelEmptyVeh + " " + distTravelEmptyVehPerc + " | " + maxPassenOnBoard + " " + avgVehOccupancy + " " + totalReqs + " " + reqsOnTime + " | " + reqsOnTimePerc + " " + reqsNotServed + " " + reqsNotServedPerc + " " + avgWaitingTime + " | " + avgDirectTripLen + " " + avgDetour + " " + avgDetourPerc + " " + avgCostSaving + " | " + avgCostSavingPerc); } } public static StatisticsLogger getInstance() { return instance; } public static void setInstance(StatisticsLogger instance) { if (StatisticsLogger.instance == null) { StatisticsLogger.instance = instance; } } /** * Return the total number of requests in the given state * * @param requests * the map of requests * @param state * the state of requests we're looking for * @return the number of requests in the given state */ private int countNumOfReqs(Map<Request, RequestState> requests, RequestState state) { int numOfReqs = 0; for (Request req : requests.keySet()) { if (requests.get(req) == state) { numOfReqs++; } } return numOfReqs; } /** * Return the total number of requests * * @param requests * the map of requests * @return the number of requests */ private int countNumOfReqs(Map<Request, RequestState> requests) { return requests.keySet().size(); } /** * Get the list of passengers, whose requests have been served on time * * @param requests * the map of requests * @return the list of ids of passengers */ private List<String> getPassengersOnTime(Map<Request, RequestState> requests) { List<String> passOnTime = new ArrayList<String>(); for (Request req : requests.keySet()) { if (requests.get(req) == RequestState.SERVED_ON_TIME) { passOnTime.add(req.getPassengerId()); } } return passOnTime; } /** * Return the requests from the given map according to the id * * @param passengerId * id of the passenger (to identify the request) * @param requests * the map of requests * @return the found request from the map, or null, if it hasn't been found */ private Request findRequest(String passengerId, Map<Request, RequestState> requests) { for (Request req : requests.keySet()) { if (req.getPassengerId().equals(passengerId)) { return req; } } return null; } }