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

145 bug non pareto optimal solution #153

Merged
merged 4 commits into from
Nov 6, 2024
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -70,11 +70,12 @@ List<Connection> reconstructParetoOptimalSolutions(List<QueryState.Label[]> best
Map<Integer, Integer> targetStops, LocalDate referenceDate) {
final List<Connection> connections = new ArrayList<>();

int bestTime = timeType == TimeType.DEPARTURE ? INFINITY : -INFINITY;

// iterate over all rounds
for (QueryState.Label[] labels : bestLabelsPerRound) {

QueryState.Label label = null;
int bestTime = timeType == TimeType.DEPARTURE ? INFINITY : -INFINITY;

for (Map.Entry<Integer, Integer> entry : targetStops.entrySet()) {
int targetStopIdx = entry.getKey();
Expand Down
16 changes: 12 additions & 4 deletions src/main/java/ch/naviqore/raptor/router/QueryState.java
Original file line number Diff line number Diff line change
Expand Up @@ -111,14 +111,22 @@ int getComparableBestTime(int stopIdx) {
* different label types (transfer vs. route), as the same stop transfer time is not considered.
*/
int getActualBestTime(int stopIdx) {
for (int i = bestLabelsPerRound.size() - 1; i >= 0; i--) {
Label label = bestLabelsPerRound.get(i)[stopIdx];
int best_time = (timeType == TimeType.DEPARTURE) ? INFINITY : -INFINITY;

// because range raptor potentially fills target times in higher rounds which are not the best solutions, every
// round has to be looked at.
for (Label[] labels : bestLabelsPerRound) {
Label label = labels[stopIdx];
if (label != null) {
return label.targetTime;
if (timeType == TimeType.DEPARTURE) {
best_time = Math.min(best_time, label.targetTime);
} else {
best_time = Math.max(best_time, label.targetTime);
}
}
}

return (timeType == TimeType.DEPARTURE) ? INFINITY : -INFINITY;
return best_time;
}

/**
Expand Down
48 changes: 48 additions & 0 deletions src/test/java/ch/naviqore/raptor/router/RangeRaptorTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ public class RangeRaptorTest {

private static final String STOP_A = "A";
private static final String STOP_I = "I";
private static final String STOP_K = "K";
private static final String STOP_N = "N";

private static final LocalDateTime START_OF_DAY = LocalDateTime.of(2021, 1, 1, 0, 0);
Expand Down Expand Up @@ -261,6 +262,53 @@ void findArrivalConnections_withSourceTransferFirst() {
STOP_A, STOP_N);
}

@Test
void ensureParetoOptimalConnections() {
// this test is based on a previous bug, where the range raptor router returned connections which were not
// pareto optimal (later arrival time and more rounds). This is owed to the fact that the range raptor spawns
// at different time points (range offsets) and potentially finds earliest arrival connections for the given
// offset with more rounds than the final best arrival time.
// this test reproduces this case by introducing a low frequency fast connection and a high frequency slower
// connection.

int headwayRoute1 = 15;
int headwayRoute2 = 60;
int headwayRoute3and4 = 5;

int dwellTime = 0; // simplification to better calculate times by hand

RaptorAlgorithm rangeRaptor = new RaptorRouterTestBuilder().withAddRoute1_AG(
RaptorRouterTestBuilder.DEFAULT_OFFSET, headwayRoute1,
RaptorRouterTestBuilder.DEFAULT_TIME_BETWEEN_STOPS, dwellTime)
.withAddRoute2_HL(RaptorRouterTestBuilder.DEFAULT_OFFSET, headwayRoute2,
RaptorRouterTestBuilder.DEFAULT_TIME_BETWEEN_STOPS, dwellTime)
.withAddRoute3_MQ(RaptorRouterTestBuilder.DEFAULT_OFFSET, headwayRoute3and4,
RaptorRouterTestBuilder.DEFAULT_TIME_BETWEEN_STOPS, dwellTime)
.withAddRoute4_RS(RaptorRouterTestBuilder.DEFAULT_OFFSET, headwayRoute3and4,
RaptorRouterTestBuilder.DEFAULT_TIME_BETWEEN_STOPS, dwellTime)
.withSameStopTransferTime(0)
.withRaptorRange(900) // departures at 08:00 and 08:15
.withMaxDaysToScan(1)
.build();

// departure at 8:00 will yield only one fastest connection
// 08:00 A --> R1 --> 08:05 B
// 08:05 B --> R2 --> 08:20 K
LocalDateTime expectedDepartureTime = EIGHT_AM;
LocalDateTime expectedArrivalTime = expectedDepartureTime.plusMinutes(20);

// however since spawning at 08:15 (first range checked) will find following best solution, this test must
// ensure that this connection is not returned as it is not pareto optimal.
// 08:15 A --> R1 --> 08:40 F
// 08:40 F --> R4 --> 08:45 P
// 08:45 P --> R3 --> 09:00 K
List<Connection> connections = RaptorRouterTestHelpers.routeEarliestArrival(rangeRaptor, STOP_A, STOP_K,
EIGHT_AM);
assertEquals(1, connections.size());
RangeRaptorHelpers.assertConnection(connections.getFirst(), expectedDepartureTime, expectedArrivalTime, 2,
STOP_A, STOP_K);
}

static class RangeRaptorHelpers {

static void assertConnection(Connection connection, LocalDateTime expectedDepartureTime,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,11 @@ public RaptorRouterTestBuilder withAddRoute4_RS() {
return this;
}

public RaptorRouterTestBuilder withAddRoute4_RS(int offset, int headway, int travelTime, int dwellTime) {
routes.add(new Route("R4", List.of("R", "P", "F", "S"), offset, headway, travelTime, dwellTime));
return this;
}

public RaptorRouterTestBuilder withAddRoute5_AH_selfIntersecting() {
routes.add(new Route("R5", List.of("A", "B", "C", "D", "E", "F", "P", "O", "N", "K", "J", "I", "B", "H")));
return this;
Expand Down