From aa5dc1beffb02fc0c71e430a8910869c52087c2b Mon Sep 17 00:00:00 2001 From: Merlin Unterfinger Date: Fri, 13 Sep 2024 12:09:24 +0200 Subject: [PATCH 1/9] ENH: NAV-181 - Set initial size of HashSets of marked stops to avoid collisions. --- src/main/java/ch/naviqore/raptor/router/RouteScanner.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/ch/naviqore/raptor/router/RouteScanner.java b/src/main/java/ch/naviqore/raptor/router/RouteScanner.java index 38f175c0..15c8dcd8 100644 --- a/src/main/java/ch/naviqore/raptor/router/RouteScanner.java +++ b/src/main/java/ch/naviqore/raptor/router/RouteScanner.java @@ -114,7 +114,7 @@ Set scan(int round, Set markedStops) { log.debug("Scanning routes for round {} ({})", round, routesToScan); // scan selected routes and mark stops with improved times - Set markedStopsNext = new HashSet<>(); + Set markedStopsNext = new HashSet<>(stops.length); for (int currentRouteIdx : routesToScan) { scanRoute(currentRouteIdx, round, markedStops, markedStopsNext); } @@ -128,7 +128,7 @@ Set scan(int round, Set markedStops) { * @param markedStops the set of marked stops from the previous round. */ private Set getRoutesToScan(Set markedStops) { - Set routesToScan = new HashSet<>(); + Set routesToScan = new HashSet<>(stops.length); for (int stopIdx : markedStops) { Stop currentStop = stops[stopIdx]; int stopRouteIdx = currentStop.stopRouteIdx(); From fdab42a24d54d90884e4ec17a8879f1b231c0666 Mon Sep 17 00:00:00 2001 From: Merlin Unterfinger Date: Fri, 13 Sep 2024 13:26:37 +0200 Subject: [PATCH 2/9] ENH: NAV-181 - Benchmark on complete day - Use multi-day capability of router. --- src/test/java/ch/naviqore/Benchmark.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/ch/naviqore/Benchmark.java b/src/test/java/ch/naviqore/Benchmark.java index 97bb6b4a..2f414ae5 100644 --- a/src/test/java/ch/naviqore/Benchmark.java +++ b/src/test/java/ch/naviqore/Benchmark.java @@ -52,7 +52,7 @@ final class Benchmark { * Limit in seconds after midnight for the departure time. Only allow early departure times, otherwise many * connections crossing the complete schedule (region) are not feasible. */ - private static final int DEPARTURE_TIME_LIMIT = 8 * 60 * 60; + private static final int DEPARTURE_TIME_LIMIT = 24 * 60 * 60; private static final long RANDOM_SEED = 1234; private static final int SAMPLE_SIZE = 10_000; From a6433e20d5f3fddf7fd3482b32bc50c7e664de7e Mon Sep 17 00:00:00 2001 From: Lukas Connolly Date: Sun, 15 Sep 2024 20:30:48 +0200 Subject: [PATCH 3/9] ENH: NAV-181 - Use boolean array masks for routes to scan and marked stops instead of sets. --- .../raptor/router/FootpathRelaxer.java | 48 ++++---- .../java/ch/naviqore/raptor/router/Query.java | 104 ++++++++++++------ .../naviqore/raptor/router/RouteScanner.java | 84 +++++++------- 3 files changed, 130 insertions(+), 106 deletions(-) diff --git a/src/main/java/ch/naviqore/raptor/router/FootpathRelaxer.java b/src/main/java/ch/naviqore/raptor/router/FootpathRelaxer.java index c0815bd6..9cdc0abb 100644 --- a/src/main/java/ch/naviqore/raptor/router/FootpathRelaxer.java +++ b/src/main/java/ch/naviqore/raptor/router/FootpathRelaxer.java @@ -3,10 +3,6 @@ import ch.naviqore.raptor.TimeType; import lombok.extern.slf4j.Slf4j; -import java.util.Collection; -import java.util.HashSet; -import java.util.Set; - import static ch.naviqore.raptor.router.QueryState.NO_INDEX; @Slf4j @@ -46,36 +42,30 @@ class FootpathRelaxer { /** * Relax all footpaths from all initial source stops. * - * @param stopIndices the indices of the stops to be relaxed. - * @return returns the newly marked stops due to the relaxation. + * @param markedStopsMask the mask of the stops to be relaxed. */ - Set relaxInitial(int[] stopIndices) { + void relaxInitial(boolean[] markedStopsMask) { log.debug("Initial relaxing of footpaths for source stops"); - Set newlyMarkedStops = new HashSet<>(); - - for (int sourceStopIdx : stopIndices) { - expandFootpathsFromStop(sourceStopIdx, 0, newlyMarkedStops); - } - - return newlyMarkedStops; + relax(0, markedStopsMask); } /** * Relax all footpaths from marked stops. * - * @param round the current round. - * @param stopIndices the indices of the stops to be relaxed. - * @return returns the newly marked stops due to the relaxation. + * @param round the current round. + * @param markedStopsMask the mask of the stops to be relaxed. */ - Set relax(int round, Collection stopIndices) { + void relax(int round, boolean[] markedStopsMask) { log.debug("Relaxing footpaths for round {}", round); - Set newlyMarkedStops = new HashSet<>(); + // to prevent extending transfers from stops that were only reached by footpath in the same round + boolean[] routeMarkedStops = markedStopsMask.clone(); - for (int sourceStopIdx : stopIndices) { - expandFootpathsFromStop(sourceStopIdx, round, newlyMarkedStops); + for (int sourceStopIdx = 0; sourceStopIdx < markedStopsMask.length; sourceStopIdx++) { + if (!routeMarkedStops[sourceStopIdx]) { + continue; + } + expandFootpathsFromStop(sourceStopIdx, round, markedStopsMask); } - - return newlyMarkedStops; } /** @@ -83,12 +73,12 @@ Set relax(int round, Collection stopIndices) { * then the target stop is marked for the next round. And the improved target time is stored in the bestTimes array * and the bestLabelPerRound list (including the new transfer label). * - * @param stopIdx the index of the stop to expand transfers from. - * @param round the current round to relax footpaths for. - * @param markedStops a set of stop indices that have been marked for scanning in the next round, which will be - * extended if new stops improve due to relaxation. + * @param stopIdx the index of the stop to expand transfers from. + * @param round the current round to relax footpaths for. + * @param markedStopsMask a mask of stop indices that have been marked for scanning in the next round, which will be + * extended if new stops improve due to relaxation. */ - private void expandFootpathsFromStop(int stopIdx, int round, Set markedStops) { + private void expandFootpathsFromStop(int stopIdx, int round, boolean[] markedStopsMask) { // if stop has no transfers, then no footpaths can be expanded if (stops[stopIdx].numberOfTransfers() == 0) { return; @@ -134,7 +124,7 @@ private void expandFootpathsFromStop(int stopIdx, int round, Set marked NO_INDEX, transfer.targetStopIdx(), queryState.getLabel(round, stopIdx)); queryState.setLabel(round, transfer.targetStopIdx(), label); // mark stop as improved - markedStops.add(transfer.targetStopIdx()); + markedStopsMask[transfer.targetStopIdx()] = true; } } } diff --git a/src/main/java/ch/naviqore/raptor/router/Query.java b/src/main/java/ch/naviqore/raptor/router/Query.java index 7fafceca..614e03ac 100644 --- a/src/main/java/ch/naviqore/raptor/router/Query.java +++ b/src/main/java/ch/naviqore/raptor/router/Query.java @@ -5,7 +5,10 @@ import lombok.extern.slf4j.Slf4j; import java.time.LocalDateTime; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; import static ch.naviqore.raptor.router.QueryState.INFINITY; @@ -30,6 +33,8 @@ class Query { private final FootpathRelaxer footpathRelaxer; private final RouteScanner routeScanner; + private final int numStops; + private final int raptorRange; /** @@ -65,6 +70,7 @@ class Query { targetStops = new int[targetStopIndices.length * 2]; cutoffTime = determineCutoffTime(); + numStops = raptorData.getStopContext().stops().length; queryState = new QueryState(raptorData.getStopContext().stops().length, timeType); // set up footpath relaxer and route scanner and inject stop labels and times @@ -93,27 +99,36 @@ class Query { List run() { // initially relax all source stops and add the newly improved stops by relaxation to the marked stops - Set markedStops = initialize(); - markedStops.addAll(footpathRelaxer.relaxInitial(sourceStopIndices)); - markedStops = removeSuboptimalLabelsForRound(0, markedStops); + boolean[] markedStopsMask = initialize(); + footpathRelaxer.relaxInitial(markedStopsMask); + removeSuboptimalLabelsForRound(0, markedStopsMask); // if range is 0 or smaller there is no range, and we don't need to rerun rounds with different start offsets if (raptorRange <= 0) { - doRounds(markedStops); + doRounds(markedStopsMask); } else { - doRangeRaptor(markedStops); + doRangeRaptor(markedStopsMask); } return queryState.getBestLabelsPerRound(); } - void doRangeRaptor(Set markedStops) { + void doRangeRaptor(boolean[] markedStops) { // prepare range offsets - List rangeOffsets = getRangeOffsets(markedStops, routeScanner); + // get initial marked stops to reset after each range offset + List initialMarkedStops = new ArrayList<>(); + for (int stopIdx = 0; stopIdx < markedStops.length; stopIdx++) { + if (markedStops[stopIdx]) { + initialMarkedStops.add(stopIdx); + } + } + List rangeOffsets = getRangeOffsets(initialMarkedStops, routeScanner); HashMap stopIdxSourceTimes = new HashMap<>(); - for (int stopIdx : markedStops) { + for (int stopIdx = 0; stopIdx < markedStops.length; stopIdx++) { + if (!markedStops[stopIdx]) { + continue; + } stopIdxSourceTimes.put(stopIdx, queryState.getLabel(0, stopIdx).targetTime()); } - // scan all range offsets in reverse order (earliest arrival / latest departure first) for (int offsetIdx = rangeOffsets.size() - 1; offsetIdx >= 0; offsetIdx--) { int rangeOffset = rangeOffsets.get(offsetIdx); @@ -121,7 +136,7 @@ void doRangeRaptor(Set markedStops) { log.debug("Running rounds with range offset {}", rangeOffset); // set source times to the source times of the previous round - for (int stopIdx : markedStops) { + for (int stopIdx : initialMarkedStops) { QueryState.Label label = queryState.getLabel(0, stopIdx); int targetTime = stopIdxSourceTimes.get(stopIdx) + timeFactor * rangeOffset; queryState.setLabel(0, stopIdx, copyLabelWithNewTargetTime(label, targetTime)); @@ -147,28 +162,46 @@ QueryState.Label copyLabelWithNewTargetTime(QueryState.Label label, int targetTi /** * Method to perform the rounds of the routing algorithm (see {@link #run()}). * - * @param markedStops the initially marked stops. + * @param markedStopsMask the initially marked stops mask. */ - private void doRounds(Set markedStops) { + private void doRounds(boolean[] markedStopsMask) { // continue with further rounds as long as there are new marked stops int round = 1; - while (!markedStops.isEmpty() && (round - 1) <= config.getMaximumTransferNumber()) { + + // check if marked stops has any true values + while (hasMarkedStops(markedStopsMask) && (round - 1) <= config.getMaximumTransferNumber()) { // add label layer for new round queryState.addNewRound(); // scan all routs and mark stops that have improved - Set markedStopsNext = routeScanner.scan(round, markedStops); + boolean[] markedStopsNext = routeScanner.scan(round, markedStopsMask); // relax footpaths for all newly marked stops - markedStopsNext.addAll(footpathRelaxer.relax(round, markedStopsNext)); + footpathRelaxer.relax(round, markedStopsNext); // prepare next round - markedStops = removeSuboptimalLabelsForRound(round, markedStopsNext); + removeSuboptimalLabelsForRound(round, markedStopsNext); + markedStopsMask = markedStopsNext; round++; } } + /** + * Check if there are any marked stops in the marked stops mask. + * + * @param markedStopsMask the marked stops mask to check. + * @return true if there are any marked stops, false otherwise. + */ + private static boolean hasMarkedStops(boolean[] markedStopsMask) { + for (boolean b : markedStopsMask) { + if (b) { + return true; + } + } + return false; + } + /** * Get the range offsets for the marked stops. *

@@ -180,13 +213,13 @@ private void doRounds(Set markedStops) { * 10:10, 10:20, and Route B has departures at 10:05, 10:15, 10:25, the range offsets are be 0, 10, 20 and not 0, 5, * 10, 15, 20, 25 (note real values are in seconds and not minutes --> *60). * - * @param markedStops the marked stops to get the range offsets for. - * @param routeScanner the route scanner to get the trip offsets for the stops. + * @param initialMarkedStops the initial marked stops to get the range offsets for. + * @param routeScanner the route scanner to get the trip offsets for the stops. * @return the range offsets (in seconds) applicable for all marked stops. */ - private List getRangeOffsets(Set markedStops, RouteScanner routeScanner) { + private List getRangeOffsets(List initialMarkedStops, RouteScanner routeScanner) { ArrayList rangeOffsets = new ArrayList<>(); - for (int stopIdx : markedStops) { + for (int stopIdx : initialMarkedStops) { List stopRangeOffsets = routeScanner.getTripOffsetsForStop(stopIdx, raptorRange); for (int i = 0; i < stopRangeOffsets.size(); i++) { // if the rangeOffsets list is not long enough, add the offset @@ -213,7 +246,8 @@ private List getRangeOffsets(Set markedStops, RouteScanner rou * * @return the initially marked stops. */ - Set initialize() { + boolean[] initialize() { + boolean[] markedStopsMask = new boolean[numStops]; log.debug("Initializing global best times per stop and best labels per round"); // fill target stops @@ -224,7 +258,6 @@ Set initialize() { } // set initial labels, best time and mark source stops - Set markedStops = new HashSet<>(); for (int i = 0; i < sourceStopIndices.length; i++) { int currentStopIdx = sourceStopIndices[i]; int targetTime = sourceTimes[i]; @@ -233,42 +266,41 @@ Set initialize() { QueryState.NO_INDEX, QueryState.NO_INDEX, currentStopIdx, null); queryState.setLabel(0, currentStopIdx, label); queryState.setBestTime(currentStopIdx, targetTime); - - markedStops.add(currentStopIdx); + markedStopsMask[currentStopIdx] = true; } - return markedStops; + return markedStopsMask; } /** * Nullify labels that are suboptimal for the current round. This method checks if the label time is worse than the * optimal time mark and removes the mark for the next round and nullifies the label in this case. * - * @param round the round to remove suboptimal labels for. - * @param markedStops the marked stops to check for suboptimal labels. + * @param round the round to remove suboptimal labels for. + * @param markedStopsMask the marked stops mask to check for suboptimal labels. */ - Set removeSuboptimalLabelsForRound(int round, Set markedStops) { + void removeSuboptimalLabelsForRound(int round, boolean[] markedStopsMask) { int bestTime = getBestTimeForAllTargetStops(); if (bestTime == INFINITY || bestTime == -INFINITY) { - return markedStops; + return; } - Set markedStopsClean = new HashSet<>(); - for (int stopIdx : markedStops) { + for (int stopIdx = 0; stopIdx < markedStopsMask.length; stopIdx++) { + if (!markedStopsMask[stopIdx]) { + continue; + } QueryState.Label label = queryState.getLabel(round, stopIdx); if (label != null) { if (timeType == TimeType.DEPARTURE && label.targetTime() > bestTime) { queryState.setLabel(round, stopIdx, null); + markedStopsMask[stopIdx] = false; } else if (timeType == TimeType.ARRIVAL && label.targetTime() < bestTime) { queryState.setLabel(round, stopIdx, null); - } else { - markedStopsClean.add(stopIdx); + markedStopsMask[stopIdx] = false; } } } - - return markedStopsClean; } /** diff --git a/src/main/java/ch/naviqore/raptor/router/RouteScanner.java b/src/main/java/ch/naviqore/raptor/router/RouteScanner.java index 15c8dcd8..afe9e5b1 100644 --- a/src/main/java/ch/naviqore/raptor/router/RouteScanner.java +++ b/src/main/java/ch/naviqore/raptor/router/RouteScanner.java @@ -9,9 +9,7 @@ import java.time.LocalDate; import java.time.LocalDateTime; import java.util.ArrayList; -import java.util.HashSet; import java.util.List; -import java.util.Set; import static ch.naviqore.raptor.router.QueryState.INFINITY; @@ -105,51 +103,55 @@ class RouteScanner { /** * Scans all routes passing marked stops for the given round. * - * @param round the current round. - * @param markedStops the marked stops for this round. - * @return the marked stops for the next round. + * @param round the current round. + * @param markedStopsMask the marked stops mask for this round. + * @return the marked stops mask for the next round. */ - Set scan(int round, Set markedStops) { - Set routesToScan = getRoutesToScan(markedStops); + boolean[] scan(int round, boolean[] markedStopsMask) { + boolean[] routesToScan = getRoutesToScan(markedStopsMask); log.debug("Scanning routes for round {} ({})", round, routesToScan); // scan selected routes and mark stops with improved times - Set markedStopsNext = new HashSet<>(stops.length); - for (int currentRouteIdx : routesToScan) { - scanRoute(currentRouteIdx, round, markedStops, markedStopsNext); + boolean[] markedStopsMaskNext = new boolean[stops.length]; + for (int currentRouteIdx = 0; currentRouteIdx < routesToScan.length; currentRouteIdx++) { + scanRoute(currentRouteIdx, round, markedStopsMask, markedStopsMaskNext); } - return markedStopsNext; + return markedStopsMaskNext; } /** * Get all routes to scan from the marked stops. * - * @param markedStops the set of marked stops from the previous round. + * @param markedStopsMask the mask of marked stops from the previous round. + * @return the mask of routes to scan. */ - private Set getRoutesToScan(Set markedStops) { - Set routesToScan = new HashSet<>(stops.length); - for (int stopIdx : markedStops) { + private boolean[] getRoutesToScan(boolean[] markedStopsMask) { + boolean[] routesToScanMask = new boolean[routes.length]; + for (int stopIdx = 0; stopIdx < markedStopsMask.length; stopIdx++) { + if (!markedStopsMask[stopIdx]) { + continue; + } Stop currentStop = stops[stopIdx]; int stopRouteIdx = currentStop.stopRouteIdx(); int stopRouteEndIdx = stopRouteIdx + currentStop.numberOfRoutes(); while (stopRouteIdx < stopRouteEndIdx) { - routesToScan.add(stopRoutes[stopRouteIdx]); + routesToScanMask[stopRoutes[stopRouteIdx]] = true; stopRouteIdx++; } } - return routesToScan; + return routesToScanMask; } /** * Scan a route in time type applicable direction to find the best times for each stop on route for given round. * - * @param currentRouteIdx the index of the current route. - * @param round the current round. - * @param markedStops the set of marked stops from the previous round. - * @param markedStopsNext the set of marked stops for the next round. + * @param currentRouteIdx the index of the current route. + * @param round the current round. + * @param markedStopsMask the mask of marked stops from the previous round. + * @param markedStopsMaskNext the mask of marked stops for the next round. */ - private void scanRoute(int currentRouteIdx, int round, Set markedStops, Set markedStopsNext) { + private void scanRoute(int currentRouteIdx, int round, boolean[] markedStopsMask, boolean[] markedStopsMaskNext) { final int lastRound = round - 1; @@ -177,7 +179,7 @@ private void scanRoute(int currentRouteIdx, int round, Set markedStops, int bestStopTime = queryState.getComparableBestTime(stopIdx); // find first marked stop in route if (activeTrip == null) { - if (!canEnterAtStop(stop, bestStopTime, markedStops, stopIdx, stopOffset, currentRoute)) { + if (!canEnterAtStop(stop, bestStopTime, markedStopsMask, stopIdx, stopOffset, currentRoute)) { continue; } } else { @@ -187,7 +189,7 @@ private void scanRoute(int currentRouteIdx, int round, Set markedStops, int targetTime = rawStopTimes[(timeType == TimeType.DEPARTURE) ? stopTimeIndex : stopTimeIndex + 1]; targetTime += activeTrip.dayTimeOffset; if (!checkIfTripIsPossibleAndUpdateMarks(targetTime, activeTrip, stop, bestStopTime, stopIdx, round, - lastRound, markedStopsNext, currentRouteIdx)) { + lastRound, markedStopsMaskNext, currentRouteIdx)) { continue; } } @@ -212,14 +214,14 @@ private boolean isRouteActiveInDaysToScan(Route route) { * performance reasons) assuming that this check is only run when not travelling with an active trip, the stop was * not marked in a previous round (i.e., the lasts round trip query would be repeated). * - * @param stop the stop to check if a trip can be entered. - * @param stopTime the time at the stop. - * @param markedStops the set of marked stops from the previous round. - * @param stopIdx the index of the stop to check if a trip can be entered. - * @param stopOffset the offset of the stop in the route. - * @param currentRoute the current route. + * @param stop the stop to check if a trip can be entered. + * @param stopTime the time at the stop. + * @param markedStopsMask the mask of marked stops from the previous round. + * @param stopIdx the index of the stop to check if a trip can be entered. + * @param stopOffset the offset of the stop in the route. + * @param currentRoute the current route. */ - private boolean canEnterAtStop(Stop stop, int stopTime, Set markedStops, int stopIdx, int stopOffset, + private boolean canEnterAtStop(Stop stop, int stopTime, boolean[] markedStopsMask, int stopIdx, int stopOffset, Route currentRoute) { int unreachableValue = timeType == TimeType.DEPARTURE ? INFINITY : -INFINITY; @@ -237,7 +239,7 @@ private boolean canEnterAtStop(Stop stop, int stopTime, Set markedStops return false; } - if (!markedStops.contains(stopIdx)) { + if (!markedStopsMask[stopIdx]) { // this stop has already been scanned in previous round without improved target time log.debug("Stop {} was not improved in previous round, continue", stop.id()); return false; @@ -291,18 +293,18 @@ private int getFurthestTripTimeOfRoute(Route route, TimeType timeType) { *

If the time was not improved, an additional check will be needed to figure out if an earlier or later trip * from the stop is possible within the current round, thus the method returns true.

* - * @param targetTime the stop time to check for an earlier or later trip. - * @param activeTrip the active trip to check for an earlier or later trip. - * @param stop the stop to check for an earlier or later trip. - * @param bestStopTime the earliest or latest time at the stop based on the TimeType. - * @param stopIdx the index of the stop to check for an earlier or later trip. - * @param markedStopsNext the set of marked stops for the next round. - * @param currentRouteIdx the index of the current route. + * @param targetTime the stop time to check for an earlier or later trip. + * @param activeTrip the active trip to check for an earlier or later trip. + * @param stop the stop to check for an earlier or later trip. + * @param bestStopTime the earliest or latest time at the stop based on the TimeType. + * @param stopIdx the index of the stop to check for an earlier or later trip. + * @param markedStopsMaskNext the mask of marked stops for the next round. + * @param currentRouteIdx the index of the current route. * @return true if an earlier or later trip is possible, false otherwise. */ private boolean checkIfTripIsPossibleAndUpdateMarks(int targetTime, ActiveTrip activeTrip, Stop stop, int bestStopTime, int stopIdx, int thisRound, int lastRound, - Set markedStopsNext, int currentRouteIdx) { + boolean[] markedStopsMaskNext, int currentRouteIdx) { boolean isImproved = (timeType == TimeType.DEPARTURE) ? targetTime < bestStopTime : targetTime > bestStopTime; @@ -313,7 +315,7 @@ private boolean checkIfTripIsPossibleAndUpdateMarks(int targetTime, ActiveTrip a QueryState.Label label = new QueryState.Label(activeTrip.entryTime, targetTime, QueryState.LabelType.ROUTE, currentRouteIdx, activeTrip.tripOffset, stopIdx, activeTrip.previousLabel); queryState.setLabel(thisRound, stopIdx, label); - markedStopsNext.add(stopIdx); + markedStopsMaskNext[stopIdx] = true; return false; } else { From 6843994c8381a1027ed583d07b6f85b7c5821296 Mon Sep 17 00:00:00 2001 From: Lukas Connolly Date: Sun, 15 Sep 2024 21:14:31 +0200 Subject: [PATCH 4/9] FIX: NAV-181 - Add missing check if route should be scanned. --- src/main/java/ch/naviqore/raptor/router/RouteScanner.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/java/ch/naviqore/raptor/router/RouteScanner.java b/src/main/java/ch/naviqore/raptor/router/RouteScanner.java index afe9e5b1..b33de078 100644 --- a/src/main/java/ch/naviqore/raptor/router/RouteScanner.java +++ b/src/main/java/ch/naviqore/raptor/router/RouteScanner.java @@ -114,6 +114,9 @@ boolean[] scan(int round, boolean[] markedStopsMask) { // scan selected routes and mark stops with improved times boolean[] markedStopsMaskNext = new boolean[stops.length]; for (int currentRouteIdx = 0; currentRouteIdx < routesToScan.length; currentRouteIdx++) { + if (!routesToScan[currentRouteIdx]) { + continue; + } scanRoute(currentRouteIdx, round, markedStopsMask, markedStopsMaskNext); } From 67fb1558d38dba20a8aa3a8b94644a2d9f2224d2 Mon Sep 17 00:00:00 2001 From: Lukas Connolly Date: Mon, 16 Sep 2024 14:17:17 +0200 Subject: [PATCH 5/9] STYLE: NAV-181 - Format project --- .../java/ch/naviqore/raptor/router/Query.java | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/main/java/ch/naviqore/raptor/router/Query.java b/src/main/java/ch/naviqore/raptor/router/Query.java index 614e03ac..01d6d761 100644 --- a/src/main/java/ch/naviqore/raptor/router/Query.java +++ b/src/main/java/ch/naviqore/raptor/router/Query.java @@ -80,6 +80,21 @@ class Query { raptorConfig.getDaysToScan()); } + /** + * Check if there are any marked stops in the marked stops mask. + * + * @param markedStopsMask the marked stops mask to check. + * @return true if there are any marked stops, false otherwise. + */ + private static boolean hasMarkedStops(boolean[] markedStopsMask) { + for (boolean b : markedStopsMask) { + if (b) { + return true; + } + } + return false; + } + /** * Main control flow of the routing algorithm. Spawns from source stops, coordinates route scanning, footpath * relaxation, and time/label updates in the correct order. @@ -187,21 +202,6 @@ private void doRounds(boolean[] markedStopsMask) { } } - /** - * Check if there are any marked stops in the marked stops mask. - * - * @param markedStopsMask the marked stops mask to check. - * @return true if there are any marked stops, false otherwise. - */ - private static boolean hasMarkedStops(boolean[] markedStopsMask) { - for (boolean b : markedStopsMask) { - if (b) { - return true; - } - } - return false; - } - /** * Get the range offsets for the marked stops. *

From 1480946bb04ebe62d476ff3718dd71be673244ac Mon Sep 17 00:00:00 2001 From: Merlin Unterfinger Date: Mon, 16 Sep 2024 18:00:57 +0200 Subject: [PATCH 6/9] STYLE: NAV-181 - Unify naming, always assign mask postfix to marked stop arrays --- .../java/ch/naviqore/raptor/router/Query.java | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/main/java/ch/naviqore/raptor/router/Query.java b/src/main/java/ch/naviqore/raptor/router/Query.java index 01d6d761..be68ff97 100644 --- a/src/main/java/ch/naviqore/raptor/router/Query.java +++ b/src/main/java/ch/naviqore/raptor/router/Query.java @@ -127,19 +127,19 @@ List run() { return queryState.getBestLabelsPerRound(); } - void doRangeRaptor(boolean[] markedStops) { + void doRangeRaptor(boolean[] markedStopsMask) { // prepare range offsets // get initial marked stops to reset after each range offset List initialMarkedStops = new ArrayList<>(); - for (int stopIdx = 0; stopIdx < markedStops.length; stopIdx++) { - if (markedStops[stopIdx]) { + for (int stopIdx = 0; stopIdx < markedStopsMask.length; stopIdx++) { + if (markedStopsMask[stopIdx]) { initialMarkedStops.add(stopIdx); } } List rangeOffsets = getRangeOffsets(initialMarkedStops, routeScanner); HashMap stopIdxSourceTimes = new HashMap<>(); - for (int stopIdx = 0; stopIdx < markedStops.length; stopIdx++) { - if (!markedStops[stopIdx]) { + for (int stopIdx = 0; stopIdx < markedStopsMask.length; stopIdx++) { + if (!markedStopsMask[stopIdx]) { continue; } stopIdxSourceTimes.put(stopIdx, queryState.getLabel(0, stopIdx).targetTime()); @@ -156,7 +156,7 @@ void doRangeRaptor(boolean[] markedStops) { int targetTime = stopIdxSourceTimes.get(stopIdx) + timeFactor * rangeOffset; queryState.setLabel(0, stopIdx, copyLabelWithNewTargetTime(label, targetTime)); } - doRounds(markedStops); + doRounds(markedStopsMask); } } @@ -190,14 +190,14 @@ private void doRounds(boolean[] markedStopsMask) { queryState.addNewRound(); // scan all routs and mark stops that have improved - boolean[] markedStopsNext = routeScanner.scan(round, markedStopsMask); + boolean[] nextMarkedStopsMask = routeScanner.scan(round, markedStopsMask); // relax footpaths for all newly marked stops - footpathRelaxer.relax(round, markedStopsNext); + footpathRelaxer.relax(round, nextMarkedStopsMask); // prepare next round - removeSuboptimalLabelsForRound(round, markedStopsNext); - markedStopsMask = markedStopsNext; + removeSuboptimalLabelsForRound(round, nextMarkedStopsMask); + markedStopsMask = nextMarkedStopsMask; round++; } } From ddee426416135b2988e7221e04f75192a1fc331a Mon Sep 17 00:00:00 2001 From: Lukas Connolly Date: Mon, 16 Sep 2024 22:26:25 +0200 Subject: [PATCH 7/9] REFACTOR: NAV-181 - Move markedStopsMask logic to QueryState. --- .../raptor/router/FootpathRelaxer.java | 28 +++---- .../java/ch/naviqore/raptor/router/Query.java | 70 ++++++---------- .../ch/naviqore/raptor/router/QueryState.java | 65 ++++++++++++++- .../naviqore/raptor/router/RouteScanner.java | 83 +++++++++---------- 4 files changed, 136 insertions(+), 110 deletions(-) diff --git a/src/main/java/ch/naviqore/raptor/router/FootpathRelaxer.java b/src/main/java/ch/naviqore/raptor/router/FootpathRelaxer.java index 9cdc0abb..e2e9a000 100644 --- a/src/main/java/ch/naviqore/raptor/router/FootpathRelaxer.java +++ b/src/main/java/ch/naviqore/raptor/router/FootpathRelaxer.java @@ -41,30 +41,27 @@ class FootpathRelaxer { /** * Relax all footpaths from all initial source stops. - * - * @param markedStopsMask the mask of the stops to be relaxed. */ - void relaxInitial(boolean[] markedStopsMask) { + void relaxInitial() { log.debug("Initial relaxing of footpaths for source stops"); - relax(0, markedStopsMask); + relax(0); } /** * Relax all footpaths from marked stops. * - * @param round the current round. - * @param markedStopsMask the mask of the stops to be relaxed. + * @param round the current round. */ - void relax(int round, boolean[] markedStopsMask) { + void relax(int round) { log.debug("Relaxing footpaths for round {}", round); // to prevent extending transfers from stops that were only reached by footpath in the same round - boolean[] routeMarkedStops = markedStopsMask.clone(); + boolean[] routeMarkedStops = queryState.getMarkedStopsMaskNextRoundClone(); - for (int sourceStopIdx = 0; sourceStopIdx < markedStopsMask.length; sourceStopIdx++) { + for (int sourceStopIdx = 0; sourceStopIdx < routeMarkedStops.length; sourceStopIdx++) { if (!routeMarkedStops[sourceStopIdx]) { continue; } - expandFootpathsFromStop(sourceStopIdx, round, markedStopsMask); + expandFootpathsFromStop(sourceStopIdx, round); } } @@ -73,12 +70,10 @@ void relax(int round, boolean[] markedStopsMask) { * then the target stop is marked for the next round. And the improved target time is stored in the bestTimes array * and the bestLabelPerRound list (including the new transfer label). * - * @param stopIdx the index of the stop to expand transfers from. - * @param round the current round to relax footpaths for. - * @param markedStopsMask a mask of stop indices that have been marked for scanning in the next round, which will be - * extended if new stops improve due to relaxation. + * @param stopIdx the index of the stop to expand transfers from. + * @param round the current round to relax footpaths for. */ - private void expandFootpathsFromStop(int stopIdx, int round, boolean[] markedStopsMask) { + private void expandFootpathsFromStop(int stopIdx, int round) { // if stop has no transfers, then no footpaths can be expanded if (stops[stopIdx].numberOfTransfers() == 0) { return; @@ -123,8 +118,7 @@ private void expandFootpathsFromStop(int stopIdx, int round, boolean[] markedSto QueryState.Label label = new QueryState.Label(sourceTime, targetTime, QueryState.LabelType.TRANSFER, i, NO_INDEX, transfer.targetStopIdx(), queryState.getLabel(round, stopIdx)); queryState.setLabel(round, transfer.targetStopIdx(), label); - // mark stop as improved - markedStopsMask[transfer.targetStopIdx()] = true; + queryState.mark(transfer.targetStopIdx()); } } } diff --git a/src/main/java/ch/naviqore/raptor/router/Query.java b/src/main/java/ch/naviqore/raptor/router/Query.java index be68ff97..d811fbef 100644 --- a/src/main/java/ch/naviqore/raptor/router/Query.java +++ b/src/main/java/ch/naviqore/raptor/router/Query.java @@ -114,38 +114,35 @@ private static boolean hasMarkedStops(boolean[] markedStopsMask) { List run() { // initially relax all source stops and add the newly improved stops by relaxation to the marked stops - boolean[] markedStopsMask = initialize(); - footpathRelaxer.relaxInitial(markedStopsMask); - removeSuboptimalLabelsForRound(0, markedStopsMask); + initialize(); + footpathRelaxer.relaxInitial(); + removeSuboptimalLabelsForRound(0); // if range is 0 or smaller there is no range, and we don't need to rerun rounds with different start offsets if (raptorRange <= 0) { - doRounds(markedStopsMask); + doRounds(); } else { - doRangeRaptor(markedStopsMask); + doRangeRaptor(); } return queryState.getBestLabelsPerRound(); } - void doRangeRaptor(boolean[] markedStopsMask) { + void doRangeRaptor() { // prepare range offsets // get initial marked stops to reset after each range offset List initialMarkedStops = new ArrayList<>(); - for (int stopIdx = 0; stopIdx < markedStopsMask.length; stopIdx++) { - if (markedStopsMask[stopIdx]) { - initialMarkedStops.add(stopIdx); - } - } - List rangeOffsets = getRangeOffsets(initialMarkedStops, routeScanner); HashMap stopIdxSourceTimes = new HashMap<>(); - for (int stopIdx = 0; stopIdx < markedStopsMask.length; stopIdx++) { - if (!markedStopsMask[stopIdx]) { + for (int stopIdx = 0; stopIdx < numStops; stopIdx++) { + if (!queryState.isMarkedNextRound(stopIdx)) { continue; } + initialMarkedStops.add(stopIdx); stopIdxSourceTimes.put(stopIdx, queryState.getLabel(0, stopIdx).targetTime()); } + List rangeOffsets = getRangeOffsets(initialMarkedStops, routeScanner); // scan all range offsets in reverse order (earliest arrival / latest departure first) for (int offsetIdx = rangeOffsets.size() - 1; offsetIdx >= 0; offsetIdx--) { + queryState.resetRounds(); int rangeOffset = rangeOffsets.get(offsetIdx); int timeFactor = timeType == TimeType.DEPARTURE ? 1 : -1; log.debug("Running rounds with range offset {}", rangeOffset); @@ -155,8 +152,9 @@ void doRangeRaptor(boolean[] markedStopsMask) { QueryState.Label label = queryState.getLabel(0, stopIdx); int targetTime = stopIdxSourceTimes.get(stopIdx) + timeFactor * rangeOffset; queryState.setLabel(0, stopIdx, copyLabelWithNewTargetTime(label, targetTime)); + queryState.mark(stopIdx); } - doRounds(markedStopsMask); + doRounds(); } } @@ -176,29 +174,22 @@ QueryState.Label copyLabelWithNewTargetTime(QueryState.Label label, int targetTi /** * Method to perform the rounds of the routing algorithm (see {@link #run()}). - * - * @param markedStopsMask the initially marked stops mask. */ - private void doRounds(boolean[] markedStopsMask) { - - // continue with further rounds as long as there are new marked stops - int round = 1; + private void doRounds() { // check if marked stops has any true values - while (hasMarkedStops(markedStopsMask) && (round - 1) <= config.getMaximumTransferNumber()) { + while (queryState.hasMarkedStops() && (queryState.getRound()) <= config.getMaximumTransferNumber()) { // add label layer for new round queryState.addNewRound(); // scan all routs and mark stops that have improved - boolean[] nextMarkedStopsMask = routeScanner.scan(round, markedStopsMask); + routeScanner.scan(queryState.getRound()); // relax footpaths for all newly marked stops - footpathRelaxer.relax(round, nextMarkedStopsMask); + footpathRelaxer.relax(queryState.getRound()); // prepare next round - removeSuboptimalLabelsForRound(round, nextMarkedStopsMask); - markedStopsMask = nextMarkedStopsMask; - round++; + removeSuboptimalLabelsForRound(queryState.getRound()); } } @@ -243,11 +234,8 @@ private List getRangeOffsets(List initialMarkedStops, RouteSca /** * Set up the best times per stop and best labels per round for a new query. - * - * @return the initially marked stops. */ - boolean[] initialize() { - boolean[] markedStopsMask = new boolean[numStops]; + void initialize() { log.debug("Initializing global best times per stop and best labels per round"); // fill target stops @@ -266,38 +254,32 @@ boolean[] initialize() { QueryState.NO_INDEX, QueryState.NO_INDEX, currentStopIdx, null); queryState.setLabel(0, currentStopIdx, label); queryState.setBestTime(currentStopIdx, targetTime); - markedStopsMask[currentStopIdx] = true; + queryState.mark(currentStopIdx); } - - return markedStopsMask; } /** * Nullify labels that are suboptimal for the current round. This method checks if the label time is worse than the * optimal time mark and removes the mark for the next round and nullifies the label in this case. * - * @param round the round to remove suboptimal labels for. - * @param markedStopsMask the marked stops mask to check for suboptimal labels. + * @param round the round to remove suboptimal labels for. */ - void removeSuboptimalLabelsForRound(int round, boolean[] markedStopsMask) { + void removeSuboptimalLabelsForRound(int round) { int bestTime = getBestTimeForAllTargetStops(); if (bestTime == INFINITY || bestTime == -INFINITY) { return; } - for (int stopIdx = 0; stopIdx < markedStopsMask.length; stopIdx++) { - if (!markedStopsMask[stopIdx]) { + for (int stopIdx = 0; stopIdx < numStops; stopIdx++) { + if (!queryState.isMarkedNextRound(stopIdx)) { continue; } QueryState.Label label = queryState.getLabel(round, stopIdx); if (label != null) { - if (timeType == TimeType.DEPARTURE && label.targetTime() > bestTime) { - queryState.setLabel(round, stopIdx, null); - markedStopsMask[stopIdx] = false; - } else if (timeType == TimeType.ARRIVAL && label.targetTime() < bestTime) { + if ((timeType == TimeType.DEPARTURE && label.targetTime() > bestTime) || (timeType == TimeType.ARRIVAL && label.targetTime() < bestTime)) { queryState.setLabel(round, stopIdx, null); - markedStopsMask[stopIdx] = false; + queryState.unmark(stopIdx); } } } diff --git a/src/main/java/ch/naviqore/raptor/router/QueryState.java b/src/main/java/ch/naviqore/raptor/router/QueryState.java index 4f34d8dd..253d210d 100644 --- a/src/main/java/ch/naviqore/raptor/router/QueryState.java +++ b/src/main/java/ch/naviqore/raptor/router/QueryState.java @@ -1,6 +1,7 @@ package ch.naviqore.raptor.router; import ch.naviqore.raptor.TimeType; +import lombok.Getter; import org.jetbrains.annotations.Nullable; import java.util.ArrayList; @@ -10,6 +11,7 @@ /** * This object stores the current best labels and times of the raptor routing algorithm for a query instance. + * Additionally, it also stores information about marked stops for the current and last round. */ final class QueryState { @@ -18,13 +20,17 @@ final class QueryState { // the best labels per stop and round private final List bestLabelsPerRound = new ArrayList<>(); - // the global best time per stop private final int[] bestTimeForStops; - private final int stopSize; private final TimeType timeType; + @Getter + private int round; + + private boolean[] markedStopsMaskNextRound; + private boolean[] markedStopsMaskThisRound; + QueryState(int stopSize, TimeType timeType) { this.stopSize = stopSize; this.timeType = timeType; @@ -33,15 +39,39 @@ final class QueryState { bestTimeForStops = new int[stopSize]; Arrays.fill(bestTimeForStops, timeType == TimeType.DEPARTURE ? INFINITY : -INFINITY); + markedStopsMaskThisRound = new boolean[stopSize]; + markedStopsMaskNextRound = new boolean[stopSize]; + + round = -1; + // set empty labels for first round addNewRound(); } + void resetRounds() { + round = 0; + Arrays.fill(markedStopsMaskThisRound, false); + Arrays.fill(markedStopsMaskNextRound, false); + } + /** * Adds a new round with empty labels. */ void addNewRound() { - bestLabelsPerRound.add(new Label[stopSize]); + if (round != -1) { + // reset boolean marked stop masks, not needed when running the first time + boolean[] tmp = markedStopsMaskThisRound; + markedStopsMaskThisRound = markedStopsMaskNextRound; + markedStopsMaskNextRound = tmp; + Arrays.fill(markedStopsMaskNextRound, false); + } + + round++; + + // only add new round if it does not exist yet (-> in range raptor same round can occur more than once) + if (round >= bestLabelsPerRound.size()) { + bestLabelsPerRound.add(new Label[stopSize]); + } } Label getLabel(int round, int stopIdx) { @@ -93,6 +123,35 @@ List getBestLabelsPerRound() { return Collections.unmodifiableList(bestLabelsPerRound); } + boolean isMarkedThisRound(int stopIdx) { + return markedStopsMaskThisRound[stopIdx]; + } + + boolean isMarkedNextRound(int stopIdx) { + return markedStopsMaskNextRound[stopIdx]; + } + + void mark(int stopIdx) { + markedStopsMaskNextRound[stopIdx] = true; + } + + void unmark(int stopIdx) { + markedStopsMaskNextRound[stopIdx] = false; + } + + boolean hasMarkedStops() { + for (int i = 0; i < stopSize; i++) { + if (markedStopsMaskNextRound[i]) { + return true; + } + } + return false; + } + + boolean[] getMarkedStopsMaskNextRoundClone() { + return markedStopsMaskNextRound.clone(); + } + /** * Arrival type of the label. */ diff --git a/src/main/java/ch/naviqore/raptor/router/RouteScanner.java b/src/main/java/ch/naviqore/raptor/router/RouteScanner.java index b33de078..c67f0b70 100644 --- a/src/main/java/ch/naviqore/raptor/router/RouteScanner.java +++ b/src/main/java/ch/naviqore/raptor/router/RouteScanner.java @@ -9,6 +9,7 @@ import java.time.LocalDate; import java.time.LocalDateTime; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import static ch.naviqore.raptor.router.QueryState.INFINITY; @@ -35,6 +36,8 @@ class RouteScanner { private final int actualDaysToScan; private final int startDayOffset; + private final boolean[] routesToScanMask; + /** * @param queryState the query state with the best time per stop and label per stop and round. * @param raptorData the current raptor data structures. @@ -57,6 +60,8 @@ class RouteScanner { this.minTransferDuration = queryConfig.getMinimumTransferDuration(); this.timeType = timeType; + this.routesToScanMask = new boolean[routes.length]; + LocalDate referenceDate = referenceDateTime.toLocalDate(); if (maxDaysToScan < 1) { @@ -103,36 +108,28 @@ class RouteScanner { /** * Scans all routes passing marked stops for the given round. * - * @param round the current round. - * @param markedStopsMask the marked stops mask for this round. - * @return the marked stops mask for the next round. + * @param round the current round. */ - boolean[] scan(int round, boolean[] markedStopsMask) { - boolean[] routesToScan = getRoutesToScan(markedStopsMask); - log.debug("Scanning routes for round {} ({})", round, routesToScan); + void scan(int round) { + setRoutesToScanMask(); + log.debug("Scanning routes for round {}", round); // scan selected routes and mark stops with improved times - boolean[] markedStopsMaskNext = new boolean[stops.length]; - for (int currentRouteIdx = 0; currentRouteIdx < routesToScan.length; currentRouteIdx++) { - if (!routesToScan[currentRouteIdx]) { + for (int currentRouteIdx = 0; currentRouteIdx < routesToScanMask.length; currentRouteIdx++) { + if (!routesToScanMask[currentRouteIdx]) { continue; } - scanRoute(currentRouteIdx, round, markedStopsMask, markedStopsMaskNext); + scanRoute(currentRouteIdx, round); } - - return markedStopsMaskNext; } /** - * Get all routes to scan from the marked stops. - * - * @param markedStopsMask the mask of marked stops from the previous round. - * @return the mask of routes to scan. + * Set all routes to scan from the marked stops. */ - private boolean[] getRoutesToScan(boolean[] markedStopsMask) { - boolean[] routesToScanMask = new boolean[routes.length]; - for (int stopIdx = 0; stopIdx < markedStopsMask.length; stopIdx++) { - if (!markedStopsMask[stopIdx]) { + private void setRoutesToScanMask() { + Arrays.fill(routesToScanMask, false); + for (int stopIdx = 0; stopIdx < stops.length; stopIdx++) { + if (!queryState.isMarkedThisRound(stopIdx)) { continue; } Stop currentStop = stops[stopIdx]; @@ -143,18 +140,15 @@ private boolean[] getRoutesToScan(boolean[] markedStopsMask) { stopRouteIdx++; } } - return routesToScanMask; } /** * Scan a route in time type applicable direction to find the best times for each stop on route for given round. * - * @param currentRouteIdx the index of the current route. - * @param round the current round. - * @param markedStopsMask the mask of marked stops from the previous round. - * @param markedStopsMaskNext the mask of marked stops for the next round. + * @param currentRouteIdx the index of the current route. + * @param round the current round. */ - private void scanRoute(int currentRouteIdx, int round, boolean[] markedStopsMask, boolean[] markedStopsMaskNext) { + private void scanRoute(int currentRouteIdx, int round) { final int lastRound = round - 1; @@ -182,7 +176,7 @@ private void scanRoute(int currentRouteIdx, int round, boolean[] markedStopsMask int bestStopTime = queryState.getComparableBestTime(stopIdx); // find first marked stop in route if (activeTrip == null) { - if (!canEnterAtStop(stop, bestStopTime, markedStopsMask, stopIdx, stopOffset, currentRoute)) { + if (!canEnterAtStop(stop, bestStopTime, stopIdx, stopOffset, currentRoute)) { continue; } } else { @@ -192,7 +186,7 @@ private void scanRoute(int currentRouteIdx, int round, boolean[] markedStopsMask int targetTime = rawStopTimes[(timeType == TimeType.DEPARTURE) ? stopTimeIndex : stopTimeIndex + 1]; targetTime += activeTrip.dayTimeOffset; if (!checkIfTripIsPossibleAndUpdateMarks(targetTime, activeTrip, stop, bestStopTime, stopIdx, round, - lastRound, markedStopsMaskNext, currentRouteIdx)) { + lastRound, currentRouteIdx)) { continue; } } @@ -217,15 +211,13 @@ private boolean isRouteActiveInDaysToScan(Route route) { * performance reasons) assuming that this check is only run when not travelling with an active trip, the stop was * not marked in a previous round (i.e., the lasts round trip query would be repeated). * - * @param stop the stop to check if a trip can be entered. - * @param stopTime the time at the stop. - * @param markedStopsMask the mask of marked stops from the previous round. - * @param stopIdx the index of the stop to check if a trip can be entered. - * @param stopOffset the offset of the stop in the route. - * @param currentRoute the current route. + * @param stop the stop to check if a trip can be entered. + * @param stopTime the time at the stop. + * @param stopIdx the index of the stop to check if a trip can be entered. + * @param stopOffset the offset of the stop in the route. + * @param currentRoute the current route. */ - private boolean canEnterAtStop(Stop stop, int stopTime, boolean[] markedStopsMask, int stopIdx, int stopOffset, - Route currentRoute) { + private boolean canEnterAtStop(Stop stop, int stopTime, int stopIdx, int stopOffset, Route currentRoute) { int unreachableValue = timeType == TimeType.DEPARTURE ? INFINITY : -INFINITY; if (stopTime == unreachableValue) { @@ -242,7 +234,7 @@ private boolean canEnterAtStop(Stop stop, int stopTime, boolean[] markedStopsMas return false; } - if (!markedStopsMask[stopIdx]) { + if (!queryState.isMarkedThisRound(stopIdx)) { // this stop has already been scanned in previous round without improved target time log.debug("Stop {} was not improved in previous round, continue", stop.id()); return false; @@ -296,18 +288,17 @@ private int getFurthestTripTimeOfRoute(Route route, TimeType timeType) { *

If the time was not improved, an additional check will be needed to figure out if an earlier or later trip * from the stop is possible within the current round, thus the method returns true.

* - * @param targetTime the stop time to check for an earlier or later trip. - * @param activeTrip the active trip to check for an earlier or later trip. - * @param stop the stop to check for an earlier or later trip. - * @param bestStopTime the earliest or latest time at the stop based on the TimeType. - * @param stopIdx the index of the stop to check for an earlier or later trip. - * @param markedStopsMaskNext the mask of marked stops for the next round. - * @param currentRouteIdx the index of the current route. + * @param targetTime the stop time to check for an earlier or later trip. + * @param activeTrip the active trip to check for an earlier or later trip. + * @param stop the stop to check for an earlier or later trip. + * @param bestStopTime the earliest or latest time at the stop based on the TimeType. + * @param stopIdx the index of the stop to check for an earlier or later trip. + * @param currentRouteIdx the index of the current route. * @return true if an earlier or later trip is possible, false otherwise. */ private boolean checkIfTripIsPossibleAndUpdateMarks(int targetTime, ActiveTrip activeTrip, Stop stop, int bestStopTime, int stopIdx, int thisRound, int lastRound, - boolean[] markedStopsMaskNext, int currentRouteIdx) { + int currentRouteIdx) { boolean isImproved = (timeType == TimeType.DEPARTURE) ? targetTime < bestStopTime : targetTime > bestStopTime; @@ -318,7 +309,7 @@ private boolean checkIfTripIsPossibleAndUpdateMarks(int targetTime, ActiveTrip a QueryState.Label label = new QueryState.Label(activeTrip.entryTime, targetTime, QueryState.LabelType.ROUTE, currentRouteIdx, activeTrip.tripOffset, stopIdx, activeTrip.previousLabel); queryState.setLabel(thisRound, stopIdx, label); - markedStopsMaskNext[stopIdx] = true; + queryState.mark(stopIdx); return false; } else { From 27d9c46c07b9d69514f19eaccc1c92e28235d68a Mon Sep 17 00:00:00 2001 From: Merlin Unterfinger Date: Tue, 17 Sep 2024 09:26:12 +0200 Subject: [PATCH 8/9] DOC: NAV-181 - Add javadoc and some cosmetics --- .../raptor/router/FootpathRelaxer.java | 2 +- .../ch/naviqore/raptor/router/QueryState.java | 54 ++++++++++++++++--- 2 files changed, 48 insertions(+), 8 deletions(-) diff --git a/src/main/java/ch/naviqore/raptor/router/FootpathRelaxer.java b/src/main/java/ch/naviqore/raptor/router/FootpathRelaxer.java index e2e9a000..58b9b27d 100644 --- a/src/main/java/ch/naviqore/raptor/router/FootpathRelaxer.java +++ b/src/main/java/ch/naviqore/raptor/router/FootpathRelaxer.java @@ -55,7 +55,7 @@ void relaxInitial() { void relax(int round) { log.debug("Relaxing footpaths for round {}", round); // to prevent extending transfers from stops that were only reached by footpath in the same round - boolean[] routeMarkedStops = queryState.getMarkedStopsMaskNextRoundClone(); + boolean[] routeMarkedStops = queryState.cloneMarkedStopsMaskNextRound(); for (int sourceStopIdx = 0; sourceStopIdx < routeMarkedStops.length; sourceStopIdx++) { if (!routeMarkedStops[sourceStopIdx]) { diff --git a/src/main/java/ch/naviqore/raptor/router/QueryState.java b/src/main/java/ch/naviqore/raptor/router/QueryState.java index 253d210d..d30c00ce 100644 --- a/src/main/java/ch/naviqore/raptor/router/QueryState.java +++ b/src/main/java/ch/naviqore/raptor/router/QueryState.java @@ -18,19 +18,21 @@ final class QueryState { public final static int INFINITY = Integer.MAX_VALUE; public final static int NO_INDEX = -1; + private final int stopSize; + private final TimeType timeType; + // the best labels per stop and round private final List bestLabelsPerRound = new ArrayList<>(); // the global best time per stop private final int[] bestTimeForStops; - private final int stopSize; - private final TimeType timeType; + + // the marked stops for route scanning and footpath relaxing + private boolean[] markedStopsMaskThisRound; + private boolean[] markedStopsMaskNextRound; @Getter private int round; - private boolean[] markedStopsMaskNextRound; - private boolean[] markedStopsMaskThisRound; - QueryState(int stopSize, TimeType timeType) { this.stopSize = stopSize; this.timeType = timeType; @@ -48,6 +50,9 @@ final class QueryState { addNewRound(); } + /** + * Resets the round and marked stops. + */ void resetRounds() { round = 0; Arrays.fill(markedStopsMaskThisRound, false); @@ -55,7 +60,7 @@ void resetRounds() { } /** - * Adds a new round with empty labels. + * Adds a new round with empty labels and reset boolean marked stop masks. */ void addNewRound() { if (round != -1) { @@ -74,6 +79,13 @@ void addNewRound() { } } + /** + * Retrieves the label for a stop at a given round. + * + * @param round the round to get the label from. + * @param stopIdx the index of the stop to retrieve the label for. + * @return the label for the stop in the specified round, or null if not present. + */ Label getLabel(int round, int stopIdx) { return bestLabelsPerRound.get(round)[stopIdx]; } @@ -123,22 +135,47 @@ List getBestLabelsPerRound() { return Collections.unmodifiableList(bestLabelsPerRound); } + /** + * Checks if the stop was marked in the current round. + * + * @param stopIdx the index of the stop to check. + * @return true if the stop was marked in this round, false otherwise. + */ boolean isMarkedThisRound(int stopIdx) { return markedStopsMaskThisRound[stopIdx]; } + /** + * Checks if the stop has been marked for the next round. + * + * @param stopIdx the index of the stop to check. + * @return true if the stop is marked for the next round, false otherwise. + */ boolean isMarkedNextRound(int stopIdx) { return markedStopsMaskNextRound[stopIdx]; } + /** + * Marks the stop for the next round. + * + * @param stopIdx the index of the stop to mark. + */ void mark(int stopIdx) { markedStopsMaskNextRound[stopIdx] = true; } + /** + * Unmarks the stop for the next round. + * + * @param stopIdx the index of the stop to unmark. + */ void unmark(int stopIdx) { markedStopsMaskNextRound[stopIdx] = false; } + /** + * Checks if any stops have been marked for the next round. + */ boolean hasMarkedStops() { for (int i = 0; i < stopSize; i++) { if (markedStopsMaskNextRound[i]) { @@ -148,7 +185,10 @@ boolean hasMarkedStops() { return false; } - boolean[] getMarkedStopsMaskNextRoundClone() { + /** + * Creates a deep copy of the marked stops mask for the next round. + */ + boolean[] cloneMarkedStopsMaskNextRound() { return markedStopsMaskNextRound.clone(); } From 8962884e75d2b129d0dfd1dbdeba4e0d213f5a1a Mon Sep 17 00:00:00 2001 From: Merlin Unterfinger Date: Tue, 17 Sep 2024 09:33:44 +0200 Subject: [PATCH 9/9] DOC: NAV-181 - Update benchmark docs --- src/test/java/ch/naviqore/Benchmark.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/ch/naviqore/Benchmark.java b/src/test/java/ch/naviqore/Benchmark.java index 2f414ae5..8cb4083f 100644 --- a/src/test/java/ch/naviqore/Benchmark.java +++ b/src/test/java/ch/naviqore/Benchmark.java @@ -35,7 +35,7 @@ * Measures the time it takes to route a number of requests using Raptor algorithm on large GTFS datasets. *

* Note: To run this benchmark, ensure that the log level is set to INFO in the - * {@code src/test/resources/log4j2-test.properties} file. + * {@code src/test/resources/logback-test.xml} file. * * @author munterfi */