Skip to content

Commit

Permalink
Merge pull request #153 from naviqore/145-bug-non-pareto-optimal-solu…
Browse files Browse the repository at this point in the history
…tion

145 bug non pareto optimal solution
  • Loading branch information
munterfi authored Nov 6, 2024
2 parents 1fe7f69 + 732f829 commit e5ec3bc
Show file tree
Hide file tree
Showing 4 changed files with 67 additions and 5 deletions.
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

0 comments on commit e5ec3bc

Please sign in to comment.