-
Notifications
You must be signed in to change notification settings - Fork 1k
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
Enable mode-specific transfers by storing mode information in transfers #6293
base: dev-2.x
Are you sure you want to change the base?
Enable mode-specific transfers by storing mode information in transfers #6293
Conversation
The tests will fail for |
Interestingly |
Interestingly now after making changes not related to functionality, the |
…into split-transfers-by-mode-pathtransfer-mode
…er requests for WALK mode.
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## dev-2.x #6293 +/- ##
=============================================
+ Coverage 69.79% 69.81% +0.02%
- Complexity 17788 17836 +48
=============================================
Files 2017 2020 +3
Lines 76042 76297 +255
Branches 7781 7804 +23
=============================================
+ Hits 53074 53270 +196
- Misses 20264 20311 +47
- Partials 2704 2716 +12 ☔ View full report in Codecov by Sentry. |
if (pathTransfer == null) { | ||
EnumSet<StreetMode> modes = EnumSet.of(mode); | ||
distinctTransfers.put( | ||
transferKey, | ||
new PathTransfer(stop, sd.stop, sd.distance, sd.edges, modes) | ||
); | ||
} else { | ||
pathTransfer.addMode(mode); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You could add a comment here to explain what these different cases mean.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added comments for the different cases
} | ||
// Calculate flex transfers if flex routing is enabled. | ||
for (RouteRequest transferProfile : flexTransferRequests) { | ||
StreetMode mode = transferProfile.journey().transfer().mode(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this just always WALK?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We have always assumed that flex transfers can only be on foot. I think this is reasonable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I changed this to StreetMode mode = StreetMode.WALK;
@@ -250,6 +250,61 @@ public void testMultipleRequestsWithPatterns() { | |||
); | |||
} | |||
|
|||
@Test | |||
public void testPathTransfersWithModesForMultipleRequestsWithPatterns() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What does multiple requests with patterns mean?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried to follow a similar naming convention as the other tests in the file. With patterns means that trippatterns are added to the timetablerepository of the test. Multiple requests means that transfers are generated for multiple requests that have different modes:
var reqWalk = new RouteRequest();
reqWalk.journey().transfer().setMode(StreetMode.WALK);
var reqBike = new RouteRequest();
reqBike.journey().transfer().setMode(StreetMode.BIKE);
var transferRequests = List.of(reqWalk, reqBike);
this.toStop = toStop; | ||
this.edges = edges; | ||
this.distanceMeters = (int) edges.stream().mapToDouble(Edge::getDistanceMeters).sum(); | ||
this.modes = modes; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We probably want to make this immutable. EnumSet are not.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I changed the getModes
method to clone the enum set
var walkTransfers = timetableRepository | ||
.getAllPathTransfers() | ||
.stream() | ||
.filter(pathTransfer -> pathTransfer.getModes().contains(StreetMode.WALK)) | ||
.toList(); | ||
var bikeTransfers = timetableRepository | ||
.getAllPathTransfers() | ||
.stream() | ||
.filter(pathTransfer -> pathTransfer.getModes().contains(StreetMode.BIKE)) | ||
.toList(); | ||
var carTransfers = timetableRepository | ||
.getAllPathTransfers() | ||
.stream() | ||
.filter(pathTransfer -> pathTransfer.getModes().contains(StreetMode.CAR)) | ||
.toList(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
please extract a method
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I created a getTransfersByMode
method in TimetableRepository
Does this mean that this PR allows you to compute CAR transfers between stops that don't serve cars at all? |
@@ -41,6 +43,7 @@ public static RaptorTransferIndex create( | |||
var transfers = transfersByStopIndex | |||
.get(fromStop) | |||
.stream() | |||
.filter(transfer -> transfer.getModes().contains(mode)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is run only once and then cached for the specific combination of request parameters, correct?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, this is run once for each cache request. You can see the code that uses this in RaptorRequestTransferCache
return this.modes; | ||
} | ||
|
||
public boolean addMode(StreetMode mode) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we want to make this class mutable. Can you create a copy method, for example withAddedMode
that returns a new instance instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I changed this to use the withAddedMode
method
@@ -46,6 +56,14 @@ public List<Edge> getEdges() { | |||
return this.edges; | |||
} | |||
|
|||
public EnumSet<StreetMode> getModes() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't want to expose the mutable EnumSet. Can you replace it with a method like allows(CAR)
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I changed the getModes
method to clone the enum set
transfersToStop.put(transfer.to, transfer); | ||
transfersFromStop.put(transfer.from, transfer); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you explain why this is now necessary?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is not strictly necessary, but instead it limits the available transfers to the WALK
mode when searching for fromstops for flex transfers. Previously all of the transfers for a stop, including non-walk mode transfers, were given when using the getTransfersFromStop
method in the FlexRouter
. This was because it used the findPathTransfers
method which gives all transfers for a given stop.
I think this could use my current implementation or the old one. I do have a somewhat shallow understanding of flex routing at the moment, but I went with this implementation because it seems more correct.
.map(transferProfile -> transferProfile.journey().transfer().mode()) | ||
.collect(Collectors.toSet())) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
.map(transferProfile -> transferProfile.journey().transfer().mode()) | |
.collect(Collectors.toSet())) { | |
.map(transferProfile -> transferProfile.journey().transfer().mode()) | |
.distinct() | |
.toList() |
Not a huge deal but avoiding a set gives you predictable sort order.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, this is a good suggestion. I changed it to this
Yes, in a future PR (#6215) I will implement more configurability for transfers |
} | ||
|
||
public EnumSet<StreetMode> getModes() { | ||
return modes.clone(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return modes.clone(); | |
return EnumSet.copyOf(modes); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I changed this
this.toStop = toStopIndex; | ||
this.distanceMeters = distanceMeters; | ||
this.edges = null; | ||
this.modes = modes; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This transfer is not serialized. Here you can use Sets.immutableEnumSet and then never have to worry about cloning or copying.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I changed this to use Sets.immutableEnumSet
for (StreetMode mode : transferRequests | ||
.stream() | ||
.map(transferProfile -> transferProfile.journey().transfer().mode()) | ||
.distinct() | ||
.toList()) { | ||
LOG.info( | ||
"Created {} transfers for mode {}.", | ||
transfersByStop | ||
.values() | ||
.stream() | ||
.filter(pathTransfer -> pathTransfer.getModes().contains(mode)) | ||
.count(), | ||
mode | ||
); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is quite difficult to read. I would probably continue the stream instead of making it a list and then looping over it, and then call some method with the mode as an argument that does the logging. So something like:
transferRequests
.stream()
.map(transferProfile -> transferProfile.journey().transfer().mode())
.distinct()
.peek(mode -> logTransfersForMode(mode));
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I changed it to this:
transferRequests
.stream()
.map(transferProfile -> transferProfile.journey().transfer().mode())
.distinct()
.forEach(mode ->
LOG.info(
"Created {} transfers for mode {}.",
timetableRepository.getTransfersByMode(mode).size(),
mode
)
);
I am following this as I am now testing BICYCLE routing on our deployment. The current deployment configuration only has "walk" with However, if I set the |
Without knowing your deployment in more detail, this sounds like there is something wrong with your configuration. While this PR limits the transfers to a specific mode thus maybe improving performance, the main reason for implementing this PR was to remove weird transfers that were relevant for a certain mode but not necessarily for another. Therefore I suggest looking more closely at your configuration for performance improvements. A large graph size because of transfers could be caused by, for example, lots of transfers or transfers with lots of edges. |
How many transfers are in your graph? If I enable 45-minute transfers on bikes, it will generate approximately 15 million transfers just in London (as there are about 44k stops in London - however only thousands will actually serve bikes)! However, the majority of these transfers are useless and only those stops which have bike permitting services (a minority of the stops) are relevant, and without change, the graph is so large that it is flooded with useless transfers to the extent that it is unusable. Long transfers are needed to enable people cycling across London to connect between intercity trains when local trains do not run between London terminals, or between pairs of terminals where no local trains run. |
I do not remember the amount of transfers off the top of my head. However, as I have written in this PR the graph for the entirety of Finland is ~1.4gb (not the exact number but in the same ballpark) and when the transfers were badly configured with the |
A graph with 45 minutes walk and bicycle transfers is 15 GB just for Greater London. I am afraid it may grow to hundreds of GB if I attempt to pull this off in Hong Kong (where the stop density is multiple times higher than London). |
a68ad75
to
3ae6eed
Compare
@@ -434,7 +435,7 @@ public Collection<PathTransfer> getTransfersByStop(StopLocation stop) { | |||
return transfersByStop.get(stop); | |||
} | |||
|
|||
public Collection<PathTransfer> getTransfersByMode(StreetMode mode) { | |||
public List<PathTransfer> getTransfersByMode(StreetMode mode) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this method should be called findTransfers
according to the naming conventions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I renamed getTransfersByMode
to findTransfers
@t2gran can we run the speed tests against that? Do you know how to do it? |
Summary
This PR adds
StreetMode
information to transfers. This enables using mode-specific transfers during runtime.Notes:
PathTransfer
s with mode information, this could also be done by using mappings from modes to transfers.Issue
Resolves a TODO comment about adding
StreetMode
information in thePathTransfer
class.Related to #5875
Unit tests
Added a new test to
DirectTransferGeneratorTest
.Documentation
No documentation has been added.
Bumping the serialization version id
This changes how transfers are generated during the graph build.