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

Multiple origin and destination candidates results in strange detours in unidirectional A* #3585

Merged
merged 22 commits into from Dec 31, 2022

Conversation

derolf
Copy link
Contributor

@derolf derolf commented Mar 29, 2022

  • Added tests for multiple origin and destination candidates to reproduce the issue -> tests fail
  • Fixed unidirectional A* to properly handle these cases -> tests pass

Issue

#3582
#1952

Tasklist

  • Add tests
  • Add #fixes with the issue number that this PR addresses
  • Update the docs with any new request parameters or changes to behavior described
  • Update the changelog
  • If you made changes to the lua files, update the taginfo too.

- Adding tests for mutlipe origin and destination candidates
- Fixing unidirectional A*
@derolf derolf changed the title 3582: Multiple origin and destination candidates Multiple origin and destination candidates results in strange detours in unidirectional A* Mar 29, 2022
@derolf
Copy link
Contributor Author

derolf commented Mar 29, 2022

Dear reviewers,

the basic idea is to wrap label creation into a lambda (add_label) in both SetOrigin and ExpandInner. add_label is always called to at least add the "normal" edge. If the edge is also a destination, add_label is called for each correlation on that destination, adjusting costs.

Cheers

Daniel

@derolf
Copy link
Contributor Author

derolf commented Mar 30, 2022

The test Standalone, NotThruPruning fails. I looked through my changes and can't figure out what's wrong there. Any idea?

EDIT: My fault, found the reason...

CHANGELOG.md Outdated Show resolved Hide resolved
}

