Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/nav 181 initialize hashset size of marked stops #117

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 19 additions & 29 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 @@ -46,49 +42,43 @@ 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<Integer> relaxInitial(int[] stopIndices) {
void relaxInitial(boolean[] markedStopsMask) {
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, 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<Integer> relax(int round, Collection<Integer> stopIndices) {
void relax(int round, boolean[] markedStopsMask) {
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 = 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;
}

/**
* 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.
* @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<Integer> 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;
Expand Down Expand Up @@ -134,7 +124,7 @@ private void expandFootpathsFromStop(int stopIdx, int round, Set<Integer> 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;
}
}
}
104 changes: 68 additions & 36 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 Down Expand Up @@ -93,35 +99,44 @@ 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);
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<Integer> markedStops) {
void doRangeRaptor(boolean[] markedStops) {
// prepare range offsets
List<Integer> rangeOffsets = getRangeOffsets(markedStops, routeScanner);
// get initial marked stops to reset after each range offset
List<Integer> initialMarkedStops = new ArrayList<>();
for (int stopIdx = 0; stopIdx < markedStops.length; stopIdx++) {
if (markedStops[stopIdx]) {
initialMarkedStops.add(stopIdx);
}
}
List<Integer> rangeOffsets = getRangeOffsets(initialMarkedStops, routeScanner);
HashMap<Integer, Integer> 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);
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));
Expand All @@ -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<Integer> 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<Integer> 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.
* <p>
Expand All @@ -180,13 +213,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 @@ -213,7 +246,8 @@ private List<Integer> getRangeOffsets(Set<Integer> markedStops, RouteScanner rou
*
* @return the initially marked stops.
*/
Set<Integer> initialize() {
boolean[] initialize() {
boolean[] markedStopsMask = new boolean[numStops];
log.debug("Initializing global best times per stop and best labels per round");

// fill target stops
Expand All @@ -224,7 +258,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 +266,41 @@ Set<Integer> 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<Integer> removeSuboptimalLabelsForRound(int round, Set<Integer> markedStops) {
void removeSuboptimalLabelsForRound(int round, boolean[] markedStopsMask) {
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 < 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;
}

/**
Expand Down
Loading