Skip to content

Commit

Permalink
Merge pull request #118 from naviqore/feature/NAV-181-initialize-hash…
Browse files Browse the repository at this point in the history
…set-size-of-marked-stops

Feature/nav 181 initialize hashset size of marked stops
  • Loading branch information
munterfi authored Sep 17, 2024
2 parents e200499 + 8962884 commit f6b1e32
Show file tree
Hide file tree
Showing 5 changed files with 211 additions and 118 deletions.
46 changes: 15 additions & 31 deletions src/main/java/ch/naviqore/raptor/router/FootpathRelaxer.java
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -45,50 +41,39 @@ 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.
*/
Set<Integer> relaxInitial(int[] stopIndices) {
void relaxInitial() {
log.debug("Initial relaxing of footpaths for source stops");
Set<Integer> newlyMarkedStops = new HashSet<>();

for (int sourceStopIdx : stopIndices) {
expandFootpathsFromStop(sourceStopIdx, 0, newlyMarkedStops);
}

return newlyMarkedStops;
relax(0);
}

/**
* 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.
*/
Set<Integer> relax(int round, Collection<Integer> stopIndices) {
void relax(int round) {
log.debug("Relaxing footpaths for round {}", round);
Set<Integer> newlyMarkedStops = new HashSet<>();
// to prevent extending transfers from stops that were only reached by footpath in the same round
boolean[] routeMarkedStops = queryState.cloneMarkedStopsMaskNextRound();

for (int sourceStopIdx : stopIndices) {
expandFootpathsFromStop(sourceStopIdx, round, newlyMarkedStops);
for (int sourceStopIdx = 0; sourceStopIdx < routeMarkedStops.length; sourceStopIdx++) {
if (!routeMarkedStops[sourceStopIdx]) {
continue;
}
expandFootpathsFromStop(sourceStopIdx, round);
}

return newlyMarkedStops;
}

/**
* Expands all transfers between stops from a given stop. If a transfer improves the target time at the target stop,
* 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.
*/
private void expandFootpathsFromStop(int stopIdx, int round, Set<Integer> markedStops) {
private void expandFootpathsFromStop(int stopIdx, int round) {
// if stop has no transfers, then no footpaths can be expanded
if (stops[stopIdx].numberOfTransfers() == 0) {
return;
Expand Down Expand Up @@ -133,8 +118,7 @@ private void expandFootpathsFromStop(int stopIdx, int round, Set<Integer> marked
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
markedStops.add(transfer.targetStopIdx());
queryState.mark(transfer.targetStopIdx());
}
}
}
108 changes: 61 additions & 47 deletions src/main/java/ch/naviqore/raptor/router/Query.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -30,6 +33,8 @@ class Query {
private final FootpathRelaxer footpathRelaxer;
private final RouteScanner routeScanner;

private final int numStops;

private final int raptorRange;

/**
Expand Down Expand Up @@ -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
Expand All @@ -74,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.
Expand All @@ -93,40 +114,47 @@ class Query {
List<QueryState.Label[]> run() {

// initially relax all source stops and add the newly improved stops by relaxation to the marked stops
Set<Integer> markedStops = initialize();
markedStops.addAll(footpathRelaxer.relaxInitial(sourceStopIndices));
markedStops = removeSuboptimalLabelsForRound(0, markedStops);
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(markedStops);
doRounds();
} else {
doRangeRaptor(markedStops);
doRangeRaptor();
}
return queryState.getBestLabelsPerRound();
}

void doRangeRaptor(Set<Integer> markedStops) {
void doRangeRaptor() {
// prepare range offsets
List<Integer> rangeOffsets = getRangeOffsets(markedStops, routeScanner);
// get initial marked stops to reset after each range offset
List<Integer> initialMarkedStops = new ArrayList<>();
HashMap<Integer, Integer> stopIdxSourceTimes = new HashMap<>();
for (int stopIdx : markedStops) {
for (int stopIdx = 0; stopIdx < numStops; stopIdx++) {
if (!queryState.isMarkedNextRound(stopIdx)) {
continue;
}
initialMarkedStops.add(stopIdx);
stopIdxSourceTimes.put(stopIdx, queryState.getLabel(0, stopIdx).targetTime());
}

List<Integer> 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);

// 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));
queryState.mark(stopIdx);
}
doRounds(markedStops);
doRounds();
}
}

Expand All @@ -146,26 +174,22 @@ 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.
*/
private void doRounds(Set<Integer> markedStops) {
private void doRounds() {

// 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 (queryState.hasMarkedStops() && (queryState.getRound()) <= config.getMaximumTransferNumber()) {
// add label layer for new round
queryState.addNewRound();

// scan all routs and mark stops that have improved
Set<Integer> markedStopsNext = routeScanner.scan(round, markedStops);
routeScanner.scan(queryState.getRound());

// relax footpaths for all newly marked stops
markedStopsNext.addAll(footpathRelaxer.relax(round, markedStopsNext));
footpathRelaxer.relax(queryState.getRound());

// prepare next round
markedStops = removeSuboptimalLabelsForRound(round, markedStopsNext);
round++;
removeSuboptimalLabelsForRound(queryState.getRound());
}
}

Expand All @@ -180,13 +204,13 @@ private void doRounds(Set<Integer> 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<Integer> getRangeOffsets(Set<Integer> markedStops, RouteScanner routeScanner) {
private List<Integer> getRangeOffsets(List<Integer> initialMarkedStops, RouteScanner routeScanner) {
ArrayList<Integer> rangeOffsets = new ArrayList<>();
for (int stopIdx : markedStops) {
for (int stopIdx : initialMarkedStops) {
List<Integer> stopRangeOffsets = routeScanner.getTripOffsetsForStop(stopIdx, raptorRange);
for (int i = 0; i < stopRangeOffsets.size(); i++) {
// if the rangeOffsets list is not long enough, add the offset
Expand All @@ -210,10 +234,8 @@ private List<Integer> getRangeOffsets(Set<Integer> markedStops, RouteScanner rou

/**
* Set up the best times per stop and best labels per round for a new query.
*
* @return the initially marked stops.
*/
Set<Integer> initialize() {
void initialize() {
log.debug("Initializing global best times per stop and best labels per round");

// fill target stops
Expand All @@ -224,7 +246,6 @@ Set<Integer> initialize() {
}

// set initial labels, best time and mark source stops
Set<Integer> markedStops = new HashSet<>();
for (int i = 0; i < sourceStopIndices.length; i++) {
int currentStopIdx = sourceStopIndices[i];
int targetTime = sourceTimes[i];
Expand All @@ -233,42 +254,35 @@ Set<Integer> initialize() {
QueryState.NO_INDEX, QueryState.NO_INDEX, currentStopIdx, null);
queryState.setLabel(0, currentStopIdx, label);
queryState.setBestTime(currentStopIdx, targetTime);

markedStops.add(currentStopIdx);
queryState.mark(currentStopIdx);
}

return markedStops;
}

/**
* 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.
*/
Set<Integer> removeSuboptimalLabelsForRound(int round, Set<Integer> markedStops) {
void removeSuboptimalLabelsForRound(int round) {
int bestTime = getBestTimeForAllTargetStops();

if (bestTime == INFINITY || bestTime == -INFINITY) {
return markedStops;
return;
}

Set<Integer> markedStopsClean = new HashSet<>();
for (int stopIdx : markedStops) {
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);
} 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);
} else {
markedStopsClean.add(stopIdx);
queryState.unmark(stopIdx);
}
}
}

return markedStopsClean;
}

/**
Expand Down
Loading

0 comments on commit f6b1e32

Please sign in to comment.