diff --git a/contribs/application/src/main/java/org/matsim/application/analysis/noise/NoiseAnalysis.java b/contribs/application/src/main/java/org/matsim/application/analysis/noise/NoiseAnalysis.java index 8238eec25da..eb76c273c61 100644 --- a/contribs/application/src/main/java/org/matsim/application/analysis/noise/NoiseAnalysis.java +++ b/contribs/application/src/main/java/org/matsim/application/analysis/noise/NoiseAnalysis.java @@ -110,7 +110,8 @@ public Integer call() throws Exception { private Config prepareConfig() { Config config = ConfigUtils.loadConfig(ApplicationUtils.matchInput("config.xml", input.getRunDirectory()).toAbsolutePath().toString(), new NoiseConfigGroup()); - config.vehicles().setVehiclesFile(ApplicationUtils.matchInput("vehicles", input.getRunDirectory()).toAbsolutePath().toString()); + //it is important to match "output_vehicles" because otherwise dvrpVehicle files might be matched and the code crashes later + config.vehicles().setVehiclesFile(ApplicationUtils.matchInput("output_vehicles", input.getRunDirectory()).toAbsolutePath().toString()); config.network().setInputFile(ApplicationUtils.matchInput("network", input.getRunDirectory()).toAbsolutePath().toString()); config.transit().setTransitScheduleFile(null); config.transit().setVehiclesFile(null); diff --git a/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/operations/shifts/analysis/efficiency/ShiftEfficiencyAnalysisControlerListener.java b/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/operations/shifts/analysis/efficiency/ShiftEfficiencyAnalysisControlerListener.java index 57e451f31b0..6dc1a10ce81 100644 --- a/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/operations/shifts/analysis/efficiency/ShiftEfficiencyAnalysisControlerListener.java +++ b/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/operations/shifts/analysis/efficiency/ShiftEfficiencyAnalysisControlerListener.java @@ -10,6 +10,7 @@ package org.matsim.contrib.drt.extension.operations.shifts.analysis.efficiency; import com.google.inject.Inject; +import jakarta.inject.Provider; import org.jfree.chart.ChartFactory; import org.jfree.chart.ChartUtils; import org.jfree.chart.JFreeChart; @@ -27,7 +28,7 @@ import org.matsim.core.controler.listener.IterationEndsListener; import org.matsim.core.utils.io.IOUtils; -import jakarta.inject.Provider; +import java.io.BufferedWriter; import java.io.FileOutputStream; import java.io.IOException; import java.util.*; @@ -43,6 +44,12 @@ public final class ShiftEfficiencyAnalysisControlerListener implements Iteration private final DrtConfigGroup drtConfigGroup; private final ShiftEfficiencyTracker shiftEfficiencyTracker; + private final String delimiter; + private final String runId; + private boolean headerWritten = false; + private static final String notAvailableString = "NA"; + + @Inject public ShiftEfficiencyAnalysisControlerListener(DrtConfigGroup drtConfigGroup, ShiftEfficiencyTracker shiftEfficiencyTracker, @@ -52,6 +59,8 @@ public ShiftEfficiencyAnalysisControlerListener(DrtConfigGroup drtConfigGroup, this.shiftEfficiencyTracker = shiftEfficiencyTracker; this.drtShiftsSpecification = drtShiftsSpecification; this.matsimServices = matsimServices; + this.delimiter = matsimServices.getConfig().global().getDefaultDelimiter(); + this.runId = Optional.ofNullable(matsimServices.getConfig().controller().getRunId()).orElse(notAvailableString); } @Override @@ -59,14 +68,57 @@ public void notifyIterationEnds(IterationEndsEvent event) { int createGraphsInterval = event.getServices().getConfig().controller().getCreateGraphsInterval(); boolean createGraphs = createGraphsInterval >0 && event.getIteration() % createGraphsInterval == 0; - writeAndPlotShiftEfficiency( - shiftEfficiencyTracker.getCurrentRecord().getRevenueByShift(), - shiftEfficiencyTracker.getCurrentRecord().getRequestsByShift(), - shiftEfficiencyTracker.getCurrentRecord().getFinishedShifts(), + ShiftEfficiencyTracker.Record record = shiftEfficiencyTracker.getCurrentRecord(); + writeAndPlotShiftEfficiency( + record.getRevenueByShift(), + record.getRequestsByShift(), + record.getFinishedShifts(), filename(event, "shiftRevenue", ".png"), filename(event, "shiftRidesPerVrh", ".png"), filename(event, "shiftEfficiency", ".csv"), createGraphs); + + List finishedShifts = record.finishedShifts() + .keySet() + .stream() + .map(id -> drtShiftsSpecification.get().getShiftSpecifications().get(id)) + .toList(); + + double earliestShiftStart = finishedShifts.stream().map(DrtShiftSpecification::getStartTime).mapToDouble(d -> d).min().orElse(Double.NaN); + double latestShiftEnd = finishedShifts.stream().map(DrtShiftSpecification::getEndTime).mapToDouble(d -> d).min().orElse(Double.NaN); + + double numberOfShifts = finishedShifts.size(); + double numberOfShiftHours = finishedShifts. + stream() + .map(s -> (s.getEndTime() - s.getStartTime()) - (s.getBreak().isPresent() ? s.getBreak().get().getDuration() : 0.)) + .mapToDouble(d -> d) + .sum() / 3600.; + + long uniqueVehicles = record.getFinishedShifts().values().stream().distinct().count(); + + double totalRevenue = record.revenueByShift().values().stream().mapToDouble(d -> d).sum(); + double meanRevenuePerShift = record.revenueByShift().values().stream().mapToDouble(d -> d).average().orElse(Double.NaN); + double meanRevenuePerShiftHour = totalRevenue / numberOfShiftHours; + + double totalRides = record.getRequestsByShift().values().stream().mapToDouble(List::size).sum(); + double meanRidesPerShift = record.getRequestsByShift().values().stream().mapToDouble(List::size).average().orElse(Double.NaN); + double meanRidesPerShiftHour = totalRides / numberOfShiftHours; + + StringJoiner stringJoiner = new StringJoiner(delimiter); + stringJoiner + .add(earliestShiftStart + "") + .add(latestShiftEnd + "") + .add(numberOfShifts + "") + .add(numberOfShiftHours + "") + .add(uniqueVehicles + "") + .add(meanRevenuePerShift + "") + .add(meanRevenuePerShiftHour + "") + .add(totalRevenue + "") + .add(meanRidesPerShift + "") + .add(meanRidesPerShiftHour + "") + .add(totalRides + ""); + writeIterationShiftEfficiencyStats(stringJoiner.toString(), event.getIteration()); + } private void writeAndPlotShiftEfficiency(Map, Double> revenuePerShift, @@ -126,12 +178,42 @@ private void writeAndPlotShiftEfficiency(Map, Double> revenuePerShi } } + private void writeIterationShiftEfficiencyStats(String summarizeShiftEfficiency, int it) { + try (var bw = getAppendingBufferedWriter("drt_shift_efficiency_metrics", ".csv")) { + if (!headerWritten) { + headerWritten = true; + StringJoiner stringJoiner = new StringJoiner(delimiter); + stringJoiner + .add("earliestShiftStart") + .add("latestShiftEnd") + .add("numberOfShifts") + .add("numberOfShiftHours") + .add("uniqueVehicles") + .add("meanRevenuePerShift") + .add("meanRevenuePerShiftHour") + .add("totalRevenue") + .add("meanRidesPerShift") + .add("meanRidesPerShiftHour") + .add("totalRides"); + bw.write(line("runId", "iteration", stringJoiner.toString())); + } + bw.write(runId + delimiter + it + delimiter + summarizeShiftEfficiency); + bw.newLine(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + private String filename(IterationEndsEvent event, String prefix, String extension) { return matsimServices.getControlerIO() .getIterationFilename(event.getIteration(), prefix + "_" + drtConfigGroup.getMode() + extension); } - private static String line(Object... cells) { - return Arrays.stream(cells).map(Object::toString).collect(Collectors.joining(";", "", "\n")); + private String line(Object... cells) { + return Arrays.stream(cells).map(Object::toString).collect(Collectors.joining(delimiter, "", "\n")); + } + + private BufferedWriter getAppendingBufferedWriter(String prefix, String extension) { + return IOUtils.getAppendingBufferedWriter(matsimServices.getControlerIO().getOutputFilename(prefix + "_" + drtConfigGroup.getMode() + extension)); } } diff --git a/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/operations/shifts/analysis/efficiency/ShiftEfficiencyTracker.java b/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/operations/shifts/analysis/efficiency/ShiftEfficiencyTracker.java index ce4c39a882b..7ccb1edbbfc 100644 --- a/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/operations/shifts/analysis/efficiency/ShiftEfficiencyTracker.java +++ b/contribs/drt-extensions/src/main/java/org/matsim/contrib/drt/extension/operations/shifts/analysis/efficiency/ShiftEfficiencyTracker.java @@ -45,7 +45,7 @@ public final class ShiftEfficiencyTracker implements PersonMoneyEventHandler, private Record currentRecord; - public static record Record(Map, Double> revenueByShift, + public record Record(Map, Double> revenueByShift, Map, Id> shiftByRequest, Map, Id> finishedShifts){ public Map, Double> getRevenueByShift() { diff --git a/contribs/drt-extensions/src/test/java/org/matsim/contrib/drt/extension/h3/RunDrtWithH3ZonalSystemIT.java b/contribs/drt-extensions/src/test/java/org/matsim/contrib/drt/extension/h3/RunDrtWithH3ZonalSystemIT.java index d1ef4e2fea7..c79a868144f 100644 --- a/contribs/drt-extensions/src/test/java/org/matsim/contrib/drt/extension/h3/RunDrtWithH3ZonalSystemIT.java +++ b/contribs/drt-extensions/src/test/java/org/matsim/contrib/drt/extension/h3/RunDrtWithH3ZonalSystemIT.java @@ -61,13 +61,12 @@ public void install() { public void install() { bindModal(DrtZonalWaitTimesAnalyzer.class).toProvider(modalProvider( getter -> new DrtZonalWaitTimesAnalyzer(drtConfig, getter.getModal(DrtEventSequenceCollector.class), - getter.getModal(ZoneSystem.class)))).asEagerSingleton(); + getter.getModal(ZoneSystem.class), config.global().getDefaultDelimiter()))).asEagerSingleton(); addControlerListenerBinding().to(modalKey(DrtZonalWaitTimesAnalyzer.class)); } }); } }); - } } diff --git a/contribs/drt-extensions/src/test/java/org/matsim/contrib/drt/extension/operations/shifts/run/RunShiftDrtScenarioIT.java b/contribs/drt-extensions/src/test/java/org/matsim/contrib/drt/extension/operations/shifts/run/RunShiftDrtScenarioIT.java index c2b671c90e7..522aff3db1b 100644 --- a/contribs/drt-extensions/src/test/java/org/matsim/contrib/drt/extension/operations/shifts/run/RunShiftDrtScenarioIT.java +++ b/contribs/drt-extensions/src/test/java/org/matsim/contrib/drt/extension/operations/shifts/run/RunShiftDrtScenarioIT.java @@ -9,6 +9,7 @@ import org.matsim.contrib.drt.extension.operations.DrtWithOperationsConfigGroup; import org.matsim.contrib.drt.extension.operations.operationFacilities.OperationFacilitiesParams; import org.matsim.contrib.drt.extension.operations.shifts.config.ShiftsParams; +import org.matsim.contrib.drt.fare.DrtFareParams; import org.matsim.contrib.drt.optimizer.DrtOptimizationConstraintsSet; import org.matsim.contrib.drt.optimizer.insertion.extensive.ExtensiveInsertionSearchParams; import org.matsim.contrib.drt.optimizer.rebalancing.RebalancingParams; @@ -142,6 +143,11 @@ void test() { shiftsParams.allowInFieldChangeover = true; drtWithShiftsConfigGroup.addParameterSet(operationsParams); + DrtFareParams drtFareParams = new DrtFareParams(); + drtFareParams.baseFare = 1.; + drtFareParams.distanceFare_m = 1. / 1000; + drtWithShiftsConfigGroup.addParameterSet(drtFareParams); + final Controler run = DrtOperationsControlerCreator.createControler(config, false); run.run(); } diff --git a/contribs/drt/src/main/java/org/matsim/contrib/drt/analysis/DrtAnalysisControlerListener.java b/contribs/drt/src/main/java/org/matsim/contrib/drt/analysis/DrtAnalysisControlerListener.java index 67ee1973686..3c50904db50 100644 --- a/contribs/drt/src/main/java/org/matsim/contrib/drt/analysis/DrtAnalysisControlerListener.java +++ b/contribs/drt/src/main/java/org/matsim/contrib/drt/analysis/DrtAnalysisControlerListener.java @@ -365,7 +365,7 @@ private void writeIterationVehicleStats(String summarizeVehicles, String vehOcc, try (var bw = getAppendingBufferedWriter("drt_detailed_distanceStats", ".csv")) { if (!vheaderWritten) { vheaderWritten = true; - bw.write("runId;iteration"); + bw.write("runId" + delimiter + "iteration"); for (int i = 0; i <= maxcap; i++) { bw.write(delimiter + i + " pax distance_m"); } diff --git a/contribs/drt/src/main/java/org/matsim/contrib/drt/analysis/zonal/DrtModeZonalSystemModule.java b/contribs/drt/src/main/java/org/matsim/contrib/drt/analysis/zonal/DrtModeZonalSystemModule.java index 05129ba7faf..aebb8b44686 100644 --- a/contribs/drt/src/main/java/org/matsim/contrib/drt/analysis/zonal/DrtModeZonalSystemModule.java +++ b/contribs/drt/src/main/java/org/matsim/contrib/drt/analysis/zonal/DrtModeZonalSystemModule.java @@ -90,7 +90,7 @@ public void install() { bindModal(DrtZonalWaitTimesAnalyzer.class).toProvider(modalProvider( getter -> new DrtZonalWaitTimesAnalyzer(drtCfg, getter.getModal(DrtEventSequenceCollector.class), - getter.getModal(ZoneSystem.class)))).asEagerSingleton(); + getter.getModal(ZoneSystem.class), getConfig().global().getDefaultDelimiter()))).asEagerSingleton(); addControlerListenerBinding().to(modalKey(DrtZonalWaitTimesAnalyzer.class)); } } diff --git a/contribs/drt/src/main/java/org/matsim/contrib/drt/analysis/zonal/DrtZonalWaitTimesAnalyzer.java b/contribs/drt/src/main/java/org/matsim/contrib/drt/analysis/zonal/DrtZonalWaitTimesAnalyzer.java index d16a0cd8e0a..ce140ce1772 100644 --- a/contribs/drt/src/main/java/org/matsim/contrib/drt/analysis/zonal/DrtZonalWaitTimesAnalyzer.java +++ b/contribs/drt/src/main/java/org/matsim/contrib/drt/analysis/zonal/DrtZonalWaitTimesAnalyzer.java @@ -58,13 +58,16 @@ public final class DrtZonalWaitTimesAnalyzer implements IterationEndsListener, S private final ZoneSystem zones; private static final Id zoneIdForOutsideOfZonalSystem = Id.create("outsideOfDrtZonalSystem", Zone.class); private static final String notAvailableString = "NaN"; + + private final String delimiter; private static final Logger log = LogManager.getLogger(DrtZonalWaitTimesAnalyzer.class); public DrtZonalWaitTimesAnalyzer(DrtConfigGroup configGroup, DrtEventSequenceCollector requestAnalyzer, - ZoneSystem zones) { + ZoneSystem zones, String delimiter) { this.drtCfg = configGroup; this.requestAnalyzer = requestAnalyzer; this.zones = zones; + this.delimiter = delimiter; } @Override @@ -76,7 +79,6 @@ public void notifyIterationEnds(IterationEndsEvent event) { } public void write(String fileName) { - String delimiter = ";"; Map, DescriptiveStatistics> zoneStats = createZonalStats(); BufferedWriter bw = IOUtils.getBufferedWriter(fileName); try { @@ -85,7 +87,11 @@ public void write(String fileName) { format.setMinimumIntegerDigits(1); format.setMaximumFractionDigits(2); format.setGroupingUsed(false); - bw.append("zone;centerX;centerY;nRequests;sumWaitTime;meanWaitTime;min;max;p95;p90;p80;p75;p50"); + String header = new StringJoiner(delimiter) + .add("zone").add("centerX").add("centerY").add("nRequests") + .add("sumWaitTime").add("meanWaitTime").add("min").add("max") + .add("p95").add("p90").add("p80").add("p75").add("p50").toString(); + bw.append(header); // sorted output SortedSet> zoneIdsAndOutside = new TreeSet<>(zones.getZones().keySet()); zoneIdsAndOutside.add(zoneIdForOutsideOfZonalSystem); @@ -96,31 +102,22 @@ public void write(String fileName) { String centerY = drtZone != null ? String.valueOf(drtZone.getCentroid().getY()) : notAvailableString; DescriptiveStatistics stats = zoneStats.get(zoneId); bw.newLine(); - bw.append(zoneId.toString()) - .append(delimiter) - .append(centerX) - .append(delimiter) - .append(centerY) - .append(delimiter) - .append(format.format(stats.getN())) - .append(delimiter) - .append(format.format(stats.getSum())) - .append(delimiter) - .append(String.valueOf(stats.getMean())) - .append(delimiter) - .append(String.valueOf(stats.getMin())) - .append(delimiter) - .append(String.valueOf(stats.getMax())) - .append(delimiter) - .append(String.valueOf(stats.getPercentile(95))) - .append(delimiter) - .append(String.valueOf(stats.getPercentile(90))) - .append(delimiter) - .append(String.valueOf(stats.getPercentile(80))) - .append(delimiter) - .append(String.valueOf(stats.getPercentile(75))) - .append(delimiter) - .append(String.valueOf(stats.getPercentile(50))); + bw.append( + new StringJoiner(delimiter) + .add(zoneId.toString()) + .add(centerX) + .add(centerY) + .add(format.format(stats.getN())) + .add(format.format(stats.getSum())) + .add(String.valueOf(stats.getMean())) + .add(String.valueOf(stats.getMin())) + .add(String.valueOf(stats.getMax())) + .add(String.valueOf(stats.getPercentile(95))) + .add(String.valueOf(stats.getPercentile(90))) + .add(String.valueOf(stats.getPercentile(80))) + .add(String.valueOf(stats.getPercentile(75))) + .add(String.valueOf(stats.getPercentile(50))).toString() + ); } bw.flush(); bw.close(); diff --git a/contribs/drt/src/main/java/org/matsim/contrib/drt/prebooking/PrebookingStopActivity.java b/contribs/drt/src/main/java/org/matsim/contrib/drt/prebooking/PrebookingStopActivity.java index 1e1965d11a6..762c80500b1 100644 --- a/contribs/drt/src/main/java/org/matsim/contrib/drt/prebooking/PrebookingStopActivity.java +++ b/contribs/drt/src/main/java/org/matsim/contrib/drt/prebooking/PrebookingStopActivity.java @@ -3,6 +3,8 @@ import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.PriorityQueue; +import java.util.Queue; import java.util.Set; import java.util.function.Supplier; @@ -38,7 +40,7 @@ public class PrebookingStopActivity extends FirstLastSimStepDynActivity implemen private final Map, ? extends AcceptedDrtRequest> dropoffRequests; private final IdMap enterTimes = new IdMap<>(Request.class); - private final IdMap leaveTimes = new IdMap<>(Request.class); + private final Queue leaveTimes = new PriorityQueue<>(); private final Set> enteredRequests = new HashSet<>(); private final PrebookingManager prebookingManager; @@ -84,27 +86,30 @@ protected void beforeFirstStep(double now) { private void initDropoffRequests(double now) { for (var request : dropoffRequests.values()) { double leaveTime = now + stopDurationProvider.calcDropoffDuration(vehicle, request.getRequest()); - leaveTimes.put(request.getId(), leaveTime); + leaveTimes.add(new QueuedRequest(request.getId(), leaveTime)); } updateDropoffRequests(now); } private boolean updateDropoffRequests(double now) { - var iterator = leaveTimes.entrySet().iterator(); - while (iterator.hasNext()) { - var entry = iterator.next(); - - if (entry.getValue() <= now) { // Request should leave now - passengerHandler.dropOffPassengers(driver, entry.getKey(), now); - prebookingManager.notifyDropoff(entry.getKey()); - onboard -= dropoffRequests.get(entry.getKey()).getPassengerCount(); - iterator.remove(); - } + while (!leaveTimes.isEmpty() && leaveTimes.peek().time <= now) { + Id requestId = leaveTimes.poll().id; + passengerHandler.dropOffPassengers(driver, requestId, now); + prebookingManager.notifyDropoff(requestId); + onboard -= dropoffRequests.get(requestId).getPassengerCount(); } - return leaveTimes.size() == 0; + return leaveTimes.isEmpty(); + } + + private record QueuedRequest(Id id, double time) implements Comparable { + + @Override + public int compareTo(QueuedRequest o) { + return Double.compare(this.time, o.time); + } } private boolean updatePickupRequests(double now) { diff --git a/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/benchmark/DvrpBenchmarkTravelTimeModule.java b/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/benchmark/DvrpBenchmarkTravelTimeModule.java index 7dad30dbd34..f46302a0d40 100644 --- a/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/benchmark/DvrpBenchmarkTravelTimeModule.java +++ b/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/benchmark/DvrpBenchmarkTravelTimeModule.java @@ -47,7 +47,8 @@ public void install() { addTravelTimeBinding(DvrpTravelTimeModule.DVRP_ESTIMATED).toProvider(() -> { URL url = ConfigGroup.getInputFileURL(getConfig().getContext(), dvrpCfg.initialTravelTimesFile); var timeDiscretizer = new TimeDiscretizer(getConfig().travelTimeCalculator()); - var linkTravelTimes = DvrpOfflineTravelTimes.loadLinkTravelTimes(timeDiscretizer, url); + var linkTravelTimes = DvrpOfflineTravelTimes.loadLinkTravelTimes(timeDiscretizer, url, + getConfig().global().getDefaultDelimiter()); return DvrpOfflineTravelTimes.asTravelTime(timeDiscretizer, linkTravelTimes); }).asEagerSingleton(); } else { diff --git a/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/trafficmonitoring/DvrpOfflineTravelTimeEstimator.java b/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/trafficmonitoring/DvrpOfflineTravelTimeEstimator.java index 33b22634347..5d80e161462 100644 --- a/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/trafficmonitoring/DvrpOfflineTravelTimeEstimator.java +++ b/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/trafficmonitoring/DvrpOfflineTravelTimeEstimator.java @@ -28,6 +28,7 @@ import org.matsim.contrib.dvrp.router.DvrpGlobalRoutingNetworkProvider; import org.matsim.contrib.dvrp.run.DvrpConfigGroup; import org.matsim.contrib.common.timeprofile.TimeDiscretizer; +import org.matsim.core.config.groups.GlobalConfigGroup; import org.matsim.core.config.groups.TravelTimeCalculatorConfigGroup; import org.matsim.core.controler.OutputDirectoryHierarchy; import org.matsim.core.controler.events.AfterMobsimEvent; @@ -63,18 +64,21 @@ public class DvrpOfflineTravelTimeEstimator private final double[][] linkTravelTimes; private final double alpha; + private final String delimiter; + @Inject public DvrpOfflineTravelTimeEstimator(@Named(DvrpTravelTimeModule.DVRP_INITIAL) TravelTime initialTT, - @Named(DvrpTravelTimeModule.DVRP_OBSERVED) TravelTime observedTT, - @Named(DvrpGlobalRoutingNetworkProvider.DVRP_ROUTING) Network network, - TravelTimeCalculatorConfigGroup ttCalcConfig, DvrpConfigGroup dvrpConfig, - OutputDirectoryHierarchy outputDirectoryHierarchy) { - this(initialTT, observedTT, network, new TimeDiscretizer(ttCalcConfig), dvrpConfig.travelTimeEstimationAlpha, outputDirectoryHierarchy); + @Named(DvrpTravelTimeModule.DVRP_OBSERVED) TravelTime observedTT, + @Named(DvrpGlobalRoutingNetworkProvider.DVRP_ROUTING) Network network, + TravelTimeCalculatorConfigGroup ttCalcConfig, DvrpConfigGroup dvrpConfig, + OutputDirectoryHierarchy outputDirectoryHierarchy, GlobalConfigGroup globalConfig) { + this(initialTT, observedTT, network, new TimeDiscretizer(ttCalcConfig), dvrpConfig.travelTimeEstimationAlpha, + outputDirectoryHierarchy, globalConfig.getDefaultDelimiter()); } public DvrpOfflineTravelTimeEstimator(TravelTime initialTT, TravelTime observedTT, Network network, TimeDiscretizer timeDiscretizer, double travelTimeEstimationAlpha, - OutputDirectoryHierarchy outputDirectoryHierarchy) { + OutputDirectoryHierarchy outputDirectoryHierarchy, String delimiter) { this.observedTT = observedTT; this.network = network; @@ -83,6 +87,7 @@ public DvrpOfflineTravelTimeEstimator(TravelTime initialTT, TravelTime observedT this.timeInterval = timeDiscretizer.getTimeInterval(); this.outputDirectoryHierarchy = outputDirectoryHierarchy; + this.delimiter = delimiter; alpha = travelTimeEstimationAlpha; checkArgument(alpha > 0 && alpha <= 1, "travelTimeEstimationAlpha must be in (0,1]"); @@ -115,7 +120,8 @@ public void notifyMobsimBeforeCleanup(@SuppressWarnings("rawtypes") MobsimBefore @Override public void notifyAfterMobsim(AfterMobsimEvent event) { DvrpOfflineTravelTimes.saveLinkTravelTimes(timeDiscretizer, linkTravelTimes, - outputDirectoryHierarchy.getIterationFilename(event.getIteration(), "dvrp_travel_times.csv.gz")); + outputDirectoryHierarchy.getIterationFilename(event.getIteration(), + "dvrp_travel_times.csv.gz"), delimiter); } private void updateTTs(TravelTime travelTime, double alpha) { diff --git a/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/trafficmonitoring/DvrpOfflineTravelTimes.java b/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/trafficmonitoring/DvrpOfflineTravelTimes.java index 67882267db2..d8bc99c89b1 100644 --- a/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/trafficmonitoring/DvrpOfflineTravelTimes.java +++ b/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/trafficmonitoring/DvrpOfflineTravelTimes.java @@ -41,25 +41,25 @@ * @author Michal Maciejewski (michalm) */ public class DvrpOfflineTravelTimes { - private static final String DELIMITER = ";"; public static void saveLinkTravelTimes(TimeDiscretizer timeDiscretizer, double[][] linkTravelTimes, - String filename) { + String filename, String delimiter) { try (Writer writer = IOUtils.getBufferedWriter(filename)) { - saveLinkTravelTimes(timeDiscretizer, linkTravelTimes, writer); + saveLinkTravelTimes(timeDiscretizer, linkTravelTimes, writer, delimiter); } catch (IOException e) { throw new RuntimeException(e); } } - public static void saveLinkTravelTimes(TimeDiscretizer timeDiscretizer, double[][] linkTravelTimes, Writer writer) + public static void saveLinkTravelTimes(TimeDiscretizer timeDiscretizer, double[][] linkTravelTimes, + Writer writer, String delimiter) throws IOException { int intervalCount = timeDiscretizer.getIntervalCount(); //header row - writer.append("linkId" + DELIMITER); + writer.append("linkId").append(delimiter); for (int i = 0; i < intervalCount; i++) { double time = i * timeDiscretizer.getTimeInterval(); - writer.append(time + DELIMITER); + writer.append(String.valueOf(time)).append(delimiter); } writer.append('\n'); @@ -71,12 +71,12 @@ public static void saveLinkTravelTimes(TimeDiscretizer timeDiscretizer, double[] if (ttRow != null) { checkArgument(ttRow.length == intervalCount); - writer.append(Id.get(idx, Link.class) + DELIMITER); + writer.append(String.valueOf(Id.get(idx, Link.class))).append(delimiter); for (int t = 0; t < intervalCount; t++) { // rounding up to full seconds, otherwise the output files are sometimes huge (even when gzipped) // consider having a switch for enabling/disabling rounding int tt = (int)Math.ceil(ttRow[t]);//rounding up to avoid zeros; also QSim rounds up - writer.append(tt + DELIMITER); + writer.append(String.valueOf(tt)).append(delimiter); } writer.append('\n'); } @@ -101,27 +101,28 @@ public static TravelTime asTravelTime(TimeDiscretizer timeDiscretizer, double[][ }; } - public static double[][] loadLinkTravelTimes(TimeDiscretizer timeDiscretizer, URL url) { + public static double[][] loadLinkTravelTimes(TimeDiscretizer timeDiscretizer, URL url, String delimiter) { try (BufferedReader reader = IOUtils.getBufferedReader(url)) { - return loadLinkTravelTimes(timeDiscretizer, reader); + return loadLinkTravelTimes(timeDiscretizer, reader, delimiter); } catch (IOException e) { throw new RuntimeException(e); } } - public static double[][] loadLinkTravelTimes(TimeDiscretizer timeDiscretizer, BufferedReader reader) + public static double[][] loadLinkTravelTimes(TimeDiscretizer timeDiscretizer, BufferedReader reader, + String delimiter) throws IOException { //start with IdMap and then convert to array (to avoid index out of bounds) IdMap linkTravelTimes = new IdMap<>(Link.class); //header row - String[] headerLine = reader.readLine().split(";"); + String[] headerLine = reader.readLine().split(delimiter); verify(timeDiscretizer.getIntervalCount() == headerLine.length - 1); verify(headerLine[0].equals("linkId")); timeDiscretizer.forEach((bin, time) -> verify(Double.parseDouble(headerLine[bin + 1]) == time)); //regular rows - reader.lines().map(line -> line.split(DELIMITER)).forEach(cells -> { + reader.lines().map(line -> line.split(delimiter)).forEach(cells -> { verify(timeDiscretizer.getIntervalCount() == cells.length - 1); double[] row = new double[timeDiscretizer.getIntervalCount()]; diff --git a/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/trafficmonitoring/DvrpTravelTimeModule.java b/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/trafficmonitoring/DvrpTravelTimeModule.java index 798ac1166c1..c295c70ac6a 100644 --- a/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/trafficmonitoring/DvrpTravelTimeModule.java +++ b/contribs/dvrp/src/main/java/org/matsim/contrib/dvrp/trafficmonitoring/DvrpTravelTimeModule.java @@ -48,7 +48,8 @@ public void install() { addTravelTimeBinding(DvrpTravelTimeModule.DVRP_INITIAL).toProvider(() -> { URL url = ConfigGroup.getInputFileURL(getConfig().getContext(), dvrpCfg.initialTravelTimesFile); var timeDiscretizer = new TimeDiscretizer(getConfig().travelTimeCalculator()); - var linkTravelTimes = DvrpOfflineTravelTimes.loadLinkTravelTimes(timeDiscretizer, url); + var linkTravelTimes = DvrpOfflineTravelTimes.loadLinkTravelTimes(timeDiscretizer, url, + getConfig().global().getDefaultDelimiter()); return DvrpOfflineTravelTimes.asTravelTime(timeDiscretizer, linkTravelTimes); }).asEagerSingleton(); } else { diff --git a/contribs/dvrp/src/test/java/org/matsim/contrib/dvrp/trafficmonitoring/DvrpOfflineTravelTimeEstimatorTest.java b/contribs/dvrp/src/test/java/org/matsim/contrib/dvrp/trafficmonitoring/DvrpOfflineTravelTimeEstimatorTest.java index 808df1ff835..2260a2f0979 100644 --- a/contribs/dvrp/src/test/java/org/matsim/contrib/dvrp/trafficmonitoring/DvrpOfflineTravelTimeEstimatorTest.java +++ b/contribs/dvrp/src/test/java/org/matsim/contrib/dvrp/trafficmonitoring/DvrpOfflineTravelTimeEstimatorTest.java @@ -61,7 +61,7 @@ public class DvrpOfflineTravelTimeEstimatorTest { @Test void getLinkTravelTime_timeAreCorrectlyBinned() { - var estimator = new DvrpOfflineTravelTimeEstimator(initialTT, null, network, timeDiscretizer, 0.25, null); + var estimator = new DvrpOfflineTravelTimeEstimator(initialTT, null, network, timeDiscretizer, 0.25, null, ";"); //bin 0 assertThat(linkTravelTime(estimator, linkAB, -1)).isEqualTo(1); @@ -98,7 +98,7 @@ void getLinkTravelTime_exponentialAveragingOverIterations() { }; var estimator = new DvrpOfflineTravelTimeEstimator(initialTT, observedTT, network, timeDiscretizer, alpha, - null); + null,";"); //expected TTs for each time bin double expectedTT_0 = 1; @@ -125,7 +125,8 @@ void getLinkTravelTime_exponentialAveragingOverIterations() { @Test void getLinkTravelTime_linkOutsideNetwork_fail() { var linkOutsideNetwork = new FakeLink(Id.createLinkId("some-link")); - var estimator = new DvrpOfflineTravelTimeEstimator(initialTT, null, network, timeDiscretizer, 0.25, null); + var estimator = new DvrpOfflineTravelTimeEstimator(initialTT, null, network, timeDiscretizer, + 0.25, null, ";"); assertThatThrownBy(() -> linkTravelTime(estimator, linkOutsideNetwork, 0)).isExactlyInstanceOf( NullPointerException.class) diff --git a/contribs/dvrp/src/test/java/org/matsim/contrib/dvrp/trafficmonitoring/DvrpOfflineTravelTimesTest.java b/contribs/dvrp/src/test/java/org/matsim/contrib/dvrp/trafficmonitoring/DvrpOfflineTravelTimesTest.java index e3b904d4084..f8270e2f8b3 100644 --- a/contribs/dvrp/src/test/java/org/matsim/contrib/dvrp/trafficmonitoring/DvrpOfflineTravelTimesTest.java +++ b/contribs/dvrp/src/test/java/org/matsim/contrib/dvrp/trafficmonitoring/DvrpOfflineTravelTimesTest.java @@ -59,7 +59,8 @@ void saveLinkTravelTimes() throws IOException { linkTTs[linkIdB.index()] = new double[] { 5.5, 6.6, 7.7, 8.8, 9.9 }; var stringWriter = new StringWriter(); - DvrpOfflineTravelTimes.saveLinkTravelTimes(new TimeDiscretizer(3600, 900), linkTTs, stringWriter); + DvrpOfflineTravelTimes.saveLinkTravelTimes(new TimeDiscretizer(3600, 900), + linkTTs, stringWriter, ";"); var lines = stringWriter.toString().split("\n"); assertThat(lines).hasSize(3); @@ -76,7 +77,8 @@ void loadLinkTravelTimes() throws IOException { var lines = String.join("\n", line0, line1, line2); var stringReader = new BufferedReader(new StringReader(lines)); - var linkTTs = DvrpOfflineTravelTimes.loadLinkTravelTimes(new TimeDiscretizer(3600, 900), stringReader); + var linkTTs = DvrpOfflineTravelTimes.loadLinkTravelTimes(new TimeDiscretizer(3600, 900), + stringReader, ";"); //the matrix may have more than 2 rows (depends on how many link ids are cached) //all rows are null (except for links A and B) diff --git a/contribs/freight/pom.xml b/contribs/freight/pom.xml index 52eb3942031..e6bcdab4bb8 100644 --- a/contribs/freight/pom.xml +++ b/contribs/freight/pom.xml @@ -86,7 +86,7 @@ org.hsqldb hsqldb - 2.7.2 + 2.7.3 diff --git a/contribs/vsp/src/main/java/playground/vsp/flowEfficiency/LinkTurnDirectionAttributesFromGraphHopper.java b/contribs/vsp/src/main/java/playground/vsp/flowEfficiency/LinkTurnDirectionAttributesFromGraphHopper.java index eaddca54ee5..dccd3716f6c 100644 --- a/contribs/vsp/src/main/java/playground/vsp/flowEfficiency/LinkTurnDirectionAttributesFromGraphHopper.java +++ b/contribs/vsp/src/main/java/playground/vsp/flowEfficiency/LinkTurnDirectionAttributesFromGraphHopper.java @@ -26,6 +26,7 @@ import com.graphhopper.ResponsePath; import com.graphhopper.config.CHProfile; import com.graphhopper.config.Profile; +import com.graphhopper.util.CustomModel; import com.graphhopper.util.Instruction; import com.graphhopper.util.InstructionList; import org.apache.logging.log4j.LogManager; @@ -162,7 +163,8 @@ private static GraphHopper createGraphHopperInstance(String ghLoc) { hopper.setGraphHopperLocation("target/routing-graph-cache"); // see docs/core/profiles.md to learn more about profiles - hopper.setProfiles(new Profile("car").setVehicle("car").setWeighting("fastest").setTurnCosts(false)); + //fixme: "Graphhopper profiles have changed" + //hopper.setProfiles(new Profile("car").setCustomModel(new CustomModel()).setWeighting("fastest").setTurnCosts(false)); // this enables speed mode for the profile we called car hopper.getCHPreparationHandler().setCHProfiles(new CHProfile("car")); diff --git a/matsim/src/main/java/ch/sbb/matsim/config/SwissRailRaptorConfigGroup.java b/matsim/src/main/java/ch/sbb/matsim/config/SwissRailRaptorConfigGroup.java index 3c8040a92fb..c5376acc84b 100644 --- a/matsim/src/main/java/ch/sbb/matsim/config/SwissRailRaptorConfigGroup.java +++ b/matsim/src/main/java/ch/sbb/matsim/config/SwissRailRaptorConfigGroup.java @@ -20,6 +20,9 @@ package ch.sbb.matsim.config; import com.google.common.base.Verify; + +import ch.sbb.matsim.routing.pt.raptor.RaptorStaticConfig.RaptorTransferCalculation; + import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; @@ -61,6 +64,8 @@ public class SwissRailRaptorConfigGroup extends ReflectiveConfigGroup { private static final String PARAM_TRANSFER_WALK_MARGIN_DESC = "time deducted from transfer walk leg during transfers between pt legs in order to avoid missing a vehicle by a few seconds due to delays."; private static final String PARAM_INTERMODAL_LEG_ONLYHANDLING = "intermodalLegOnlyHandling"; private static final String PARAM_INTERMODAL_LEG_ONLYHANDLING_DESC = "Define how routes containing only intermodal legs are handled: Useful options: alllow, avoid, forbid"; + private static final String PARAM_TRANSFER_CALCULATION = "transferCalculation"; + private static final String PARAM_TRANFER_CALCULATION_DESC = "Defines whether all potential transfers are precomputed at the beginning of the simulation (Initial) or whether they are constructed on-demand (Cached). The former incurs potentially long up-front caclulations, but quicker routing. The latter avoids any initial computation, but may require longer routing time."; private boolean useRangeQuery = false; private boolean useIntermodality = false; @@ -74,6 +79,7 @@ public class SwissRailRaptorConfigGroup extends ReflectiveConfigGroup { private double transferPenaltyHourlyCost = 0; private double transferWalkMargin = 5; private IntermodalLegOnlyHandling intermodalLegOnlyHandling = IntermodalLegOnlyHandling.forbid; + private RaptorTransferCalculation transferCalculation = RaptorTransferCalculation.Initial; private ScoringParameters scoringParameters = ScoringParameters.Default; @@ -128,9 +134,19 @@ public void setIntermodalLegOnlyHandling(IntermodalLegOnlyHandling intermodalLeg public String getIntermodalLegOnlyHandlingString() { return intermodalLegOnlyHandling.toString(); } - + public IntermodalLegOnlyHandling getIntermodalLegOnlyHandling() { return intermodalLegOnlyHandling; + } + + @StringSetter(PARAM_TRANSFER_CALCULATION) + public void setTransferCalculation(RaptorTransferCalculation transferCalculation) { + this.transferCalculation = transferCalculation; + } + + @StringGetter(PARAM_TRANSFER_CALCULATION) + public RaptorTransferCalculation getTransferCalculation() { + return transferCalculation; } @StringGetter(PARAM_USE_RANGE_QUERY) @@ -707,6 +723,7 @@ public Map getComments() { comments.put(PARAM_USE_CAPACITY_CONSTRAINTS, PARAM_USE_CAPACITY_CONSTRAINTS_DESC); comments.put(PARAM_TRANSFER_WALK_MARGIN, PARAM_TRANSFER_WALK_MARGIN_DESC); comments.put(PARAM_INTERMODAL_ACCESS_EGRESS_MODE_SELECTION,PARAM_INTERMODAL_ACCESS_EGRESS_MODE_SELECTION_DESC); + comments.put(PARAM_TRANSFER_CALCULATION, PARAM_TRANFER_CALCULATION_DESC); return comments; } diff --git a/matsim/src/main/java/ch/sbb/matsim/routing/pt/raptor/RaptorStaticConfig.java b/matsim/src/main/java/ch/sbb/matsim/routing/pt/raptor/RaptorStaticConfig.java index 20b94216349..8f49f121ab2 100644 --- a/matsim/src/main/java/ch/sbb/matsim/routing/pt/raptor/RaptorStaticConfig.java +++ b/matsim/src/main/java/ch/sbb/matsim/routing/pt/raptor/RaptorStaticConfig.java @@ -51,6 +51,22 @@ public enum RaptorOptimization { * (see {@link SwissRailRaptor#calcTree(TransitStopFacility, double, RaptorParameters, Person)} ). */ OneToAllRouting } + + public enum RaptorTransferCalculation { + /** + * Use this option if you want the algorithm to calculate all possible transfers + * up-front, which will allow for rapid lookup during routing, but may come with + * significant simulation startup time. + */ + Initial, + + /** + * Use this option if you want the algorithm to calculate transfers on demand, + * which avoids any simulation start-up time but may increase the routing time + * itself. + */ + Cached + } /** @@ -71,6 +87,7 @@ public enum RaptorOptimization { private boolean useCapacityConstraints = false; private RaptorOptimization optimization = RaptorOptimization.OneToOneRouting; + private RaptorTransferCalculation transferCalculation = RaptorTransferCalculation.Initial; private SwissRailRaptorConfigGroup.IntermodalLegOnlyHandling intermodalLegOnlyHandling = SwissRailRaptorConfigGroup.IntermodalLegOnlyHandling.forbid; @@ -167,4 +184,12 @@ public SwissRailRaptorConfigGroup.IntermodalLegOnlyHandling getIntermodalLegOnly public void setIntermodalLegOnlyHandling(SwissRailRaptorConfigGroup.IntermodalLegOnlyHandling intermodalLegOnlyHandling) { this.intermodalLegOnlyHandling = intermodalLegOnlyHandling; } + + public RaptorTransferCalculation getTransferCalculation() { + return this.transferCalculation; + } + + public void setTransferCalculation(RaptorTransferCalculation transferCalculation) { + this.transferCalculation = transferCalculation; + } } diff --git a/matsim/src/main/java/ch/sbb/matsim/routing/pt/raptor/RaptorUtils.java b/matsim/src/main/java/ch/sbb/matsim/routing/pt/raptor/RaptorUtils.java index f289b04e486..724af748454 100644 --- a/matsim/src/main/java/ch/sbb/matsim/routing/pt/raptor/RaptorUtils.java +++ b/matsim/src/main/java/ch/sbb/matsim/routing/pt/raptor/RaptorUtils.java @@ -63,6 +63,7 @@ public static RaptorStaticConfig createStaticConfig(Config config) { staticConfig.setTransferWalkMargin(srrConfig.getTransferWalkMargin()); staticConfig.setIntermodalLegOnlyHandling(srrConfig.getIntermodalLegOnlyHandling()); staticConfig.setMinimalTransferTime(config.transitRouter().getAdditionalTransferTime()); + staticConfig.setTransferCalculation(srrConfig.getTransferCalculation()); staticConfig.setUseModeMappingForPassengers(srrConfig.isUseModeMappingForPassengers()); if (srrConfig.isUseModeMappingForPassengers()) { diff --git a/matsim/src/main/java/ch/sbb/matsim/routing/pt/raptor/SwissRailRaptorCore.java b/matsim/src/main/java/ch/sbb/matsim/routing/pt/raptor/SwissRailRaptorCore.java index 0242f5e91ab..37855f385f0 100644 --- a/matsim/src/main/java/ch/sbb/matsim/routing/pt/raptor/SwissRailRaptorCore.java +++ b/matsim/src/main/java/ch/sbb/matsim/routing/pt/raptor/SwissRailRaptorCore.java @@ -752,10 +752,17 @@ private void handleTransfers(boolean strict, RaptorParameters raptorParams) { continue; } RRouteStop fromRouteStop = fromPE.toRouteStop; // this is the route stop we arrive with least cost at stop - int firstTransferIndex = fromRouteStop.indexFirstTransfer; - int lastTransferIndex = firstTransferIndex + fromRouteStop.countTransfers; + + // obtain on-demand transfers if applicable (will return null if transfers are calculated initially) + RTransfer[] transfers = this.data.calculateTransfers(fromRouteStop); + + int firstTransferIndex = transfers == null ? fromRouteStop.indexFirstTransfer : 0; + int lastTransferIndex = transfers == null ? firstTransferIndex + fromRouteStop.countTransfers : transfers.length; + transfers = transfers == null ? this.data.transfers : transfers; + for (int transferIndex = firstTransferIndex; transferIndex < lastTransferIndex; transferIndex++) { - RTransfer transfer = this.data.transfers[transferIndex]; + RTransfer transfer = transfers[transferIndex]; + int toRouteStopIndex = transfer.toRouteStop; transferProvider.reset(transfer); int newArrivalTime = arrivalTime + transfer.transferTime; diff --git a/matsim/src/main/java/ch/sbb/matsim/routing/pt/raptor/SwissRailRaptorData.java b/matsim/src/main/java/ch/sbb/matsim/routing/pt/raptor/SwissRailRaptorData.java index 5abe7fb49e0..4813fafd95d 100644 --- a/matsim/src/main/java/ch/sbb/matsim/routing/pt/raptor/SwissRailRaptorData.java +++ b/matsim/src/main/java/ch/sbb/matsim/routing/pt/raptor/SwissRailRaptorData.java @@ -20,10 +20,26 @@ package ch.sbb.matsim.routing.pt.raptor; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.Supplier; + +import javax.annotation.Nullable; + import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.matsim.api.core.v01.Coord; import org.matsim.api.core.v01.Id; +import org.matsim.api.core.v01.IdMap; import org.matsim.api.core.v01.TransportMode; import org.matsim.api.core.v01.network.Link; import org.matsim.api.core.v01.network.Network; @@ -41,18 +57,8 @@ import org.matsim.vehicles.Vehicle; import org.matsim.vehicles.Vehicles; -import javax.annotation.Nullable; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.function.Supplier; +import ch.sbb.matsim.routing.pt.raptor.RaptorStaticConfig.RaptorOptimization; +import ch.sbb.matsim.routing.pt.raptor.RaptorStaticConfig.RaptorTransferCalculation; /** * @author mrieser / SBB @@ -75,12 +81,16 @@ public class SwissRailRaptorData { final QuadTree stopsQT; final Map>> stopFilterAttribute2Value2StopsQT; final OccupancyData occupancyData; + + // data needed if cached transfer construction is activated + final IdMap> staticTransferTimes; + final RTransfer[][] transferCache; private SwissRailRaptorData(RaptorStaticConfig config, int countStops, RRoute[] routes, int[] departures, Vehicle[] departureVehicles, Id[] departureIds, RRouteStop[] routeStops, RTransfer[] transfers, Map stopFacilityIndices, Map routeStopsPerStopFacility, QuadTree stopsQT, - OccupancyData occupancyData) { + OccupancyData occupancyData, IdMap> staticTransferTimes) { this.config = config; this.countStops = countStops; this.countRouteStops = routeStops.length; @@ -95,6 +105,10 @@ private SwissRailRaptorData(RaptorStaticConfig config, int countStops, this.stopsQT = stopsQT; this.stopFilterAttribute2Value2StopsQT = new HashMap<>(); this.occupancyData = occupancyData; + + // data needed if cached transfer construction is activated + this.staticTransferTimes = staticTransferTimes; + this.transferCache = new RTransfer[routeStops.length][]; } public static SwissRailRaptorData create(TransitSchedule schedule, @Nullable Vehicles transitVehicles, RaptorStaticConfig staticConfig, Network network, OccupancyData occupancyData) { @@ -200,7 +214,16 @@ public static SwissRailRaptorData create(TransitSchedule schedule, @Nullable Veh QuadTree stopsQT = TransitScheduleUtils.createQuadTreeOfTransitStopFacilities(stops); int countStopFacilities = stops.size(); - Map allTransfers = calculateRouteStopTransfers(schedule, stopsQT, routeStopsPerStopFacility, routeStops, staticConfig); + // if cached transfer calculation is active, don't generate any transfers here + final Map allTransfers; + + if (staticConfig.getTransferCalculation().equals(RaptorTransferCalculation.Initial)) { + allTransfers = calculateRouteStopTransfers(schedule, stopsQT, routeStopsPerStopFacility, routeStops, + staticConfig); + } else { + allTransfers = Collections.emptyMap(); + } + long countTransfers = 0; for (RTransfer[] transfers : allTransfers.values()) { countTransfers += transfers.length; @@ -221,8 +244,22 @@ public static SwissRailRaptorData create(TransitSchedule schedule, @Nullable Veh indexTransfer += transferCount; } } + + // if cached transfer calculation is used, build a map for quick lookup of minimal transfer times + IdMap> staticTransferTimes = null; + if (staticConfig.getTransferCalculation().equals(RaptorTransferCalculation.Cached)) { + staticTransferTimes = new IdMap<>(TransitStopFacility.class); - SwissRailRaptorData data = new SwissRailRaptorData(staticConfig, countStopFacilities, routes, departures, departureVehicles, departureIds, routeStops, transfers, stopFacilityIndices, routeStopsPerStopFacility, stopsQT, occupancyData); + MinimalTransferTimes.MinimalTransferTimesIterator iterator = schedule.getMinimalTransferTimes().iterator(); + while (iterator.hasNext()) { + iterator.next(); + + staticTransferTimes.computeIfAbsent(iterator.getFromStopId(), id -> new HashMap<>()) + .put(schedule.getFacilities().get(iterator.getToStopId()), iterator.getSeconds()); + } + } + + SwissRailRaptorData data = new SwissRailRaptorData(staticConfig, countStopFacilities, routes, departures, departureVehicles, departureIds, routeStops, transfers, stopFacilityIndices, routeStopsPerStopFacility, stopsQT, occupancyData, staticTransferTimes); long endMillis = System.currentTimeMillis(); log.info("SwissRailRaptor data preparation done. Took " + (endMillis - startMillis) / 1000 + " seconds."); @@ -604,4 +641,77 @@ public Transfer get() { return this.transfer; } } + + RTransfer[] calculateTransfers(RRouteStop fromRouteStop) { + if (config.getTransferCalculation().equals(RaptorTransferCalculation.Initial)) { + return null; + } + + // We tested this in a parallel set-up and things seem to work as they are + // implemented. The routing threads will access the cache as read-only an + // retrieve the cached stop connections. It can happen that two of them try to + // obtain a non-existent entry at the same time. In that case, the calculation + // is performed twice, but this is not critical. Then this function writes the + // connections into the cache. This is a replacement of one address in the array + // from null to a concrete value, and our tests show that this seems to appear + // as atomic to the using threads. However, it is not 100% excluded that there + // is some parallelization issue here and that we rather should shield the cache + // using a lock in some way. But so far, we didn't experience any problem. /sh + // may 2024 + + RTransfer[] cache = transferCache[fromRouteStop.index]; + if (cache != null) return cache; // we had a cache hit + + // setting up useful constants + final double minimalTransferTime = config.getMinimalTransferTime(); + final double beelineWalkConnectionDistance = config.getBeelineWalkConnectionDistance(); + final double beelineDistanceFactor = config.getBeelineWalkDistanceFactor(); + final double beelineWalkSpeed = config.getBeelineWalkSpeed(); + final RaptorOptimization optimization = config.getOptimization(); + + // the facility from which we want to transfer + TransitStopFacility fromRouteFacility = fromRouteStop.routeStop.getStopFacility(); + Collection transferCandidates = new LinkedList<>(); + + // find transfer candidates by distance + transferCandidates.addAll(stopsQT.getDisk(fromRouteFacility.getCoord().getX(), fromRouteFacility.getCoord().getY(), config.getBeelineWalkConnectionDistance())); + + // find transfer candidates with predefined transfer time + Map transferTimes = staticTransferTimes.get(fromRouteFacility.getId()); + + if (transferTimes != null) { // some transfer times are predefined + transferCandidates.addAll(transferTimes.keySet()); + } + + // now evaluate whether transfers are useful, distance, and travel time + List transfers = new LinkedList<>(); + for (TransitStopFacility toRouteFacility : transferCandidates) { + for (int toRouteStopIndex : routeStopsPerStopFacility.get(toRouteFacility)) { + RRouteStop toRouteStop = routeStops[toRouteStopIndex]; + + double beelineDistance = CoordUtils.calcEuclideanDistance(fromRouteFacility.getCoord(), toRouteFacility.getCoord()); + double transferTime = beelineDistance / beelineWalkSpeed; + + if (transferTime < minimalTransferTime) { + transferTime = minimalTransferTime; + } + + if (transferTimes != null) { + // check if we find a predefined transfer time + transferTime = transferTimes.getOrDefault(toRouteFacility, transferTime); + } + + if (SwissRailRaptorData.isUsefulTransfer(fromRouteStop, toRouteStop, beelineWalkConnectionDistance, optimization)) { + transfers.add(new RTransfer(fromRouteStop.index, toRouteStop.index, transferTime, beelineDistance * beelineDistanceFactor)); + } + } + } + + // convert to array + RTransfer[] stopTransfers = transfers.toArray(new RTransfer[transfers.size()]); + + // save to cache (no issue regarding parallel execution because we simply set an element) + transferCache[fromRouteStop.index] = stopTransfers; + return stopTransfers; + } } diff --git a/matsim/src/main/java/org/matsim/pt/transitSchedule/MinimalTransferTimesImpl.java b/matsim/src/main/java/org/matsim/pt/transitSchedule/MinimalTransferTimesImpl.java index f8e5bf92d9f..c78b1b1687e 100644 --- a/matsim/src/main/java/org/matsim/pt/transitSchedule/MinimalTransferTimesImpl.java +++ b/matsim/src/main/java/org/matsim/pt/transitSchedule/MinimalTransferTimesImpl.java @@ -19,9 +19,11 @@ package org.matsim.pt.transitSchedule; +import java.util.Collections; import java.util.Iterator; import java.util.Map; import java.util.NoSuchElementException; +import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import org.matsim.api.core.v01.Id; @@ -164,4 +166,15 @@ public double getSeconds() { throw new NoSuchElementException(); } } + + @Override + public Set> getCandidates(Id fromStop) { + var inner = minimalTransferTimes.get(fromStop); + + if (inner == null) { + return Collections.emptySet(); + } + + return inner.keySet(); + } } diff --git a/matsim/src/main/java/org/matsim/pt/transitSchedule/api/MinimalTransferTimes.java b/matsim/src/main/java/org/matsim/pt/transitSchedule/api/MinimalTransferTimes.java index 72819f5cf70..c567093f865 100644 --- a/matsim/src/main/java/org/matsim/pt/transitSchedule/api/MinimalTransferTimes.java +++ b/matsim/src/main/java/org/matsim/pt/transitSchedule/api/MinimalTransferTimes.java @@ -19,6 +19,8 @@ package org.matsim.pt.transitSchedule.api; +import java.util.Set; + import org.matsim.api.core.v01.Id; /** @@ -66,6 +68,8 @@ public interface MinimalTransferTimes { * @return the previously set minimal transfer time, or Double.NaN if none was set. */ double remove(Id fromStop, Id toStop); + + Set> getCandidates(Id fromStop); /** * @return an iterator to iterate over all minimal transfer times set. diff --git a/matsim/src/main/java/org/matsim/vehicles/MatsimVehicleReader.java b/matsim/src/main/java/org/matsim/vehicles/MatsimVehicleReader.java index 069d90dba83..f22edb704b4 100644 --- a/matsim/src/main/java/org/matsim/vehicles/MatsimVehicleReader.java +++ b/matsim/src/main/java/org/matsim/vehicles/MatsimVehicleReader.java @@ -83,6 +83,11 @@ public void startTag(final String name, final Attributes atts, final Stack 2.23.1 - 31.0 + 31.1 0.49.2 1.19.0 7.0.0 - 2.16.2 + 2.17.1 2.5.0 5.10.2 @@ -103,7 +103,7 @@ com.google.guava guava - 33.2.0-jre + 33.2.1-jre org.apache.commons @@ -170,6 +170,11 @@ gt-epsg-extension ${geotools.version} + + org.geotools + gt-api + ${geotools.version} + org.geotools.jdbc gt-jdbc-postgis @@ -200,7 +205,7 @@ com.google.errorprone error_prone_annotations - 2.27.1 + 2.28.0 @@ -291,7 +296,7 @@ org.checkerframework checker-qual - 3.43.0 + 3.44.0 @@ -445,7 +450,7 @@ org.apache.maven.plugins maven-javadoc-plugin - 3.6.3 + 3.7.0 org.codehaus.mojo