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

Fix bug in heuristics cost calculation for egress legs #5783

Merged
Show file tree
Hide file tree
Changes from 1 commit
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 @@ -140,7 +140,13 @@ private int bestNumOfTransfers(int stop) {
}

private int bestGeneralizedCost(int stop) {
return costCalculator.calculateMinCost(bestTravelDuration(stop), bestNumOfTransfers(stop));
return (
costCalculator.calculateRemainingMinCost(
bestTravelDuration(stop),
bestNumOfTransfers(stop),
stop
)
);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,12 @@ int boardingCost(

/**
* Used for estimating the remaining value for a criteria at a given stop arrival. The calculated
* value should be a an optimistic estimate for the heuristics to work properly. So, to calculate
* value should be an optimistic estimate for the heuristics to work properly. So, to calculate
* the generalized cost for given the {@code minTravelTime} and {@code minNumTransfers} retuning
* the greatest value, which is guaranteed to be less than the
* <em>real value</em> would be correct and a good choose.
* <em>real value</em> would be correct and a good choice.
*/
int calculateMinCost(int minTravelTime, int minNumTransfers);
int calculateRemainingMinCost(int minTravelTime, int minNumTransfers, int fromStop);

/**
* This method allows the cost calculator to add cost in addition to the generalized-cost of the
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ public class CostCalculatorFactory {

public static <T extends DefaultTripSchedule> RaptorCostCalculator<T> createCostCalculator(
GeneralizedCostParameters generalizedCostParameters,
int[] stopBoardAlightCosts
int[] stopTransferCosts
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

boardAndAlightCost is not the same as transferCosts - we can discuss this in the OTP dev meeting

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My reasoning was that it looks like the costCalculator treats the boardAlightCosts as transferCosts, and thus it would be a little bit clearer how this parameter will be used if this was reflected in the signature for the createCostCalculator() method.

But as discussed in the meeting it might be better to draw the line at the module border. I'm fine with reverting this.

Copy link
Member

@t2gran t2gran Apr 23, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for the confution here. This is the case:

  • Raptor does not have transferCost only alight and board cost, so the transfer cost is added either to boarding(if symetrical) or in this case split in two and added to both alight and boarding - to stops may have different cost - hence we need to add a cost for both alighting and boarding.

This "mess" should probably be visited again and cleaned up - the cost model have grown and it might be just a bit more clear if Raptor supported board, alight and transfer costs - unsure what the performance overhead it. Performance was the original reason for why this is like it is. Compressing stop information in memory increases the performance.

So this feature comes from NeTEX where the name is transfer, but since we are adding the cost to alight and boarding (both ends) I think the name in the OTP model i wrong - the mapping should happen when going from stopPriority to cost. A few comments in the model would have helped as well.

In TransitRoutingConfig and TransitTuningParameters this should be renamed to something like boardAndAlightCostForStopTransferPriority - a shorter name and a better java doc is probably the best solution.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sumarized: boardAndAlight cost is added twice (both ends of a transfer), while transferCost is added once.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for that explanation!

The confusing thing about the name "stopBoardAlightCosts" is that it sounds like it should be applied for the first boarding and the last alighting as well. There is nothing that indicates that it is specific to transfers. But calling it stopBoardAlightTransferCosts may be a bit long?

Here are some possible alternatives for the name of this concept:

  • stopTransferCosts
  • stopBoardAlightCosts
  • stopBoardAlightTransferCosts
  • stopBoardAlightDuringTransferCosts
  • stopHalfTransferCosts

Do you think we should go with "stopBoardAlightCosts"?

I'm ok with that and think the naming is perhaps less important than being consistent, so I'm happy to make the naming consistent and will add some javadoc.

Copy link
Member

@t2gran t2gran Apr 25, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can use this stopBoardAlightTransferCosts with JavaDoc.

Doc in one place and link to doc in other places.

) {
RaptorCostCalculator<T> calculator = new DefaultCostCalculator<>(
generalizedCostParameters,
stopBoardAlightCosts
stopTransferCosts
);

if (generalizedCostParameters.wheelchairEnabled()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,14 +121,21 @@ public int waitCost(int waitTimeInSeconds) {
}

@Override
public int calculateMinCost(int minTravelTime, int minNumTransfers) {
return (
boardCostOnly +
boardAndTransferCost *
minNumTransfers +
transitFactors.minFactor() *
minTravelTime
);
public int calculateRemainingMinCost(int minTravelTime, int minNumTransfers, int fromStop) {
if (minNumTransfers > -1) {
return (
boardCostOnly +
boardAndTransferCost *
minNumTransfers +
transitFactors.minFactor() *
minTravelTime
);
} else if (stopTransferCost != null) {
// Remove cost that was added during alighting similar as we do in the costEgress() method
return (transitFactors.minFactor() * minTravelTime - stopTransferCost[fromStop]);
} else {
return transitFactors.minFactor() * minTravelTime;
}
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,8 @@ public int waitCost(int waitTimeInSeconds) {
}

@Override
public int calculateMinCost(int minTravelTime, int minNumTransfers) {
return delegate.calculateMinCost(minTravelTime, minNumTransfers);
public int calculateRemainingMinCost(int minTravelTime, int minNumTransfers, int fromStop) {
return delegate.calculateRemainingMinCost(minTravelTime, minNumTransfers, fromStop);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,8 @@ public int waitCost(int waitTimeInSeconds) {
}

@Override
public int calculateMinCost(int minTravelTime, int minNumTransfers) {
return delegate.calculateMinCost(minTravelTime, minNumTransfers);
public int calculateRemainingMinCost(int minTravelTime, int minNumTransfers, int fromStop) {
return delegate.calculateRemainingMinCost(minTravelTime, minNumTransfers, fromStop);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ public interface RaptorTestConstants {
int STOP_L = 12;
int STOP_M = 13;

int NUM_STOPS = 14;

// Stop position in pattern
int STOP_POS_0 = 0;
int STOP_POS_1 = 1;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ public class TestTransitData
private final List<ConstrainedTransfer> constrainedTransfers = new ArrayList<>();
private final GeneralizedCostParametersBuilder costParamsBuilder = GeneralizedCostParameters.of();

private final int[] stopBoardAlightCosts = new int[NUM_STOPS];

private RaptorSlackProvider slackProvider = SLACK_PROVIDER;

@Override
Expand Down Expand Up @@ -300,6 +302,11 @@ public TestTransitData withConstrainedTransfer(
return this;
}

public TestTransitData withStopBoardAlightCost(int stop, int boardAlightCost) {
stopBoardAlightCosts[stop] = boardAlightCost;
return this;
}

public GeneralizedCostParametersBuilder mcCostParamsBuilder() {
return costParamsBuilder;
}
Expand Down Expand Up @@ -352,7 +359,7 @@ public List<DefaultTripPattern> getPatterns() {

private int[] stopBoardAlightCost() {
// Not implemented, no test for this yet.
return null;
return stopBoardAlightCosts;
}

private void expandNumOfStops(int stopIndex) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package org.opentripplanner.raptor.moduletests;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.opentripplanner.raptor._data.transit.TestRoute.route;
import static org.opentripplanner.raptor._data.transit.TestTripSchedule.schedule;
import static org.opentripplanner.raptor.moduletests.support.RaptorModuleTestConfig.multiCriteria;

import java.util.List;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import org.opentripplanner.raptor.RaptorService;
import org.opentripplanner.raptor._data.RaptorTestConstants;
import org.opentripplanner.raptor._data.transit.TestAccessEgress;
import org.opentripplanner.raptor._data.transit.TestTransitData;
import org.opentripplanner.raptor._data.transit.TestTripSchedule;
import org.opentripplanner.raptor.api.request.RaptorRequestBuilder;
import org.opentripplanner.raptor.configure.RaptorConfig;
import org.opentripplanner.raptor.moduletests.support.ModuleTestDebugLogging;
import org.opentripplanner.raptor.moduletests.support.RaptorModuleTestCase;

/**
* FEATURE UNDER TEST
* <p>
* This verifies that the stopTransferCost is not applied for egress legs. If this is not correctly
* handled by the heuristics optimization, the cheapest journey could be discarded.
*/
public class B05_EgressStopTransferCostTest implements RaptorTestConstants {

private final TestTransitData data = new TestTransitData();
private final RaptorRequestBuilder<TestTripSchedule> requestBuilder = new RaptorRequestBuilder<>();
private final RaptorService<TestTripSchedule> raptorService = new RaptorService<>(
RaptorConfig.defaultConfigForTest()
);

@BeforeEach
void setup() {
data
.withRoute(route("R1", STOP_B, STOP_C).withTimetable(schedule("0:10, 0:14")))
.withRoute(route("R2", STOP_C, STOP_D).withTimetable(schedule("0:18, 0:20")));

data.mcCostParamsBuilder().transferCost(0).boardCost(0);
data.withStopBoardAlightCost(STOP_D, 60000);

requestBuilder
.searchParams()
.addAccessPaths(TestAccessEgress.free(STOP_B))
.addEgressPaths(
TestAccessEgress.walk(STOP_C, D5m), // This will be the fastest
TestAccessEgress.walk(STOP_D, D20s) // This will be the cheapest
)
.earliestDepartureTime(T00_00)
.latestArrivalTime(T00_30);

ModuleTestDebugLogging.setupDebugLogging(data, requestBuilder);
}

static List<RaptorModuleTestCase> testCases() {
return RaptorModuleTestCase
.of()
.add(
multiCriteria(),
// We should get both the fastest and the c1-cheapest results
// The stopTransferCost should not be applied to the egress leg from STOP_D
"B ~ BUS R1 0:10 0:14 ~ C ~ Walk 5m [0:10 0:19 9m Tₓ0 C₁840]",
"B ~ BUS R1 0:10 0:14 ~ C ~ BUS R2 0:18 0:20 ~ D ~ Walk 20s [0:10 0:20:20 10m20s Tₓ1 C₁640]"
)
.build();
}

@ParameterizedTest
@MethodSource("testCases")
void testRaptor(RaptorModuleTestCase testCase) {
assertEquals(testCase.expected(), testCase.run(raptorService, data, requestBuilder));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -76,22 +76,28 @@ public void calculateMinCost() {
// - Transit factor: 80 (min of 80 and 100)

// Board cost is 500:
assertEquals(500, subject.calculateMinCost(0, 0));
assertEquals(500, subject.calculateRemainingMinCost(0, 0, 0));
// The transfer 1s * 80 = 80 + board cost 500
assertEquals(580, subject.calculateMinCost(1, 0));
assertEquals(580, subject.calculateRemainingMinCost(1, 0, 0));
// Board 2 times and transfer 1: 2 * 500 + 200
assertEquals(1200, subject.calculateMinCost(0, 1));
assertEquals(1200, subject.calculateRemainingMinCost(0, 1, 0));

// Transit 200s * 80 + Board 4 * 500 + Transfer 3 * 200
assertEquals(18_600, subject.calculateMinCost(200, 3));
assertEquals(18_600, subject.calculateRemainingMinCost(200, 3, 0));

// Cost of egress should subtract the stop transfer cost 25
assertEquals(-25, subject.calculateRemainingMinCost(0, -1, 1));
}

@Test
public void testConvertBetweenRaptorAndMainOtpDomainModel() {
assertEquals(RaptorCostConverter.toRaptorCost(BOARD_COST_SEC), subject.calculateMinCost(0, 0));
assertEquals(
RaptorCostConverter.toRaptorCost(BOARD_COST_SEC),
subject.calculateRemainingMinCost(0, 0, 0)
);
assertEquals(
RaptorCostConverter.toRaptorCost(0.8 * 20 + BOARD_COST_SEC),
subject.calculateMinCost(20, 0)
subject.calculateRemainingMinCost(20, 0, 0)
);
}

Expand Down
Loading