MATSIM
WithinDayTravelTime.java
Go to the documentation of this file.
1 /* *********************************************************************** *
2  * project: org.matsim.*
3  * WithinDayTravelTime.java
4  * *
5  * *********************************************************************** *
6  * *
7  * copyright : (C) 2011 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.withinday.trafficmonitoring;
22 
23 import java.util.ArrayList;
24 import java.util.Collection;
25 import java.util.HashMap;
26 import java.util.HashSet;
27 import java.util.Iterator;
28 import java.util.List;
29 import java.util.Map;
30 import java.util.Queue;
31 import java.util.Set;
32 import java.util.TreeMap;
33 import java.util.concurrent.BrokenBarrierException;
34 import java.util.concurrent.ConcurrentHashMap;
35 import java.util.concurrent.CyclicBarrier;
36 
37 import jakarta.inject.Inject;
38 import jakarta.inject.Singleton;
39 
40 import org.apache.logging.log4j.LogManager;
41 import org.apache.logging.log4j.Logger;
42 import org.matsim.api.core.v01.Id;
43 import org.matsim.api.core.v01.Scenario;
57 import org.matsim.core.gbl.Gbl;
66 import org.matsim.core.mobsim.qsim.QSim;
73 import org.matsim.core.utils.misc.Time;
74 import org.matsim.vehicles.Vehicle;
75 
85 @Singleton
86 public class WithinDayTravelTime implements TravelTime,
91 
92  private static final Logger log = LogManager.getLogger(WithinDayTravelTime.class);
93 
94  private Network network;
95 
96  // Trips with no Activity on the current Link
97  private Map<Id<Vehicle>, TripBin> regularActiveTrips; // VehicleId
98  private Map<Id<Link>, TravelTimeInfo> travelTimeInfos; // LinkId
99 
101 
102  // Links that are changed by network change events
103  private TreeMap<Double, Map<Link,Double>> changedLinksByTime;
104  // yy better a priority queue. kai, dec'17
105 
106  /*
107  * For parallel Execution
108  */
109  private CyclicBarrier startBarrier;
110  private CyclicBarrier endBarrier;
112  private final int numOfThreads;
113 
114  private final int infoTimeStep = 3600;
115  private int nextInfoTime = 0;
116 
117  private Set<Id<Vehicle>> vehiclesToFilter;
118  private final Set<String> analyzedModes;
119  private final boolean filterModes;
120 
121  private boolean problem = true ;
122  private int resetCnt = 0;
123 
124  private double now = Double.NEGATIVE_INFINITY ;
125 
126  @Inject
127  WithinDayTravelTime(Scenario scenario) {
128  this(scenario, null);
129  }
130 
131  public WithinDayTravelTime(Scenario scenario, Set<String> analyzedModes) {
132 // log.setLevel(Level.DEBUG);
133 
134  /*
135  * The parallelization should scale almost linear, therefore we do use
136  * the number of available threads according to the config file.
137  */
138  this.network = scenario.getNetwork();
139  this.numOfThreads = scenario.getConfig().global().getNumberOfThreads();
140 
141  if (analyzedModes == null || analyzedModes.size() == 0) {
142  this.filterModes = false;
143  this.analyzedModes = null;
144  } else {
145  this.analyzedModes = new HashSet<>(analyzedModes);
146  filterModes = true;
147  }
148 
149  init();
150  }
151 
152  private void init() {
153  this.regularActiveTrips = new HashMap<>();
154  this.travelTimeInfos = new ConcurrentHashMap<>();
155  this.changedLinksByTime = new TreeMap<>();
156  this.vehiclesToFilter = new HashSet<>();
157 
158  // one TravelTimeInfo per link:
159  for (Link link : this.network.getLinks().values()) {
160  TravelTimeInfo travelTimeInfo = new TravelTimeInfo();
161  this.travelTimeInfos.put(link.getId(), travelTimeInfo);
162  }
163 
164  /*
165  * If no RoutingNetwork is used, ArrayBasedTravelTimeInfoProvider uses
166  * a MapBasedTravelTimeInfoProvider as fall back solution. This increases
167  * routing times of a AStarLandmarks router by ~5%.
168  */
169 // this.travelTimeInfoProvider = new MapBasedTravelTimeInfoProvider(this.travelTimeInfos);
170  this.travelTimeInfoProvider = new ArrayBasedTravelTimeInfoProvider(this.travelTimeInfos, this.network);
171 
172  /*
173  * If the network is time variant, we have to update the link parameters
174  * according to the network change events.
175  */
176  Queue<NetworkChangeEvent> networkChangeEvents = NetworkUtils.getNetworkChangeEvents(this.network);
177 
178  // I just changed the return type of the network change events from Collection to Queue.
179  // This should, in a first step, allow to remove the separate changedLinksByTime data structure.
180  // In a second step, this should allow to insert link change events after everything
181  // has started. kai, dec'17
182  // Well, but maybe I don't even want this, and I want this class here recognize changes
183  // only when someone observes them??? (bushfire evacuation use case). kai, dec'17
184 
185  // If one looks at transport telematics, then we need to also be able to set expected
186  // speeds for the future! Which would indeed justify its own data model. kai, dec'17
187  // (in contrast, watch out: the NetworkChangeEventsEngine also keeps its own
188  // copy for the mobsim)
189 
190  if (networkChangeEvents != null) {
191  for (NetworkChangeEvent networkChangeEvent : networkChangeEvents) {
192  addNetworkChangeEventToLocalDataStructure(networkChangeEvent);
193  }
194  }
195  }
196 
198  ChangeValue freespeedChange = networkChangeEvent.getFreespeedChange();
199  if (freespeedChange != null) {
200  double startTime = networkChangeEvent.getStartTime();
201  Map<Link,Double> newLinkSpeedsAtThisTime = changedLinksByTime.computeIfAbsent(startTime, k -> new HashMap<>());
202  for ( Link link : networkChangeEvent.getLinks() ) {
203  // yy seems that the following should be available centrally. kai, dec'17
204  double newSpeed ;
205  switch ( freespeedChange.getType() ) {
206  case ABSOLUTE_IN_SI_UNITS:
207  newSpeed = freespeedChange.getValue() ;
208  break;
209  case FACTOR:
210  newSpeed = link.getFreespeed() * freespeedChange.getValue() ;
211  break;
212  case OFFSET_IN_SI_UNITS:
213  newSpeed = link.getFreespeed() + freespeedChange.getValue() ;
214  break;
215  default:
216  throw new RuntimeException("change event type not implemented") ;
217  }
218  if ( startTime > 0. ) {
219  log.debug( "registering a change event for time=" + startTime
220  + "; linkId=" + link.getId() ) ;
221  }
222  newLinkSpeedsAtThisTime.put( link, newSpeed ) ;
223  }
224  }
225  }
226 
227  public final void addNetworkChangeEvent(NetworkChangeEvent networkChangeEvent) {
228  this.addNetworkChangeEventToLocalDataStructure(networkChangeEvent);
229  }
230 
231  @Override
232  public double getLinkTravelTime(Link link, double time, Person person, Vehicle vehicle) {
233  final double travelTime = this.travelTimeInfoProvider.getTravelTimeInfo(link).travelTime;
234  return travelTime;
235  }
236 
237  @Override
238  public void reset(int iteration) {
239  init();
240  resetCnt++ ;
241  if ( resetCnt >1 ) {
242  if ( problem ) {
243  throw new RuntimeException("using WithinDayTravelTime, but mobsim notifications not called between two resets. "
244  + "Did you really add this as a mobsim listener?") ;
245  }
246  }
247  }
248 
249  @Override
250  public void handleEvent(LinkEnterEvent event) {
251  /*
252  * If only some modes are analyzed, we check whether the vehicle
253  * performs a trip with one of those modes. if not, we skip the event.
254  */
255  if (filterModes && vehiclesToFilter.contains(event.getVehicleId())) return;
256 
257  Id<Vehicle> vehicleId = event.getVehicleId();
258  double time = event.getTime();
259 
260  TripBin tripBin = new TripBin();
261  tripBin.enterTime = time;
262 
263  this.regularActiveTrips.put(vehicleId, tripBin);
264  }
265 
266  @Override
267  public void handleEvent(LinkLeaveEvent event) {
268  Id<Link> linkId = event.getLinkId();
269  Id<Vehicle> vehicleId = event.getVehicleId();
270  double time = event.getTime();
271 
272  TripBin tripBin = this.regularActiveTrips.remove(vehicleId);
273  if (tripBin != null) {
274  tripBin.leaveTime = time;
275 
276  double tripTime = tripBin.leaveTime - tripBin.enterTime;
277 
278  TravelTimeInfo travelTimeInfo = this.travelTimeInfoProvider.getTravelTimeInfo(linkId);
279  travelTimeInfo.tripBins.add(tripBin);
280  travelTimeInfo.addedTravelTimes += tripTime;
281  travelTimeInfo.addedTrips++;
282 
283  travelTimeInfo.checkActiveState();
284  travelTimeInfo.checkBinSize(tripTime);
285  }
286  }
287 
288  /*
289  * We don't have to count Stuck Events. The MobSim creates LeaveLink Events
290  * before throwing Stuck Events.
291  */
292  @Override
293  public void handleEvent(PersonStuckEvent event) {
294 
295  }
296 
297  /*
298  * If a vehicle leaves the traffic we have to remove its current
299  * trip. Otherwise we would have a trip with the duration of the trip itself
300  * and the activity.
301  */
302  @Override
304  Id<Vehicle> vehicleId = event.getVehicleId();
305 
306  this.regularActiveTrips.remove(vehicleId);
307 
308  // try to remove vehicle from set with filtered vehicles
309  if (filterModes) this.vehiclesToFilter.remove(event.getVehicleId());
310  }
311 
312  @Override
314  /*
315  * If filtering transport modes is enabled and the vehicle
316  * starts a leg on a non analyzed transport mode, add the vehicle
317  * to the filtered vehicles set.
318  */
319  if (filterModes && !analyzedModes.contains(event.getNetworkMode())) this.vehiclesToFilter.add(event.getVehicleId());
320  }
321 
322  /*
323  * Initially set free speed travel time.
324  */
325  @Override
327  problem = false ;
328 
329  if (e.getQueueSimulation() instanceof QSim) {
330  double simStartTime = ((QSim) e.getQueueSimulation()).getSimTimer().getSimStartTime();
331 
332  /*
333  * infoTime may be < simStartTime, this ensures to print
334  * out the info at the very first timestep already
335  */
336  this.nextInfoTime = (int)(Math.floor(simStartTime / this.infoTimeStep) * this.infoTimeStep);
337  }
338 
339 
340  for (Link link : this.network.getLinks().values()) {
341  double freeSpeedTravelTime = link.getLength() / link.getFreespeed();
342 
343  TravelTimeInfo travelTimeInfo = this.travelTimeInfoProvider.getTravelTimeInfo(link);
344  travelTimeInfo.travelTime = freeSpeedTravelTime;
345  travelTimeInfo.init(freeSpeedTravelTime);
346  }
347 
348  // Now initialize the Parallel Update Threads
350  }
351 
352  // Update Link TravelTimeInfos if link attributes have changed
353  @Override
355  problem = false ;
356 
357  // yyyy In terms of "bushfire evacuation" design (and maybe even in terms of more general transport telematics)
358  // one would need some settable TimeDependentNetworkUpdater class. kai, dec'17
359 
360 // Collection<Link> links = changedLinksByTime.remove(e.getSimulationTime());
361  // yyyyyy I find it quite optimistic to do this with doubles. What
362  // if someone adds a link change event in between two integer
363  // time steps? kai, dec'17
364 
365  while( !changedLinksByTime.isEmpty() && changedLinksByTime.firstKey() <= e.getSimulationTime() ) {
366  Map<Link, Double> map = changedLinksByTime.pollFirstEntry().getValue();
367  for ( Map.Entry<Link,Double> link2speed : map.entrySet() ) {
368  Link link = link2speed.getKey() ;
369  double freeSpeedTravelTime = link.getLength() / link2speed.getValue() ;
370  if ( e.getSimulationTime() > ((QSim)e.getQueueSimulation()).getSimTimer().getSimStartTime() ) {
371  // (otherwise, in some simulations one gets a lot of change events at time 0. kai, dec'17)
372  log.debug("time=" + e.getSimulationTime() +
373  "; network change event for link=" + link.getId() +
374  "; new ttime="+ freeSpeedTravelTime );
375  }
376  TravelTimeInfo travelTimeInfo = this.travelTimeInfoProvider.getTravelTimeInfo(link);
377  travelTimeInfo.init(freeSpeedTravelTime);
378  travelTimeInfo.checkActiveState(); // ensure that the estimated link travel time is updated
379  }
380  }
381 
382 // if (links != null) {
383 // for (Link link : links) {
384 // double freeSpeedTravelTime = link.getLength() / link.getFreespeed(e.getSimulationTime());
385 // if ( e.getSimulationTime() > 0 ) {
386 // log.debug("time=" + e.getSimulationTime() +
387 // ";\tnetwork change event for link=" + link.getId() +
388 // ";\tnew ttime="+ freeSpeedTravelTime );
389 // }
390 // TravelTimeInfo travelTimeInfo = this.travelTimeInfoProvider.getTravelTimeInfo(link);
391 // travelTimeInfo.init(freeSpeedTravelTime);
392 // travelTimeInfo.checkActiveState(); // ensure that the estimated link travel time is updated
393 // }
394 // }
395  }
396 
397  // Update Link TravelTimes
398  @Override
400  problem = false ;
401 
402  now = e.getSimulationTime() ;
403 
404  // parallel Execution
405  this.run(e.getSimulationTime());
406 
408  }
409 
410 
411  @Override
413  problem = false ;
414 
415  /*
416  * Calling the afterSim Method of the Threads will set their
417  * simulationRunning flag to false.
418  */
419  for (UpdateMeanTravelTimesRunnable runnable : this.updateMeanTravelTimesRunnables) {
420  runnable.afterSim();
421  }
422 
423  /*
424  * Triggering the startBarrier of the UpdateMeanTravelTimesRunnables.
425  * They will check whether the Simulation is still running.
426  * It is not, so the Threads will terminate.
427  */
428  try {
429  this.startBarrier.await();
430  } catch (InterruptedException | BrokenBarrierException ex) {
431  throw new RuntimeException(ex);
432  }
433  }
434 
435  private void printInfo(double time) {
436  if (time >= this.nextInfoTime) {
437  int activeLinks = 0;
438  for (UpdateMeanTravelTimesRunnable runnable : this.updateMeanTravelTimesRunnables) {
439  activeLinks += runnable.getActiveLinksCount();
440  }
441 
442  log.info("WithinDayTravelTime at " + Time.writeTime(time) + " #links=" + activeLinks);
443 
444  this.nextInfoTime += this.infoTimeStep;
445  }
446  }
447 
448  private static class TripBin {
449  double enterTime;
450  double leaveTime;
451  }
452 
453  /*package*/ static class TravelTimeInfo {
454 
456  List<TripBin> tripBins = new ArrayList<>();
457 
458  boolean isActive = false;
459  // int numActiveTrips = 0;
460  int addedTrips = 0;
461  double addedTravelTimes = 0.0;
462  double sumTravelTimes = 0.0; // We cache the sum of the TravelTimes
463 
464  double freeSpeedTravelTime = Double.MAX_VALUE; // We cache the FreeSpeedTravelTimes
465  double travelTime = Double.MAX_VALUE;
466 
467  double dynamicBinSize = 0.0; // size of the time window that is taken into account
468 
469  static Counter enlarge = new Counter("WithinDayTravelTime: enlarged time bin size: ");
470  static Counter shrink = new Counter("WithinDayTravelTime: shrunk time bin size: ");
471 
472  /*package*/ void init(double freeSpeedTravelTime) {
473  this.freeSpeedTravelTime = freeSpeedTravelTime;
474  this.dynamicBinSize = freeSpeedTravelTime * 2.5;
475  }
476 
477  /*package*/ void checkActiveState() {
478  if (!isActive) {
479  this.isActive = true;
480  runnable.addTravelTimeInfo(this);
481  }
482  }
483 
484  /*package*/ void checkBinSize(double tripTime) {
485  if (tripTime > dynamicBinSize) {
486  dynamicBinSize = tripTime * 2;
487  enlarge.incCounter();
488  } else if (tripTime * 3 < dynamicBinSize) {
489  dynamicBinSize = tripTime * 3;
490  shrink.incCounter();
491  }
492  }
493  }
494 
495  /*
496  * ----------------------------------------------------------------
497  * Methods for parallel Execution
498  * ----------------------------------------------------------------
499  */
500 
501  /*
502  * The Threads are waiting at the TimeStepStartBarrier. We trigger them by
503  * reaching this Barrier. Now the Threads will start moving the Nodes. We
504  * wait until all of them reach the TimeStepEndBarrier to move on. We should
505  * not have any Problems with Race Conditions because even if the Threads
506  * would be faster than this Thread, means they reach the TimeStepEndBarrier
507  * before this Method does, it should work anyway.
508  */
509  private void run(double time) {
510 
511  try {
512  // set current Time
513  for (UpdateMeanTravelTimesRunnable updateMeanTravelTimesRunnable : updateMeanTravelTimesRunnables) {
514  updateMeanTravelTimesRunnable.setTime(time);
515  }
516 
517  this.startBarrier.await();
518 
519  this.endBarrier.await();
520  } catch (InterruptedException | BrokenBarrierException e) {
521  throw new RuntimeException(e);
522  }
523  }
524 
525  private void initParallelThreads() {
526 
527  this.startBarrier = new CyclicBarrier(numOfThreads + 1);
528  this.endBarrier = new CyclicBarrier(numOfThreads + 1);
529 
530  Thread[] threads = new Thread[numOfThreads];
531  this.updateMeanTravelTimesRunnables = new UpdateMeanTravelTimesRunnable[numOfThreads];
532 
533  // setup threads
534  for (int i = 0; i < numOfThreads; i++) {
535  UpdateMeanTravelTimesRunnable updateMeanTravelTimesRunnable = new UpdateMeanTravelTimesRunnable();
536  updateMeanTravelTimesRunnable.setStartBarrier(this.startBarrier);
537  updateMeanTravelTimesRunnable.setEndBarrier(this.endBarrier);
538  updateMeanTravelTimesRunnables[i] = updateMeanTravelTimesRunnable;
539 
540  Thread thread = new Thread(updateMeanTravelTimesRunnable);
541  thread.setName("UpdateMeanTravelTimes" + i);
542  thread.setDaemon(true); // make the Thread demons so they will terminate automatically
543  threads[i] = thread;
544 
545  thread.start();
546  }
547 
548  /*
549  * Assign the TravelTimeInfos to the Threads
550  */
551  int roundRobin = 0;
552  for (TravelTimeInfo travelTimeInfo : this.travelTimeInfos.values()) {
553  travelTimeInfo.runnable = updateMeanTravelTimesRunnables[roundRobin % numOfThreads];
554  roundRobin++;
555  }
556 
557  /*
558  * After initialization the Threads are waiting at the endBarrier. We
559  * trigger this Barrier once so they wait at the startBarrier what has
560  * to be their state when the run() method is called.
561  */
562  try {
563  this.endBarrier.await();
564  } catch (InterruptedException | BrokenBarrierException e) {
565  throw new RuntimeException(e);
566  }
567  }
568 
569  /*
570  * The thread class that updates the mean travel times.
571  */
572  private static class UpdateMeanTravelTimesRunnable implements Runnable {
573 
574  private volatile boolean simulationRunning = true;
575 
576  private CyclicBarrier startBarrier = null;
577  private CyclicBarrier endBarrier = null;
578 
580  private Collection<TravelTimeInfo> activeTravelTimeInfos;
581 
583  activeTravelTimeInfos = new ArrayList<>();
584  }
585 
586  public void setStartBarrier(CyclicBarrier cyclicBarrier) {
587  this.startBarrier = cyclicBarrier;
588  }
589 
590  public void setEndBarrier(CyclicBarrier cyclicBarrier) {
591  this.endBarrier = cyclicBarrier;
592  }
593 
594  public void setTime(final double t) {
595  time = OptionalTime.defined(t);
596  }
597 
598  public void addTravelTimeInfo(TravelTimeInfo travelTimeInfo) {
599  this.activeTravelTimeInfos.add(travelTimeInfo);
600  }
601 
602  public int getActiveLinksCount() {
603  return this.activeTravelTimeInfos.size();
604  }
605 
606  public void afterSim() {
607  this.simulationRunning = false;
608  }
609 
610  @Override
611  public void run() {
612 
613  while (true) {
614  try {
615  /*
616  * The End of the Moving is synchronized with the
617  * endBarrier. If all Threads reach this Barrier the main
618  * run() Thread can go on.
619  *
620  * The Threads wait now at the startBarrier until they are
621  * triggered again in the next TimeStep by the main run()
622  * method.
623  */
624  endBarrier.await();
625 
626  startBarrier.await();
627 
628  /*
629  * Check if Simulation is still running.
630  * Otherwise print CPU usage and end Thread.
631  */
632  if (!simulationRunning) {
634  return;
635  }
636 
637  Iterator<TravelTimeInfo> iter = activeTravelTimeInfos.iterator();
638  while (iter.hasNext()) {
639  TravelTimeInfo travelTimeInfo = iter.next();
640  calcBinTravelTime(this.time.seconds(), travelTimeInfo);
641 
642  /*
643  * If no further trips are stored in the TravelTimeInfo,
644  * we deactivate the link and ensure that its expected
645  * travel time is its free speed travel time.
646  */
647  if (travelTimeInfo.tripBins.size() == 0) {
648  travelTimeInfo.isActive = false;
649  travelTimeInfo.travelTime = travelTimeInfo.freeSpeedTravelTime;
650  iter.remove();
651  }
652  }
653 
654  } catch (InterruptedException | BrokenBarrierException e) {
655  throw new RuntimeException(e);
656  }
657  }
658  } // run()
659 
660  private void calcBinTravelTime(double time, TravelTimeInfo travelTimeInfo) {
661  double removedTravelTimes = 0.0;
662 
663  List<TripBin> tripBins = travelTimeInfo.tripBins;
664 
665  // first remove old TravelTimes
666  Iterator<TripBin> iter = tripBins.iterator();
667  while (iter.hasNext()) {
668  TripBin tripBin = iter.next();
669  if (tripBin.leaveTime + travelTimeInfo.dynamicBinSize < time) {
670  double travelTime = tripBin.leaveTime - tripBin.enterTime;
671  removedTravelTimes += travelTime;
672  iter.remove();
673  } else break;
674  }
675 
676  /*
677  * We don't need an update if no Trips have been added or removed
678  * within the current SimStep. The initial FreeSpeedTravelTime has
679  * to be set correctly via setTravelTime!
680  */
681 // if (removedTravelTimes == 0.0 && travelTimeInfo.addedTravelTimes == 0.0) return;
682  // yyyyyy does not work when a network change event comes in. If the old functionality was intentional, we need to talk:
683  // We are setting speed to zero in the bushfire, and if there is no car on the link already, no car will enter it
684  // (because of special within-day rerouting logic). kai, feb'18
685 
686  travelTimeInfo.sumTravelTimes = travelTimeInfo.sumTravelTimes - removedTravelTimes + travelTimeInfo.addedTravelTimes;
687 
688  travelTimeInfo.addedTravelTimes = 0.0;
689  /*
690  * Ensure that we don't allow TravelTimes shorter than the FreeSpeedTravelTime.
691  */
692  double meanTravelTime = travelTimeInfo.freeSpeedTravelTime;
693  if (!tripBins.isEmpty()) meanTravelTime = travelTimeInfo.sumTravelTimes / tripBins.size();
694 
695  if (meanTravelTime < travelTimeInfo.freeSpeedTravelTime) {
696 // log.warn("Mean TravelTime too short?");
697  // can happen when network change event came in with lower speed. kai, feb'18
698  travelTimeInfo.travelTime = travelTimeInfo.freeSpeedTravelTime;
699  } else {
700  travelTimeInfo.travelTime = meanTravelTime;
701  }
702  }
703 
704  } // ReplannerRunnable
705 
706 }
TravelTimeInfo getTravelTimeInfo(final Id< Link > linkId)
WithinDayTravelTime(Scenario scenario, Set< String > analyzedModes)
final void addNetworkChangeEvent(NetworkChangeEvent networkChangeEvent)
void addNetworkChangeEventToLocalDataStructure(NetworkChangeEvent networkChangeEvent)
static Queue< NetworkChangeEvent > getNetworkChangeEvents(Network network)
static final String writeTime(final double seconds, final String timeformat)
Definition: Time.java:80
static OptionalTime defined(double seconds)
Map< Id< Link >, ? extends Link > getLinks()
double getLinkTravelTime(Link link, double time, Person person, Vehicle vehicle)
final GlobalConfigGroup global()
Definition: Config.java:395
static final void printCurrentThreadCpuTime()
Definition: Gbl.java:203