Java tutorial
/* *********************************************************************** * * project: org.matsim.* * SocialCostCalculator.java * * * *********************************************************************** * * * * copyright : (C) 2012 by the members listed in the COPYING, * * LICENSE and WARRANTY file. * * email : info at matsim dot org * * * * *********************************************************************** * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * See also COPYING, LICENSE and WARRANTY file * * * * *********************************************************************** */ package playground.christoph.socialcosts; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.apache.commons.math.stat.descriptive.DescriptiveStatistics; import org.apache.log4j.Logger; import org.matsim.api.core.v01.Id; import org.matsim.api.core.v01.TransportMode; import org.matsim.api.core.v01.events.PersonArrivalEvent; import org.matsim.api.core.v01.events.PersonDepartureEvent; import org.matsim.api.core.v01.events.PersonMoneyEvent; import org.matsim.api.core.v01.events.LinkEnterEvent; import org.matsim.api.core.v01.events.LinkLeaveEvent; import org.matsim.api.core.v01.events.handler.PersonArrivalEventHandler; import org.matsim.api.core.v01.events.handler.PersonDepartureEventHandler; import org.matsim.api.core.v01.events.handler.LinkEnterEventHandler; import org.matsim.api.core.v01.events.handler.LinkLeaveEventHandler; import org.matsim.api.core.v01.network.Link; import org.matsim.api.core.v01.network.Network; import org.matsim.api.core.v01.population.Person; import org.matsim.core.api.experimental.events.EventsManager; import org.matsim.core.controler.events.AfterMobsimEvent; import org.matsim.core.controler.events.IterationEndsEvent; import org.matsim.core.controler.events.IterationStartsEvent; import org.matsim.core.controler.listener.AfterMobsimListener; import org.matsim.core.controler.listener.IterationEndsListener; import org.matsim.core.controler.listener.IterationStartsListener; import org.matsim.core.router.util.TravelDisutility; import org.matsim.core.router.util.TravelTime; import org.matsim.vehicles.Vehicle; /** * TODO Analysis: * - performed Trips (Duration and Distance) * - Trip Duration vs. Trip Distance (Mean and Distribution) * * Please note that this code is very experimental, therefore have a critical * look at the results and check whether they are feasible or not. If you think * you found a bug, feel free to contact me! * * @author cdobler */ public class SocialCostCalculator implements TravelDisutility, IterationStartsListener, IterationEndsListener, AfterMobsimListener, PersonDepartureEventHandler, PersonArrivalEventHandler, LinkEnterEventHandler, LinkLeaveEventHandler { private static Logger log = Logger.getLogger(SocialCostCalculator.class); private int travelTimeBinSize; private int numSlots; private Network network; private EventsManager events; private TravelTime travelTime; private double endTime = 48 * 3600; /* * Blur the Social Cost to speed up the relaxation process. Values between * 0.0 and 1.0 are valid. 0.0 means the old value will be kept, 1.0 means * the old value will be totally overwritten. */ private final double blendFactor = 0.25; /* * This is a lookup table because currently the TransportMode of a Leg * is only included in the DepartureEvents but not in the * LinkEnter/LinkLeave Events. */ private Set<String> transportModes; // TransportModes which create congestion and therefore have to be respected private Set<Id> activeAgents; // AgentId - Agents that perform a Leg with a TransportMode contained in transportModes private Map<Id, SocialCostsData> socialCostsMap; // LinkId private Map<Id, LinkTrip> activeTrips; // LinkId private List<LinkTrip> performedTrips; /* * only for Analysis */ private Map<Id, LegTrip> activeLegs; // AgentId private List<LegTrip> performedLegs; private List<Double> meanSocialCosts = new ArrayList<Double>(); private List<Double> medianSocialCosts = new ArrayList<Double>(); private List<Double> quantil25PctSocialCosts = new ArrayList<Double>(); private List<Double> quantil75PctSocialCosts = new ArrayList<Double>(); private List<Double> meanNormalizedSocialCosts = new ArrayList<Double>(); private List<Double> medianNormalizedSocialCosts = new ArrayList<Double>(); private List<Double> quantil25PctNormalizedSocialCosts = new ArrayList<Double>(); private List<Double> quantil75PctNormalizedSocialCosts = new ArrayList<Double>(); public SocialCostCalculator(final Network network, EventsManager events, TravelTime travelTime) { this(network, 15 * 60, 30 * 3600, events, travelTime); // default timeslot-duration: 15 minutes } public SocialCostCalculator(final Network network, final int timeslice, EventsManager events, TravelTime travelTime) { this(network, timeslice, 30 * 3600, events, travelTime); // default: 30 hours at most } public SocialCostCalculator(Network network, int timeslice, int maxTime, EventsManager events, TravelTime travelTime) { this.travelTimeBinSize = timeslice; this.numSlots = (maxTime / this.travelTimeBinSize) + 1; this.network = network; this.events = events; this.travelTime = travelTime; init(); } private void init() { transportModes = new HashSet<String>(); transportModes.add(TransportMode.car); socialCostsMap = new HashMap<Id, SocialCostsData>(); for (Link link : network.getLinks().values()) { SocialCostsData scd = new SocialCostsData(); scd.link = link; scd.socialCosts = new double[this.numSlots]; scd.freeSpeedTravelTime = travelTime.getLinkTravelTime(link, 0.0, null, null); /* * We have to do some blurring here: Imagine an Agent travels over a * Link at FreeSpeed but has to wait one second before it can leave * the Link. The Algorithm would detect this as congestion, which is * not true. Therefore we increase the "FreeSpeedTravelTime" and * assume that each Trip over a Link was traveled with FreeSpeed * as long as its TravelTime was shorter than this * PseudoFreeSpeedTravelTime. */ double pseudoFreeSpeedTravelTime = scd.freeSpeedTravelTime; double pseudoFactor = Math.max(pseudoFreeSpeedTravelTime * 1.01, 2.0); // 1%, but at least 2 seconds pseudoFreeSpeedTravelTime = pseudoFreeSpeedTravelTime + pseudoFactor; scd.pseudoFreeSpeedTravelTime = pseudoFreeSpeedTravelTime; socialCostsMap.put(link.getId(), scd); } activeAgents = new HashSet<Id>(); activeTrips = new HashMap<Id, LinkTrip>(); performedTrips = new ArrayList<LinkTrip>(); activeLegs = new HashMap<Id, LegTrip>(); performedLegs = new ArrayList<LegTrip>(); } @Override public double getLinkTravelDisutility(final Link link, final double time, final Person person, final Vehicle vehicle) { return calcSocCosts(link.getId(), time); } @Override public double getLinkMinimumTravelDisutility(Link link) { throw new UnsupportedOperationException(); } @Override public void notifyIterationStarts(final IterationStartsEvent event) { } @Override public void handleEvent(final LinkEnterEvent event) { /* * Return, if the Agent is on a Leg which does not create congestion. */ if (!activeAgents.contains(event.getPersonId())) return; LinkTrip linkTrip = new LinkTrip(); linkTrip.person_id = event.getPersonId(); linkTrip.link_id = event.getLinkId(); linkTrip.enterTime = event.getTime(); activeTrips.put(event.getPersonId(), linkTrip); /* * Analysis */ LegTrip legTrip = activeLegs.get(event.getPersonId()); if (legTrip == null) { log.error("LegTrip was not found!"); return; } legTrip.linkTrips.add(linkTrip); } @Override public void handleEvent(final LinkLeaveEvent event) { /* * Return, if the Agent is on a Leg which does not create congestion. */ if (!activeAgents.contains(event.getPersonId())) return; LinkTrip linkTrip = activeTrips.get(event.getPersonId()); if (linkTrip == null) { log.error("LinkTrip was not found!"); return; } linkTrip.leaveTime = event.getTime(); performedTrips.add(linkTrip); } @Override public void handleEvent(final PersonDepartureEvent event) { /* * If the TransportMode does not create any congestion (e.g. walk, * bike), it is ignored. */ String transportMode = event.getLegMode(); if (!transportModes.contains(transportMode)) return; LinkTrip linkTrip = new LinkTrip(); linkTrip.person_id = event.getPersonId(); linkTrip.link_id = event.getLinkId(); linkTrip.enterTime = event.getTime(); activeTrips.put(event.getPersonId(), linkTrip); activeAgents.add(event.getPersonId()); /* * Analysis */ LegTrip legTrip = new LegTrip(); legTrip.person_id = event.getPersonId(); legTrip.departureTime = event.getTime(); activeLegs.put(event.getPersonId(), legTrip); } @Override public void handleEvent(final PersonArrivalEvent event) { /* * Try to remove the Agent from the Active Set. If he was not * active, the performed Leg created no congestion and therefore * there is no LinkTrip object. */ boolean wasActive = activeAgents.remove(event.getPersonId()); if (!wasActive) return; LinkTrip linkTrip = activeTrips.remove(event.getPersonId()); if (linkTrip == null) { log.error("LinkTrip was not found!"); return; } linkTrip.leaveTime = event.getTime(); performedTrips.add(linkTrip); /* * Analysis */ LegTrip legTrip = activeLegs.remove(event.getPersonId()); if (legTrip == null) { log.error("LegTrip was not found!"); return; } legTrip.arrivalTime = event.getTime(); performedLegs.add(legTrip); } @Override public void notifyAfterMobsim(AfterMobsimEvent event) { /* * If there are still active Trips left -> end them */ for (LinkTrip trip : activeTrips.values()) { trip.leaveTime = this.endTime; this.performedTrips.add(trip); } activeTrips.clear(); updateSocCosts(event.getIteration()); // printSocialCosts(); calcSocCosts(); calcStatistics(); calcLegStatistics(); } @Override public void notifyIterationEnds(IterationEndsEvent event) { writeSocialCostsPlot(event); } @Override public void reset(final int iteration) { activeAgents.clear(); activeTrips.clear(); performedTrips.clear(); activeLegs.clear(); performedLegs.clear(); } private int getTimeSlotIndex(final double time) { int slice = ((int) time) / travelTimeBinSize; if (slice >= numSlots) slice = numSlots - 1; return slice; } private double calcSocCosts(Id link_id, double enterTime) { double socialCosts = 0.0; int enterIndex = getTimeSlotIndex(enterTime); // if it is the last time slot if (enterIndex == numSlots - 1) { socialCosts = socialCostsMap.get(link_id).socialCosts[enterIndex]; } else { double beginEnterSlot = enterIndex * travelTimeBinSize; /* * if linkTrip.enterTime == beginEnterSlot -> fraction = 1.0 if * linkTrip.enterTime == endEnterSlot -> fraction = 0.0 */ double fraction = 1 - ((enterTime - beginEnterSlot) / travelTimeBinSize); socialCosts = fraction * socialCostsMap.get(link_id).socialCosts[enterIndex] + (1 - fraction) * socialCostsMap.get(link_id).socialCosts[enterIndex + 1]; } return socialCosts; } private void calcSocCosts() { double totalSocialCosts = 0.0; for (LinkTrip linkTrip : performedTrips) { double socialCosts = calcSocCosts(linkTrip.link_id, linkTrip.enterTime); // convert from seconds to CHF (currently defined as 6 CHF/hour) // socialCosts = socialCosts / 600; if (socialCosts <= 0.0) continue; PersonMoneyEvent e = new PersonMoneyEvent(0.5 * (linkTrip.enterTime + linkTrip.leaveTime), linkTrip.person_id, -socialCosts); this.events.processEvent(e); totalSocialCosts = totalSocialCosts + socialCosts; } log.info("Total social costs caused: " + totalSocialCosts); } private void calcStatistics() { // Get a DescriptiveStatistics instance DescriptiveStatistics stats = new DescriptiveStatistics(); DescriptiveStatistics statsNormalized = new DescriptiveStatistics(); // Add the data from the array for (LegTrip legTrip : performedLegs) { double costs = 0.0; for (LinkTrip linkTrip : legTrip.linkTrips) { double socialCosts = calcSocCosts(linkTrip.link_id, linkTrip.enterTime); if (socialCosts > 0.0) costs = costs + socialCosts; } stats.addValue(costs); /* * Normalize a legs social cost by dividing them by the leg travel time. * As a result we get something like social costs per traveled second. * Another option would be doing this on link level instead of leg level. */ double legTravelTime = legTrip.arrivalTime - legTrip.departureTime; if (costs > 0.0 && legTravelTime > 0.0) statsNormalized.addValue(costs / legTravelTime); } // Compute some statistics double sum = stats.getSum(); double mean = stats.getMean(); double std = stats.getStandardDeviation(); double median = stats.getPercentile(50); double quantile25 = stats.getPercentile(25); double quantile75 = stats.getPercentile(75); double sumNormalized = statsNormalized.getSum(); double meanNormalized = statsNormalized.getMean(); double stdNormalized = statsNormalized.getStandardDeviation(); double medianNormalized = statsNormalized.getPercentile(50); double quantile25Normalized = statsNormalized.getPercentile(25); double quantile75Normalized = statsNormalized.getPercentile(75); log.info("Sum of all leg costs: " + sum); log.info("Mean leg costs: " + mean); log.info("Standard deviation: " + std); log.info("Median leg costs: " + median); log.info("25% quantile leg costs: " + quantile25); log.info("75% quantile leg costs: " + quantile75); log.info("Normalized sum of all leg costs: " + sumNormalized); log.info("Normalized mean leg costs: " + meanNormalized); log.info("Normalized standard deviation: " + stdNormalized); log.info("Normalized median leg costs: " + medianNormalized); log.info("Normalized 25% quantile leg costs: " + quantile25Normalized); log.info("Normalized 75% quantile leg costs: " + quantile75Normalized); meanSocialCosts.add(mean); medianSocialCosts.add(median); quantil25PctSocialCosts.add(quantile25); quantil75PctSocialCosts.add(quantile75); meanNormalizedSocialCosts.add(meanNormalized); medianNormalizedSocialCosts.add(medianNormalized); quantil25PctNormalizedSocialCosts.add(quantile25Normalized); quantil75PctNormalizedSocialCosts.add(quantile75Normalized); } private void calcLegStatistics() { double totalDistance = 0.0; double totalTravelTime = 0.0; for (LegTrip legTrip : performedLegs) { for (LinkTrip linkTrip : legTrip.linkTrips) { totalDistance = totalDistance + network.getLinks().get(linkTrip.link_id).getLength(); } totalTravelTime = totalTravelTime + (legTrip.arrivalTime - legTrip.departureTime); } log.info("Total Legs: " + performedLegs.size()); log.info("Total Distance: " + totalDistance); log.info("Total Travel Time: " + totalTravelTime); } private void writeSocialCostsPlot(IterationEndsEvent event) { // We have a data set for each iteration. We start counting by 0, therefore we add 1. int dataLength = event.getIteration() + 1; String fileName = null; SocialCostWriter writer = new SocialCostWriter(event.getIteration()); double[] meanData = new double[dataLength]; double[] medianData = new double[dataLength]; double[] quantil25Data = new double[dataLength]; double[] quantil75Data = new double[dataLength]; for (int i = 0; i < dataLength; i++) { meanData[i] = meanSocialCosts.get(i); medianData[i] = medianSocialCosts.get(i); quantil25Data[i] = quantil25PctSocialCosts.get(i); quantil75Data[i] = quantil75PctSocialCosts.get(i); } fileName = event.getControler().getControlerIO().getOutputFilename("socialCosts"); writer.writeGraphic(fileName + ".png", "social costs (per leg)", meanData, medianData, quantil25Data, quantil75Data); writer.writeTable(fileName + ".txt", meanData, medianData, quantil25Data, quantil75Data); for (int i = 0; i < dataLength; i++) { meanData[i] = meanNormalizedSocialCosts.get(i); medianData[i] = medianNormalizedSocialCosts.get(i); quantil25Data[i] = quantil25PctNormalizedSocialCosts.get(i); quantil75Data[i] = quantil75PctNormalizedSocialCosts.get(i); } fileName = event.getControler().getControlerIO().getOutputFilename("normalizedSocialCosts"); writer.writeGraphic(fileName + ".png", "social costs (per leg, normalized)", meanData, medianData, quantil25Data, quantil75Data); writer.writeTable(fileName + ".txt", meanData, medianData, quantil25Data, quantil75Data); } private void updateSocCosts(int iteration) { for (SocialCostsData data : this.socialCostsMap.values()) { int ke = this.numSlots; // ke = K // for k = K-1 .. 0 for (int k = this.numSlots - 1; k >= 0; k--) { /* * if ta(k) = tfree then ke = k We have to use "<=" because we * use a PseudoFreeSpeedTravelTime! */ if (travelTime.getLinkTravelTime(data.link, k * travelTimeBinSize, null, null) <= data.pseudoFreeSpeedTravelTime) ke = k; // Ca(k) = max(0, (ke - k)*T - tfree) double socialCost = (ke - k) * travelTimeBinSize - data.freeSpeedTravelTime; if (socialCost < 0.0) socialCost = 0.0; // data.socialCosts[k] = socialCost; // If it is the first iteration, there is no old value, therefore use this iterations value. double oldValue; if (iteration == 0) oldValue = socialCost; else oldValue = data.socialCosts[k]; double blendedOldValue = (1 - blendFactor) * oldValue; double blendedNewValue = blendFactor * socialCost; data.socialCosts[k] = blendedOldValue + blendedNewValue; } } } private void printSocialCosts() { for (SocialCostsData data : this.socialCostsMap.values()) { String arrayString = ""; for (double d : data.socialCosts) arrayString = arrayString + " " + d; log.info(data.link.getId() + " " + arrayString); } } private static class SocialCostsData { Link link; double[] socialCosts; double freeSpeedTravelTime; // TODO: make this time-dependent double pseudoFreeSpeedTravelTime; // TODO: make this time-dependent } private static class LinkTrip { Id person_id; Id link_id; double enterTime; double leaveTime; } private static class LegTrip { Id person_id; double departureTime; double arrivalTime; List<LinkTrip> linkTrips = new ArrayList<LinkTrip>(); } }