-
Notifications
You must be signed in to change notification settings - Fork 1.5k
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
(Almost) move access flag checks into weighting for CH preparation #1837
Conversation
# Conflicts: # core/src/main/java/com/graphhopper/routing/ch/CHRoutingAlgorithmFactory.java # core/src/main/java/com/graphhopper/routing/ch/PrepareCHEdgeIterator.java # core/src/main/java/com/graphhopper/routing/ch/PrepareCHEdgeIteratorImpl.java
while (true) { | ||
boolean hasNext = chIterator.next(); | ||
if (!hasNext) { | ||
return false; | ||
} else if (hasAccess()) { | ||
return true; | ||
} | ||
} |
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.
Here we are filtering edges within the iterator so we do not have to do it from the outside, the hasAccess method should ultimately only depend on the weighting
|
||
private boolean hasAccess() { | ||
if (isShortcut()) { | ||
return shortcutFilter.accept((CHEdgeIterator) chIterator); |
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.
Shortcut edges are special in that they only store a single weight and use their own kind of access flags, which will not go into weighting.
if (chIterator.getBaseNode() == chIterator.getAdjNode()) { | ||
return finiteWeight(false) || finiteWeight(true); | ||
} | ||
return shortcutFilter.fwd && finiteWeight(false) || shortcutFilter.bwd && finiteWeight(true); |
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 the essential change here: instead of checking the access flags we 'calculate' the weight
// todo: for #1776 move the access check into the weighting | ||
final boolean access = reverse | ||
? chIterator.getReverse(accessEnc) | ||
: chIterator.get(accessEnc); | ||
if (!access) { | ||
return Double.POSITIVE_INFINITY; | ||
} | ||
if (!needWeight) { | ||
return 0; | ||
} | ||
return weighting.calcWeight(chIterator, reverse, EdgeIterator.NO_EDGE); |
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 the only place we are calculating weights for CH preparation. The access flags are checked just before calcWeight
. In a future PR it should be easy to move this inside calcWeight
. There is one catch here: In many cases we do not need the weight and all we want to know is whether or not the weight is finite or not. Currently we can early exit here using the needWeight
parameter. For #1776 we could use something like weighting.isFinite(iter, reverse)
to cover this. For Germany, node-based I measured about 5% improvement with this check (obviously this depends on how complicated the weight calculation is). In the near future I think the edge weights will be calculated beforehand anyway, so no need to worry about this I think.
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 thought about this isFinite method too, but I do not think we should introduce this. Maybe as GHUtility but not for Weighting.
If this is provided from the Weighting it is suggested we can separate soft from hard constraints, but soft constraints can always degenerate into hard constraints e.g. too big factors in multiplications leading to an overflow into infinity. And so the isFinite method has to be always like Double.isFinite(calcWeight)
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 could make this clear in javadocs: isFinite would allow the creator of the weighting to benefit performance-wise but it would be wrong to 'break the contract' by returning something that does not correspond to Double.isInfinite(calcWeight)
. Since the performance difference (for the current FastestWeighting at least) is relatively small we can continue without the method though and it should not be a big issue to add it later when its needed.
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.
The problem is that such a method would be hard to implement. The soft constraints (outside of Weighting.isFinite but inside calcWeight) need a very sensitive range check and so even a combination of soft constraints shall never return infinity i.e. if isFinite==false, then calcWeight<infinity should be always true.
Because if this contract is violated we'll get the problems back that we currently have due to the access and weighting separation (#367, #1234).
Or we introduce a Weighting.couldBeInfinity method that can be better used as pre-check of calcWeight and used only for performance reasons.
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.
Or we introduce a Weighting.couldBeInfinity method that can be better used as pre-check of calcWeight and used only for performance reasons.
Yes I think this is what I meant: Be able to fully block an edge (for example we might have a boolean encoded value set to false for all highways/Autobahn for bike). I would rather call it isBlocked
?
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.
isBlocked
implies the separation that I would avoid. IMO we should call it couldBeBlocked
:) ... I see a bigger problem here as the "implementor" is required to keep isBlocked
and calcWeight
consistent. Where the past showed that this is hard to achieve even from ourselves.
And couldBeBlocked
could just use the current access bit stuff and then calcWeight
can still return infinity.
But well, as you said, we probably should not introduce this right now as it is really only a performance tweak. And other (more evil) techniques like caching should be tried too ;)
return new PrepareCHEdgeIteratorImpl(chGraph.createEdgeExplorer(DefaultEdgeFilter.inEdges(encoder)), weighting); | ||
return PrepareCHEdgeIteratorImpl.inEdges(chGraph.createEdgeExplorer(), weighting); |
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.
Here we got rid of in/out/allEdges
and use the unfiltered edge explorer instead
/** | ||
* @return true if this shortcut can be used in fwd direction. Do not call this method if {@link #isShortcut()} is false | ||
*/ | ||
boolean getFwdAccess(); | ||
|
||
/** | ||
* @see #getFwdAccess | ||
*/ | ||
boolean getBwdAccess(); |
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.
These methods are needed / used to get the access flags for shortcuts which are different to the access flags of normal edges, because they cannot be 'changed' by a configurable weighting (after the preparation has finished)
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.
These methods are only necessary for node-based CH at the moment, because for edge-based CH the edges are assumed to be always fwd==true and bwd==false, is my understanding correct here?
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 true and also pretty interesting. There are lots of shortcuts with fwd=bwd=true and this way we can store two weights with one double. The shortcuts either have the same weight for both directions or they are unidirectional, in both cases there is only one weight. For edge-based CH we do not do this optimization yet. Possibly it also will not work as well as it does for node-based because of turn costs at the skipped nodes (they usually depend on the travel direction), but of course doing this could be an improvement (50% less memory in the best (albeit very unlikely) case).
Here I stuck with the createIn/Out/AllEdgeExplorer. The alternative approach would be just using createEdgeExplorer (unfiltered) and then filter within the code that uses the iterator. I think this becomes messy because one still has to keep track of which (in/out/all) edges should be filtered. I did this up to the point where I am not using in/Out/AllExplorers to see where its going here: https://github.com/graphhopper/graphhopper/tree/filter_outside (#1839) |
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.
Nice!
The alternative approach would be just using createEdgeExplorer (unfiltered) and then filter within the code that uses the iterator.
Will have a look into this.
// todo: for #1776 move the access check into the weighting | ||
final boolean access = reverse | ||
? chIterator.getReverse(accessEnc) | ||
: chIterator.get(accessEnc); | ||
if (!access) { | ||
return Double.POSITIVE_INFINITY; | ||
} | ||
if (!needWeight) { | ||
return 0; | ||
} | ||
return weighting.calcWeight(chIterator, reverse, EdgeIterator.NO_EDGE); |
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 thought about this isFinite method too, but I do not think we should introduce this. Maybe as GHUtility but not for Weighting.
If this is provided from the Weighting it is suggested we can separate soft from hard constraints, but soft constraints can always degenerate into hard constraints e.g. too big factors in multiplications leading to an overflow into infinity. And so the isFinite method has to be always like Double.isFinite(calcWeight)
/** | ||
* @return true if this shortcut can be used in fwd direction. Do not call this method if {@link #isShortcut()} is false | ||
*/ | ||
boolean getFwdAccess(); | ||
|
||
/** | ||
* @see #getFwdAccess | ||
*/ | ||
boolean getBwdAccess(); |
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.
These methods are only necessary for node-based CH at the moment, because for edge-based CH the edges are assumed to be always fwd==true and bwd==false, is my understanding correct here?
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 work towards #1835 and #1776. With this PR it should be easy to move the access flag checks that occur during CH preparation inside the weighting in a later PR.