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

(Almost) move access flag checks into weighting for CH preparation #1837

Merged
merged 10 commits into from
Dec 30, 2019

Conversation

easbar
Copy link
Member

@easbar easbar commented Dec 30, 2019

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.

@easbar easbar added this to the 1.0 milestone Dec 30, 2019
Comment on lines +63 to +70
while (true) {
boolean hasNext = chIterator.next();
if (!hasNext) {
return false;
} else if (hasAccess()) {
return true;
}
}
Copy link
Member Author

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);
Copy link
Member Author

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);
Copy link
Member Author

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

Comment on lines +94 to +104
// 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);
Copy link
Member Author

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.

Copy link
Member

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)

Copy link
Member Author

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.

Copy link
Member

@karussell karussell Dec 30, 2019

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.

Copy link
Member Author

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 ?

Copy link
Member

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 ;)

Comment on lines -62 to +60
return new PrepareCHEdgeIteratorImpl(chGraph.createEdgeExplorer(DefaultEdgeFilter.inEdges(encoder)), weighting);
return PrepareCHEdgeIteratorImpl.inEdges(chGraph.createEdgeExplorer(), weighting);
Copy link
Member Author

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

Comment on lines +46 to +54
/**
* @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();
Copy link
Member Author

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)

Copy link
Member

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?

Copy link
Member Author

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).

@easbar
Copy link
Member Author

easbar commented Dec 30, 2019

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)
For now I like the approach in this PR better.

Copy link
Member

@karussell karussell left a 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.

Comment on lines +94 to +104
// 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);
Copy link
Member

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)

Comment on lines +46 to +54
/**
* @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();
Copy link
Member

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?

Copy link
Member

@karussell karussell left a comment

Choose a reason for hiding this comment

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

👍

@easbar easbar merged commit 03dd6d4 into master Dec 30, 2019
@easbar easbar deleted the prepare_ch_update branch December 30, 2019 21:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants