Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ public class EqasimConfigGroup extends ReflectiveConfigGroup {
private final static String ANALYSIS_DISTANCE_UNIT = "analysisDistanceUnit";

private final static String TRAVEL_TIME_RECORDING_INTERVAL = "travelTimeRecordingInterval";
private final static String DETAILED_TRAVEL_TIME_ANALYSIS_INTERVAL = "detailedTravelTimeAnalysisInterval";

private final static String USE_SCHEDULE_BASED_TRANSPORT = "useScheduleBasedTransport";

Expand All @@ -41,6 +42,7 @@ public class EqasimConfigGroup extends ReflectiveConfigGroup {
private DistanceUnit analysisDistanceUnit = DistanceUnit.meter;

private int travelTimeRecordingInterval = 0;
private int detailedTravelTimeAnalysisInterval = 0;

private boolean useScheduleBasedTransport = true;

Expand Down Expand Up @@ -89,6 +91,16 @@ public void setUsePseudoRandomErrors(boolean usePseudoRandomErrors) {
this.usePseudoRandomErrors = usePseudoRandomErrors;
}

@StringGetter(DETAILED_TRAVEL_TIME_ANALYSIS_INTERVAL)
public int getDetailedTravelTimeAnalysisInterval() {
return detailedTravelTimeAnalysisInterval;
}

@StringSetter(DETAILED_TRAVEL_TIME_ANALYSIS_INTERVAL)
public void setDetailedTravelTimeAnalysisInterval(int detailedTravelTimeAnalysisInterval) {
this.detailedTravelTimeAnalysisInterval = detailedTravelTimeAnalysisInterval;
}

@Override
public ConfigGroup createParameterSet(String type) {
switch (type) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,30 @@
package org.eqasim.core.simulation.analysis;

import java.util.HashSet;

import org.eqasim.core.analysis.DefaultPersonAnalysisFilter;
import org.eqasim.core.analysis.PersonAnalysisFilter;
import org.eqasim.core.analysis.activities.ActivityListener;
import org.eqasim.core.analysis.legs.LegListener;
import org.eqasim.core.analysis.pt.PublicTransportLegListener;
import org.eqasim.core.analysis.trips.TripListener;
import org.eqasim.core.components.config.EqasimConfigGroup;
import org.eqasim.core.components.travel_time.TravelTimeRecorder;
import org.eqasim.core.scenario.cutter.network.RoadNetwork;
import org.eqasim.core.simulation.analysis.stuck.StuckAnalysisModule;
import org.eqasim.core.simulation.analysis.travel_time.TravelTimeComparisionListener;
import org.eqasim.core.simulation.modes.drt.analysis.DrtAnalysisModule;
import org.matsim.api.core.v01.network.Network;
import org.matsim.api.core.v01.population.Population;
import org.matsim.contrib.drt.run.MultiModeDrtConfigGroup;
import org.matsim.core.api.experimental.events.EventsManager;
import org.matsim.core.config.Config;
import org.matsim.core.config.groups.RoutingConfigGroup;
import org.matsim.core.controler.AbstractModule;
import org.matsim.core.controler.OutputDirectoryHierarchy;
import org.matsim.core.router.AnalysisMainModeIdentifier;
import org.matsim.core.router.RoutingModeMainModeIdentifier;
import org.matsim.core.utils.timing.TimeInterpretation;
import org.matsim.pt.transitSchedule.api.TransitSchedule;

import com.google.inject.Provides;
Expand All @@ -37,8 +46,9 @@ public void install() {
}

install(new StuckAnalysisModule());

bind(AnalysisMainModeIdentifier.class).toInstance(new RoutingModeMainModeIdentifier());
addControlerListenerBinding().to(TravelTimeComparisionListener.class);
}

@Provides
Expand All @@ -59,7 +69,7 @@ public PublicTransportLegListener providePublicTransportListener(Network network
PersonAnalysisFilter personFilter) {
return new PublicTransportLegListener(schedule);
}

@Provides
@Singleton
public ActivityListener provideActivityListener(PersonAnalysisFilter personFilter) {
Expand All @@ -77,4 +87,15 @@ public TravelTimeRecorder travelTimeRecorder(Network network, Config config) {
}
return new TravelTimeRecorder(new RoadNetwork(network), startTime, stopTime, 600);
}

@Provides
@Singleton
public TravelTimeComparisionListener provideTravelTimeComparisionListener(Population population,
TimeInterpretation timeInterpretation,
OutputDirectoryHierarchy outputDirectoryHierarchy, EventsManager eventsManager,
EqasimConfigGroup eqasimConfig, RoutingConfigGroup routingConfig) {
return new TravelTimeComparisionListener(population, timeInterpretation, outputDirectoryHierarchy,
eventsManager, eqasimConfig.getAnalysisInterval(), eqasimConfig.getDetailedTravelTimeAnalysisInterval(),
new HashSet<>(routingConfig.getNetworkModes()));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,246 @@
package org.eqasim.core.simulation.analysis.travel_time;

import java.io.BufferedWriter;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.math3.stat.descriptive.DescriptiveStatistics;
import org.matsim.api.core.v01.Id;
import org.matsim.api.core.v01.IdMap;
import org.matsim.api.core.v01.events.PersonArrivalEvent;
import org.matsim.api.core.v01.events.PersonDepartureEvent;
import org.matsim.api.core.v01.events.handler.PersonArrivalEventHandler;
import org.matsim.api.core.v01.events.handler.PersonDepartureEventHandler;
import org.matsim.api.core.v01.population.Leg;
import org.matsim.api.core.v01.population.Person;
import org.matsim.api.core.v01.population.PlanElement;
import org.matsim.api.core.v01.population.Population;
import org.matsim.core.api.experimental.events.EventsManager;
import org.matsim.core.controler.OutputDirectoryHierarchy;
import org.matsim.core.controler.events.IterationEndsEvent;
import org.matsim.core.controler.events.IterationStartsEvent;
import org.matsim.core.controler.listener.IterationEndsListener;
import org.matsim.core.controler.listener.IterationStartsListener;
import org.matsim.core.utils.io.IOUtils;
import org.matsim.core.utils.timing.TimeInterpretation;
import org.matsim.core.utils.timing.TimeTracker;

public class TravelTimeComparisionListener
implements IterationStartsListener, IterationEndsListener, PersonDepartureEventHandler,
PersonArrivalEventHandler {
static public final String DETAILED_OUTPUT_NAME = "detailed_travel_time_comparison.csv";
static public final String HOURLY_OUTPUT_NAME = "hourly_travel_time_comparison.csv";
static public final String OVERALL_OUTPUT_NAME = "travel_time_comparison.csv";

private final Population population;
private final TimeInterpretation timeInterpretation;
private final OutputDirectoryHierarchy outputHierarchy;
private final EventsManager eventsManager;

private final int analysisInterval;
private final int detailedAnalysisInterval;

private final Set<String> modes;

private final Map<String, IdMap<Person, List<FinishedLegItem>>> trackedTimes = new HashMap<>();
private final IdMap<Person, OngoingLegItem> ongoing = new IdMap<>(Person.class);

public TravelTimeComparisionListener(Population population, TimeInterpretation timeInterpretation,
OutputDirectoryHierarchy outputDirectoryHierarchy, EventsManager eventsManager, int analysisInterval,
int detailedAnalysisInterval, Set<String> modes) {
this.population = population;
this.timeInterpretation = timeInterpretation;
this.outputHierarchy = outputDirectoryHierarchy;
this.eventsManager = eventsManager;
this.analysisInterval = analysisInterval;
this.detailedAnalysisInterval = detailedAnalysisInterval;
this.modes = modes;
}

private record OngoingLegItem(String mode, double departureTime) {
}

private record FinishedLegItem(double departureTime, double travelTime) {
}

private List<FinishedLegItem> getList(String mode, Id<Person> personId) {
return trackedTimes.computeIfAbsent(mode, m -> new IdMap<>(Person.class)).computeIfAbsent(personId,
p -> new LinkedList<>());
}

@Override
public void notifyIterationStarts(IterationStartsEvent event) {
trackedTimes.clear();
ongoing.clear();

if (analysisInterval > 0 && (event.getIteration() % analysisInterval == 0 || event.isLastIteration())) {
eventsManager.addHandler(this);
}
}

@Override
public void handleEvent(PersonDepartureEvent event) {
if (modes.contains(event.getLegMode())) {
ongoing.put(event.getPersonId(), new OngoingLegItem(event.getLegMode(), event.getTime()));
}
}

@Override
public void handleEvent(PersonArrivalEvent event) {
if (modes.contains(event.getLegMode())) {
OngoingLegItem item = ongoing.remove(event.getPersonId());
getList(item.mode, event.getPersonId())
.add(new FinishedLegItem(item.departureTime, event.getTime() - item.departureTime));
}
}

@Override
public void notifyIterationEnds(IterationEndsEvent event) {
try {
if (analysisInterval > 0 && (event.getIteration() % analysisInterval == 0 || event.isLastIteration())) {
eventsManager.removeHandler(this);

Map<String, DescriptiveStatistics> overallSummary = new HashMap<>();
Map<String, List<DescriptiveStatistics>> hourlySummary = new HashMap<>();

for (String mode : modes) {
overallSummary.put(mode, new DescriptiveStatistics());
hourlySummary.put(mode, new LinkedList<>());

for (int hour = 0; hour < 24; hour++) {
hourlySummary.get(mode).add(new DescriptiveStatistics());
}
}

boolean writeDetails = detailedAnalysisInterval > 0
&& (event.getIteration() % detailedAnalysisInterval == 0 || event.isLastIteration());

BufferedWriter detailsWriter = writeDetails ? IOUtils
.getBufferedWriter(
outputHierarchy.getIterationFilename(event.getIteration(), DETAILED_OUTPUT_NAME))
: null;

if (detailsWriter != null) {
detailsWriter.write(String.join(";", new String[] { //
"person_id", "leg_index", "mode", "planned_departure_time", "planned_travel_time",
"tracked_departure_time", "tracked_travel_time"
}) + "\n");
}

for (Person person : population.getPersons().values()) {
TimeTracker timeTracker = new TimeTracker(timeInterpretation);
int legIndex = 0;

for (PlanElement element : person.getSelectedPlan().getPlanElements()) {
if (element instanceof Leg leg) {
if (modes.contains(leg.getMode())) {
List<FinishedLegItem> finished = getList(leg.getMode(), person.getId());

double trackedDepartureTime = Double.NaN;
double trackedTravelTime = Double.NaN;

if (legIndex < finished.size()) {
FinishedLegItem item = finished.get(legIndex);
trackedDepartureTime = item.departureTime;
trackedTravelTime = item.travelTime;
}

double plannedDepartureTime = timeTracker.getTime().seconds();
double plannedTravelTime = leg.getTravelTime().seconds();

if (detailsWriter != null) {
detailsWriter.write(String.join(";", new String[] { //
person.getId().toString(), //
String.valueOf(legIndex), //
leg.getMode(), //
String.valueOf(plannedDepartureTime), //
String.valueOf(plannedTravelTime), //
String.valueOf(trackedDepartureTime), //
String.valueOf(trackedTravelTime) //
}) + "\n");
}

if (plannedTravelTime > 0.0 || trackedTravelTime > 0.0) {
int hour = (int) Math.floor(plannedDepartureTime / 3600.0);
if (hour < 24 && hour >= 0) {
hourlySummary.get(leg.getMode()).get(hour)
.addValue(plannedTravelTime - trackedTravelTime);
}

overallSummary.get(leg.getMode()).addValue(plannedTravelTime - trackedTravelTime);
}
}

legIndex++;
}

timeTracker.addElement(element);
}
}

if (detailsWriter != null) {
detailsWriter.close();
}

BufferedWriter hourlyWriter = IOUtils.getAppendingBufferedWriter(
outputHierarchy.getIterationFilename(event.getIteration(), HOURLY_OUTPUT_NAME));

hourlyWriter.write(String.join(";", new String[] {
"hour", "mode", "obs", "mean", "median", "q10", "q90", "std"
}) + "\n");

for (String mode : modes) {
for (int hour = 0; hour < 24; hour++) {
DescriptiveStatistics summary = hourlySummary.get(mode).get(hour);

hourlyWriter.write(String.join(";", new String[] {
String.valueOf(hour), mode, //
String.valueOf(summary.getN()), //
String.valueOf(summary.getMean()), //
String.valueOf(summary.getPercentile(50)), //
String.valueOf(summary.getPercentile(10)), //
String.valueOf(summary.getPercentile(90)), //
String.valueOf(summary.getStandardDeviation()) //
}) + "\n");
}
}

hourlyWriter.close();

boolean writeHeader = !new File(outputHierarchy.getOutputFilename(OVERALL_OUTPUT_NAME)).exists();
BufferedWriter overallWriter = IOUtils
.getAppendingBufferedWriter(outputHierarchy.getOutputFilename(OVERALL_OUTPUT_NAME));

if (writeHeader) {
overallWriter.write(String.join(";", new String[] {
"iteration", "mode", "mean", "median", "q10", "q90", "std"
}) + "\n");
}

for (String mode : modes) {
DescriptiveStatistics summary = overallSummary.get(mode);

overallWriter.write(String.join(";", new String[] { //
String.valueOf(event.getIteration()), //
mode, //
String.valueOf(summary.getMean()), //
String.valueOf(summary.getMean()), //
String.valueOf(summary.getPercentile(50)), //
String.valueOf(summary.getPercentile(10)), //
String.valueOf(summary.getPercentile(90)), //
String.valueOf(summary.getStandardDeviation()), //
}) + "\n");
}

overallWriter.close();
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}