MATSIM
TravelDistanceStats.java
Go to the documentation of this file.
1 /* *********************************************************************** *
2  * project: org.matsim.*
3  * TravelDistanceStats.java
4  * *
5  * *********************************************************************** *
6  * *
7  * copyright : (C) 2008 by the members listed in the COPYING, *
8  * LICENSE and WARRANTY file. *
9  * email : info at matsim dot org *
10  * *
11  * *********************************************************************** *
12  * *
13  * This program is free software; you can redistribute it and/or modify *
14  * it under the terms of the GNU General Public License as published by *
15  * the Free Software Foundation; either version 2 of the License, or *
16  * (at your option) any later version. *
17  * See also COPYING, LICENSE and WARRANTY file *
18  * *
19  * *********************************************************************** */
20 
21 package org.matsim.analysis;
22 
23 import org.apache.logging.log4j.LogManager;
24 import org.apache.logging.log4j.Logger;
25 import org.matsim.api.core.v01.IdMap;
35 import org.matsim.core.utils.io.IOUtils;
36 
37 import jakarta.inject.Inject;
38 import java.io.BufferedWriter;
39 import java.io.IOException;
40 import java.io.UncheckedIOException;
41 import java.util.*;
42 import java.util.stream.Collectors;
43 
58 public class TravelDistanceStats {
59 
60  private static final int MAX_ITERATIONS_IN_GRAPH = 5000;
61 
63  private BufferedWriter out;
64  private final String legStatsPngName;
65  private final String tripStatsPngName;
66  private final String delimiter;
67 
68  private Map<Integer, DoubleSummaryStatistics> legStats;
69  private Map<Integer, DoubleSummaryStatistics> tripStats;
70 
71  private final static Logger log = LogManager.getLogger(TravelDistanceStats.class);
72 
73  @Inject
74  TravelDistanceStats(ControllerConfigGroup controllerConfigGroup, OutputDirectoryHierarchy controlerIO, GlobalConfigGroup globalConfig) {
75  this(controllerConfigGroup, controlerIO.getOutputFilename("traveldistancestats"),
76  controlerIO.getOutputFilename("traveldistancestats") + "legs",
77  controlerIO.getOutputFilename("traveldistancestats") + "trips",
78  globalConfig.getDefaultDelimiter());
79  }
80 
81  private TravelDistanceStats(ControllerConfigGroup controllerConfigGroup, String travelDistanceStatsFileName, String legStatsPngName,
82  String tripStatsPngName, String delimiter) {
83  this.controllerConfigGroup = controllerConfigGroup;
84  this.legStatsPngName = legStatsPngName;
85  this.tripStatsPngName = tripStatsPngName;
86  this.delimiter = delimiter;
87  initStats(controllerConfigGroup);
88  initWriter(travelDistanceStatsFileName);
89  }
90 
91  private void initWriter(String travelDistanceStatsFileName) {
92  if (travelDistanceStatsFileName.toLowerCase(Locale.ROOT).endsWith(".csv")) {
93  this.out = IOUtils.getBufferedWriter(travelDistanceStatsFileName);
94  } else {
95  this.out = IOUtils.getBufferedWriter(travelDistanceStatsFileName + ".csv");
96  }
97 
98  try {
100  } catch (IOException e) {
101  throw new UncheckedIOException(e);
102  }
103  }
104 
105  private void initStats(ControllerConfigGroup controllerConfigGroup) {
106  int expectedIterations = controllerConfigGroup.getLastIteration() - controllerConfigGroup.getFirstIteration();
107  this.legStats = new HashMap<>(expectedIterations+1);
108  this.tripStats = new HashMap<>(expectedIterations+1);
109  }
110 
111  public void addIteration(int iteration, IdMap<Person, Plan> map) {
112  DoubleSummaryStatistics legStats = getLegStats(map);
113  DoubleSummaryStatistics tripStats = getTripStats(map);
114 
115  log.info("-- average leg distance per plan (executed plans only): " + legStats.getAverage() + " meters");
116  log.info("average leg distance per Person (executed plans only): " + legStats.getSum() / map.size() + " meters (statistic on all " + legStats.getCount() + " legs which have a finite distance)");
117  log.info("-- average trip distance per plan (executed plans only): " + tripStats.getAverage() + " meters");
118  log.info("average trip distance per Person (executed plans only): " + tripStats.getSum() / map.size() + " meters (statistic on all " + tripStats.getCount() + " trips which have a finite distance)");
119  log.info("(TravelDistanceStats takes an average over all legs where the simulation reports travelled (network) distances");
120  log.info("(and teleported legs whose route contains a distance.)");// TODO: still valid?
121 
122  this.legStats.put(iteration, legStats);
123  this.tripStats.put(iteration, tripStats);
124 
125  assert this.legStats.size() == this.tripStats.size();
126  }
127 
128  void writeOutput(int iteration, boolean writePngs){
129  writeCsvEntry(iteration);
130 
131  if(writePngs && this.legStats.size() < MAX_ITERATIONS_IN_GRAPH){
132  writePngs(iteration);
133  }
134  }
135 
136  private void writeCsvHeader() throws IOException {
137  this.out.write("ITERATION" + this.delimiter + "avg. Average Leg distance" + this.delimiter + "avg. Average Trip distance\n");
138  this.out.flush();
139  }
140 
141  private void writeCsvEntry(int iteration) {
142  DoubleSummaryStatistics legStats = this.legStats.get(iteration);
143  DoubleSummaryStatistics tripStats = this.tripStats.get(iteration);
144 
145  try {
146  this.out.write(iteration + this.delimiter + legStats.getAverage() + this.delimiter + tripStats.getAverage() + "\n");
147  this.out.flush();
148  } catch (IOException e) {
149  e.printStackTrace();
150  }
151  }
152 
153  void writePngs(int iteration){
154  writeLegStatsPng(iteration);
155  writeTripStatsPng(iteration);
156  }
157 
158  private void writeTripStatsPng(int iteration) {
159  if (iteration == controllerConfigGroup.getFirstIteration()){
160  return;
161  }
162 
163  // create chart when data of more than one iteration is available.
164  XYLineChart chart = new XYLineChart("Trip Travel Distance Statistics", "iteration", "average of the average trip distance per plan ");
165  double[] iterations = new double[iteration + 1];
166  double[] values = new double[iteration + 1];
167 
168  for (int i = 0; i <= iteration; i++) {
169  iterations[i] = i + controllerConfigGroup.getFirstIteration();
170  values[i] = Optional.ofNullable(this.tripStats.get(i)).map(DoubleSummaryStatistics::getAverage).orElse(0.);
171  }
172 
173  chart.addSeries("executed plan", iterations, values);
174  chart.addMatsimLogo();
175  chart.saveAsPng(this.tripStatsPngName + ".png", 800, 600);
176  }
177 
178  private void writeLegStatsPng(int iteration) {
179  if (iteration == controllerConfigGroup.getFirstIteration()){
180  return;
181  }
182 
183  // create chart when data of more than one iteration is available.
184  XYLineChart chart = new XYLineChart("Leg Travel Distance Statistics", "iteration", "average of the average leg distance per plan ");
185  double[] iterations = new double[iteration + 1];
186  double[] values = new double[iteration + 1];
187 
188  for (int i = 0; i <= iteration; i++) {
189  iterations[i] = i + controllerConfigGroup.getFirstIteration();
190  values[i] = Optional.ofNullable(this.legStats.get(i)).map(DoubleSummaryStatistics::getAverage).orElse(0.);
191 
192  }
193 
194  chart.addSeries("executed plan", iterations, values);
195  chart.addMatsimLogo();
196  chart.saveAsPng(this.legStatsPngName + ".png", 800, 600);
197  }
198 
199  private static DoubleSummaryStatistics getTripStats(IdMap<Person, Plan> map) {
200  return map.values()
201  .parallelStream()
202  .flatMap(plan -> TripStructureUtils.getTrips(plan).stream())
203  .mapToDouble(t -> {
204  Trip trip = (Trip) t;
205  return trip.getTripElements()
206  .stream()
207  .filter(Leg.class::isInstance)
208  .collect(Collectors.summingDouble(l -> {
209  Leg leg = (Leg) l;
210  // TODO NaN handling of Collectors.summingDouble will lead to many NaNs... rethink
211  return leg.getRoute() != null ? leg.getRoute().getDistance() : Double.NaN;
212  }));
213  })
214  // the following means trips with infinite distance are silently ignored.
215  .filter(Double::isFinite)
216  .summaryStatistics();
217  }
218 
219  private static DoubleSummaryStatistics getLegStats(IdMap<Person, Plan> map) {
220  return map.values()
221  //TODO: This probably doesn't control how many threads parallelStream is using despite the number of threads setting in config
222  .parallelStream()
223  .flatMap(plan -> plan.getPlanElements().stream())
224  .filter(Leg.class::isInstance)
225  .mapToDouble(l -> {
226  Leg leg = (Leg) l;
227  return leg.getRoute() != null ? leg.getRoute().getDistance() : Double.NaN;
228  })
229  // the following means legs with infinite distance are ignored
230  .filter(Double::isFinite)
231  .summaryStatistics();
232  }
233 
234  public void close() {
235  try {
236  this.out.close();
237  } catch (IOException e) {
238  e.printStackTrace();
239  }
240 
241  }
242 }
static DoubleSummaryStatistics getTripStats(IdMap< Person, Plan > map)
final ControllerConfigGroup controllerConfigGroup
TravelDistanceStats(ControllerConfigGroup controllerConfigGroup, String travelDistanceStatsFileName, String legStatsPngName, String tripStatsPngName, String delimiter)
Collection< V > values()
Definition: IdMap.java:204
static DoubleSummaryStatistics getLegStats(IdMap< Person, Plan > map)
static BufferedWriter getBufferedWriter(URL url, Charset charset, boolean append)
Definition: IOUtils.java:390
Map< Integer, DoubleSummaryStatistics > legStats
final void addSeries(final String title, final double[] xs, final double[] ys)
static List< Trip > getTrips(final Plan plan)
void initStats(ControllerConfigGroup controllerConfigGroup)
Map< Integer, DoubleSummaryStatistics > tripStats
void saveAsPng(final String filename, final int width, final int height)
Definition: ChartUtil.java:65
void initWriter(String travelDistanceStatsFileName)
void addIteration(int iteration, IdMap< Person, Plan > map)