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..9ea39e1a75f 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 @@ -43,6 +43,8 @@ public final class ShiftEfficiencyAnalysisControlerListener implements Iteration private final DrtConfigGroup drtConfigGroup; private final ShiftEfficiencyTracker shiftEfficiencyTracker; + private final String delimiter; + @Inject public ShiftEfficiencyAnalysisControlerListener(DrtConfigGroup drtConfigGroup, ShiftEfficiencyTracker shiftEfficiencyTracker, @@ -52,6 +54,7 @@ public ShiftEfficiencyAnalysisControlerListener(DrtConfigGroup drtConfigGroup, this.shiftEfficiencyTracker = shiftEfficiencyTracker; this.drtShiftsSpecification = drtShiftsSpecification; this.matsimServices = matsimServices; + this.delimiter = matsimServices.getConfig().global().getDefaultDelimiter(); } @Override @@ -131,7 +134,7 @@ private String filename(IterationEndsEvent event, String prefix, String extensio .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")); } } 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/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/parking/src/main/java/org/matsim/contrib/parking/parkingproxy/LinearPenaltyFunctionWithCap.java b/contribs/parking/src/main/java/org/matsim/contrib/parking/parkingproxy/LinearPenaltyFunctionWithCap.java index 810f7de6f35..af9b2fbb758 100644 --- a/contribs/parking/src/main/java/org/matsim/contrib/parking/parkingproxy/LinearPenaltyFunctionWithCap.java +++ b/contribs/parking/src/main/java/org/matsim/contrib/parking/parkingproxy/LinearPenaltyFunctionWithCap.java @@ -31,17 +31,15 @@ class LinearPenaltyFunctionWithCap implements PenaltyFunction { private final double penaltyPerCar; private final double maxPenalty; - private final double areaFactor; - public LinearPenaltyFunctionWithCap(double gridSize, double penaltyPerCar, double maxPenalty) { + public LinearPenaltyFunctionWithCap(double penaltyPerCar, double maxPenalty) { this.penaltyPerCar = penaltyPerCar; this.maxPenalty = maxPenalty; - this.areaFactor = gridSize * gridSize / 2500.; } @Override public double calculatePenalty(int numberOfCars) { - return Math.max(Math.min(numberOfCars * penaltyPerCar / areaFactor, maxPenalty), 0); + return Math.max(Math.min(numberOfCars * penaltyPerCar, maxPenalty), 0); } } diff --git a/contribs/parking/src/main/java/org/matsim/contrib/parking/parkingproxy/ParkingProxyModule.java b/contribs/parking/src/main/java/org/matsim/contrib/parking/parkingproxy/ParkingProxyModule.java index 8f83c20d126..f356f0911e8 100644 --- a/contribs/parking/src/main/java/org/matsim/contrib/parking/parkingproxy/ParkingProxyModule.java +++ b/contribs/parking/src/main/java/org/matsim/contrib/parking/parkingproxy/ParkingProxyModule.java @@ -44,6 +44,7 @@ */ public /*deliberately non-final*/ class ParkingProxyModule extends AbstractModule { + private final static int GRIDSIZE = 500; private final Scenario scenario; public ParkingProxyModule(Scenario scenario) { @@ -60,57 +61,38 @@ public void install() { initialLoad, parkingConfig.getTimeBinSize(), qsimEndTime, - parkingConfig.getGridSize() + GRIDSIZE ); - PenaltyFunction penaltyFunction = new LinearPenaltyFunctionWithCap(parkingConfig.getGridSize(), parkingConfig.getDelayPerCar(), parkingConfig.getMaxDelay()); + PenaltyFunction penaltyFunction = new LinearPenaltyFunctionWithCap(parkingConfig.getDelayPerCar(), parkingConfig.getMaxDelay()); //PenaltyFunction penaltyFunction = new ExponentialPenaltyFunctionWithCap(10, parkingConfig.getGridSize(), parkingConfig.getMaxDelay(), 360); - switch(parkingConfig.getCalculationMethod()) { - case none: + ParkingVehiclesCountEventHandler parkingHandler = new ParkingVehiclesCountEventHandler(carCounter, scenario.getNetwork(), parkingConfig.getScenarioScaleFactor()); + super.addEventHandlerBinding().toInstance(parkingHandler); + + CarEgressWalkObserver walkObserver; + switch (parkingConfig.getIter0Method()) { + case hourPenalty: + walkObserver = new CarEgressWalkObserver(parkingHandler, penaltyFunction, PenaltyCalculator.getDummyHourCalculator()); + break; + case noPenalty: + walkObserver = new CarEgressWalkObserver(parkingHandler, penaltyFunction, PenaltyCalculator.getDummyZeroCalculator()); break; - case events: - ParkingVehiclesCountEventHandler parkingHandler = new ParkingVehiclesCountEventHandler(carCounter, scenario.getNetwork(), parkingConfig.getScenarioScaleFactor()); - super.addEventHandlerBinding().toInstance(parkingHandler); - - CarEgressWalkObserver walkObserver; - switch (parkingConfig.getIter0Method()) { - case hourPenalty: - walkObserver = new CarEgressWalkObserver(parkingHandler, penaltyFunction, PenaltyCalculator.getDummyHourCalculator()); - break; - case noPenalty: - walkObserver = new CarEgressWalkObserver(parkingHandler, penaltyFunction, PenaltyCalculator.getDummyZeroCalculator()); - break; - case takeFromAttributes: - // CarEgressWalkChanger will handle this, we don't want to also change egress walks. Note that if it is observeOnly, the first iteration will put out zeros. - walkObserver = new CarEgressWalkObserver(parkingHandler, penaltyFunction, PenaltyCalculator.getDummyZeroCalculator()); - break; - case estimateFromPlans: - ParkingCounterByPlans plansCounter = new ParkingCounterByPlans(carCounter, parkingConfig.getScenarioScaleFactor()); - plansCounter.calculateByPopulation(scenario.getPopulation(), scenario.getNetwork()); - walkObserver = new CarEgressWalkObserver(parkingHandler, penaltyFunction, plansCounter.generatePenaltyCalculator()); - break; - default: - throw new RuntimeException("Unknown iter0 mode"); - } - if (parkingConfig.getObserveOnly()) { - super.addControlerListenerBinding().toInstance(walkObserver); - } else { - super.addControlerListenerBinding().toInstance(new CarEgressWalkChanger(parkingHandler, penaltyFunction, walkObserver, parkingConfig.getIter0Method())); - } + case takeFromAttributes: + // CarEgressWalkChanger will handle this, we don't want to also change egress walks. Note that if it is observeOnly, the first iteration will put out zeros. + walkObserver = new CarEgressWalkObserver(parkingHandler, penaltyFunction, PenaltyCalculator.getDummyZeroCalculator()); + break; + case estimateFromPlans: + ParkingCounterByPlans plansCounter = new ParkingCounterByPlans(carCounter, parkingConfig.getScenarioScaleFactor()); + plansCounter.calculateByPopulation(scenario.getPopulation(), scenario.getNetwork()); + walkObserver = new CarEgressWalkObserver(parkingHandler, penaltyFunction, plansCounter.generatePenaltyCalculator()); break; - case plans: - throw new RuntimeException("Mode \"plans\" is not working yet. Use \"events\" instead."); - /* - ParkingCounterByPlans planCounter = new ParkingCounterByPlans(carCounter, parkingConfig.getScenarioScaleFactor()); - super.addControlerListenerBinding().toInstance(planCounter); - if (parkingConfig.getObserveOnly()) { - super.addControlerListenerBinding().toInstance(new CarEgressWalkObserver(planCounter, penaltyFunction)); - } else { - super.addControlerListenerBinding().toInstance(new CarEgressWalkChanger(planCounter, penaltyFunction)); - } - break;*/ default: - throw new RuntimeException("Unsupported calculation method " + parkingConfig.getCalculationMethod()); + throw new RuntimeException("Unknown iter0 mode"); + } + if (parkingConfig.getObserveOnly()) { + super.addControlerListenerBinding().toInstance(walkObserver); + } else { + super.addControlerListenerBinding().toInstance(new CarEgressWalkChanger(parkingHandler, penaltyFunction, walkObserver, parkingConfig.getIter0Method())); } } diff --git a/contribs/parking/src/main/java/org/matsim/contrib/parking/parkingproxy/config/ParkingProxyConfigGroup.java b/contribs/parking/src/main/java/org/matsim/contrib/parking/parkingproxy/config/ParkingProxyConfigGroup.java index 060b072ecdc..0d0d0fec3af 100644 --- a/contribs/parking/src/main/java/org/matsim/contrib/parking/parkingproxy/config/ParkingProxyConfigGroup.java +++ b/contribs/parking/src/main/java/org/matsim/contrib/parking/parkingproxy/config/ParkingProxyConfigGroup.java @@ -24,28 +24,23 @@ public class ParkingProxyConfigGroup extends ReflectiveConfigGroup { - public static enum CalculationMethod {none, events, plans}; public static enum Iter0Method {noPenalty, hourPenalty, takeFromAttributes, estimateFromPlans} public static final String GROUP_NAME = "parkingProxy"; - public static final String METHOD = "method"; public static final String ITER0 = "iter0"; public static final String OBSERVE_ONLY = "observeOnly"; public static final String DELAY_PER_CAR = "delayPerCar"; public static final String MAX_DELAY = "maxDelay"; public static final String SCALE_FACTOR = "scenarioScaleFactor"; public static final String TIME_BIN_SIZE = "timeBinSize"; - public static final String GRID_SIZE = "gridSize"; public static final String CARS_PER_1000_PERSONS = "carsPer1000Persons"; - private CalculationMethod method = CalculationMethod.events; private Iter0Method iter0Method = Iter0Method.hourPenalty; private boolean observeOnly = false; private double delayPerCar = 2.5; private double maxDelay = 900; private int scenarioScaleFactor = 100; private int timeBinSize = 900; - private int gridSize = 500; private int carsPer1000Persons = 500; public ParkingProxyConfigGroup() { @@ -58,22 +53,12 @@ public Map getComments() { comments.put(SCALE_FACTOR, "The inverse of the scenario perentage, i.e. the number with which to multiply the" + " number of agents to get the real life population, e.g. 4 in a 25% scenario. Needs to be an Intger," + " so in case of weird percentages (e.g. 1/3) please round."); - comments.put(DELAY_PER_CAR, "in seconds. Note that this should be scaled MANUALLY with the gridsize!"); - comments.put(MAX_DELAY, "in seconds. Note that this should be scaled MANUALLY with the gridsize!"); + comments.put(DELAY_PER_CAR, "in seconds"); + comments.put(MAX_DELAY, "in seconds"); comments.put(TIME_BIN_SIZE, "in seconds"); - comments.put(GRID_SIZE, "in CRS units, usually meters"); return comments; } - @StringGetter(METHOD) - public CalculationMethod getCalculationMethod() { - return this.method; - } - @StringSetter(METHOD) - public void setCalculationMethod(CalculationMethod method) { - this.method = method; - } - @StringGetter(ITER0) public Iter0Method getIter0Method() { return this.iter0Method; @@ -110,15 +95,6 @@ public void setMaxDelay(double maxDelay) { this.maxDelay = maxDelay; } - @StringGetter(GRID_SIZE) - public int getGridSize() { - return gridSize; - } - @StringSetter(GRID_SIZE) - public void setGridSize(int gridSize) { - this.gridSize = gridSize; - } - @StringGetter(TIME_BIN_SIZE) public int getTimeBinSize() { return timeBinSize; 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.