/**
* Sets this edge as an origin.
Copy link
Member

Choose a reason for hiding this comment

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

nit: origin seems like the wrong word maybe? maybe you meant that it can be either and origin or destination since we have both forward and reverse search algorithms?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

An edge can be both origin and destination at the same time.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

btw "Sets this edge as an origin." is already part of the untouched code, see here:

Copy link
Member

Choose a reason for hiding this comment

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

sorry im just pointing out that the comment says "set this edge as an origin" but the code sets the destination bit. that seems contradictory though yes i understand that its possible an edge is both a destination and an origin for certain routes. really i just wonder why the comment isnt "sets this edge as a destination"

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah, my bad!!!! Sorry

@kevinkreiser
Copy link
Member

@dgearhart it looks like the pencilpoint tests are failing on this one with finding completely different routes? i just looked at ci and saw the logs and thats what it looked like. do you have a second to take a look? otherwise i can look maybe in a couple of days.

@derolf thanks very much for the massive refactor of these algorithms. i'm hoping that @dnesbitt61 and i can take a look in the next few days. anyone else who has time (maybe @purew ? ) would also be much appreciated!

@dgearhart
Copy link
Member

@dgearhart it looks like the pencilpoint tests are failing on this one with finding completely different routes? i just looked at ci and saw the logs and thats what it looked like. do you have a second to take a look? otherwise i can look maybe in a couple of days.

@kevinkreiser will not have any time today

@kevinkreiser
Copy link
Member

@derolf apologies its taken so long to get to this, but i just wanted to say its still on my radar. just have to appease the tests at this point.. bear with us

@nilsnolde nilsnolde mentioned this pull request Oct 2, 2022
@derolf
Copy link
Contributor Author

derolf commented Nov 7, 2022

Hey @kevinkreiser,

Any idea how to fix the tests?

Assuming I would fix them in the next days, would it be ready to get merged?

cheers

Daniel

@kevinkreiser
Copy link
Member

@derolf we were just starting to look at them last week to see what is going on. As soon as I can qualify the exact cause I'll post back to this issue. All I see at the moment is it takes a different path. It might be that the test itself should change

@derolf
Copy link
Contributor Author

derolf commented Nov 18, 2022

@nilsnolde @kevinkreiser Coverage went down a tiny bit. Any idea for an easy gain here?

@nilsnolde
Copy link
Member

Ah that's fine @derolf. Can happen with bigger refactors where lots of (apparently very well tested) code is removed. As for me, the more important thing is the patch diff and that looks as good as it can be.

Though I have a hard time wrapping my head around the overall decrease if the patch diff is so high. I could understand if the patch diff was like 84.9 or so, then I could reason that code was removed that was covered at a much higher degree. But with 96% coverage.. Anyways, maybe I understand those metrics wrong. Again, it's fine with me at least.

@derolf
Copy link
Contributor Author

derolf commented Nov 18, 2022

@nilsnolde Ship it!

@nilsnolde nilsnolde self-requested a review November 18, 2022 18:11
@nilsnolde
Copy link
Member

jep, we'll review it again in a bit. I'm pretty sure it's good though! was just saying it's fine to decrease coverage. thanks again and sorry for the long wait.

@nilsnolde nilsnolde mentioned this pull request Dec 19, 2022
Comment on lines +206 to +207
assert(opp_edge_id);
assert(opp_edge);
Copy link
Member

Choose a reason for hiding this comment

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

if we have the tile, it's not possible that we don't have the edge

nilsnolde
nilsnolde previously approved these changes Dec 26, 2022
Copy link
Member

@nilsnolde nilsnolde left a comment

Choose a reason for hiding this comment

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

Finally had some time to look into this. Looks good, thanks @derolf . I think the assert are redundant, other than that all good. Will go over it with @kevinkreiser tmrw, then we can merge.

Comment on lines +836 to +837
if (FORWARD ? costing_->AvoidAsDestinationEdge(edgeid, edge.percent_along())
: costing_->AvoidAsOriginEdge(edgeid, edge.percent_along())) {
Copy link
Member

Choose a reason for hiding this comment

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

I like the changes in this function:)

Comment on lines +704 to 708
// edge (check has to be done BEFORE we invert the edge if !FORWARD)
if (FORWARD ? costing_->AvoidAsOriginEdge(edgeid, percent_along)
: costing_->AvoidAsDestinationEdge(edgeid, percent_along)) {
continue;
}
Copy link
Member

Choose a reason for hiding this comment

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

very good

Copy link
Member

Choose a reason for hiding this comment

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

Hm, not so convinced anymore after all.. For reverse this might not do what we'd expect I think. Basically it seems this relies on the edge as it's being traversed by the algorithm, not its opposing. That exact edge might not even be in the avoid_edges set if the current mode isn't allowed on it. However, its opposing (the one we actually care about) might've been. That is to say, it's better than before, where we didn't look at avoided edges at all for the reverse tree, but might still need some more fiddling to get it really right, so we should leave a TODO.

@derolf
Copy link
Contributor Author

derolf commented Dec 27, 2022

Thanks for approving the PR. What would you like me to do before you merge the PR?

@kevinkreiser
Copy link
Member

I'd still like to test the pr. We need to run a suite of routes through it and look at the differences vs master. I'll do that and report back. I'd also like to reason about the changes myself i just haven't had the time. I started looking weeks ago but other things came up.

@nilsnolde
Copy link
Member

Maybe we can do that tmrw together if there’s enough time.

// Destinations, id and percent used along the edge
std::unordered_map<uint64_t, float> destinations_percent_along_;
// Mark if edge is a destination
std::unordered_multimap<valhalla::baldr::GraphId, std::reference_wrapper<const valhalla::PathEdge>>
Copy link
Member

Choose a reason for hiding this comment

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

im not too sure that we need a multimap here. the correlated edges can only have one copy of each unique edge id so you wont ever push in the same edge twice as a destination. unless im missing something here.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That's not correct. The map-matcher could match the same edge multiple times. Just think about a hard loop shaped like a U with the car between.

Copy link
Member

Choose a reason for hiding this comment

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

while it is true that map matching needs to handle this case, this algorithm has nothing to do with our map matching implementation. all our map matching code is in the meili namespace (and a little bit of it in thor/map_matcher thor/trace_*). this is because we do a one-to-many routing between map matching candidates and this algorithm only does a one-to-one routing.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I mean if the incoming destination set has the same edge multiple times in the list, then we would need to add complex logic for tie breaking. Using a multimap usually nicely handles that case without any other checks required.

Copy link
Member

Choose a reason for hiding this comment

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

yep sorry what im saying is that that cannot happen. the list is populated by this line:

for (const auto& edge : dest.correlation().edges()) {

that list in turn is populated by loki search and im just telling you that loki search will only have unique edge ids in the list. this is because the whole job of loki search is to find the closest point on a given edge and return that as a candidate. there can be only 1 closest point on a given edge per location. so im just saying that multimap is an unneeded complication. i can see you are using it to nicely handle the event that there would be the same id more than once but what im saying is that there will never be.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

One should not couple implementation details of two algorithms, esp. across public APIs.

So relying on the fact that LOKI behaves that way is not good because the A* could also be used in a different way.

Copy link
Member

Choose a reason for hiding this comment

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

one should probably also not constantly change the interface to those public APIs but i guess it depends on the guarantees one wants to provide with a given API.

since its inception the project has not really concerned itself with maintaining compatibility (commit to commit) between the APIs exposed in the headers. we pretty much only gaurantee the json API. in fact it is a long term goal to vastly reduce the scope of methods exposed in the library headers (a battle for another day.. or maybe slowly plugging away at it rather).

furthermore, i can tell you for a fact that other parts of thor assume that there are no duplicates in the list of a locations edge candidates (eg: https://github.com/valhalla/valhalla/blob/master/src/thor/triplegbuilder.cc#L411 if there were duplicates here it would randomly pick one candidate, not necessarily the right one)

anyway the whole reason i was telling you this was because i saw the multimap and i saw the call to equal_range and i was wondering: why is he protecting against this case that doesn't occur? which lead to me really just trying to do a polite: FYI you dont need to worry about this, it cant happen. but i think from your round about answers afterward i could understand that you may be calling this algorithm with your own set of edge candidates which may indeed have duplicates?

my point is i understand how one could put more than one correlation point for the same edge in the list, it just doesnt happen in any of the code here on github. if you have other designs elsewhere or future plans, best to just say that and shortcut the discussion 😄

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Nay, no plans right now, but could happen.

I did that change for the simple reason of my edge-cases-tuned brain :-)

@@ -441,6 +457,7 @@ class EdgeLabel {
uint64_t shortcut_ : 1;
uint64_t dest_only_ : 1;
uint64_t origin_ : 1;
uint64_t destination_ : 1;
Copy link
Member

Choose a reason for hiding this comment

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

this is convenient good call

@kevinkreiser
Copy link
Member

so we ran master and this branch on a 14k route file and the diff results were good for depart at. only 351 diffs total. we will run arrive by and also look at differences in performance. i'll post some of the screen shots of the differences so we can see what kinds of diffs we have. the routes i ran were with a 10m radius so that the logic to select different destination candidates would be triggered. looking good so far! thanks again for all the work @derolf

@kevinkreiser kevinkreiser mentioned this pull request Dec 30, 2022
@kevinkreiser
Copy link
Member

ok i've checked performance and as expected there is no impact (we are doing the same amount of unordered::finds as we were before basically and the lambda is basically free).

@nilsnolde and i looked at a bunch of the diffs and just about all of them make sense and are better results. there are a few where i'd say the results are worse and not intuitive but on the whole the ones we checked (maybe 20 manually reviewed) id say only a couple were noticeably worse.

algorithmically the change makes sense to me so i think we are good to merge.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants