-
Notifications
You must be signed in to change notification settings - Fork 28
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
panic: Sweep line misses event to be removed #17
Comments
Thanks for the detailed issue description! Without having started any debugging, this is most likely exactly the issue I'm working on currently. The background is: Internally the line segments of the sweep line are stored in a tree. When neighbouring line segments intersect or overlap, they get subdivided. This mutates the segments that are in the tree, and because of numerical instabilities the order can change. This is something that trees don't like at all ;). Think of a tree storing the values 1 < 2 < 3 and the value 1 gets mutated to 2.5 < 2 < 3. Now searching the tree for 2.5 fails. A solution that doesn't degrade performance is a bit tricky, but I have an idea. It will just take a little bit, because it probably requires to move away from the splay tree to a customized data structure I'm currently working on (a kind of a fixed-2-level B tree that allows to shuffle sort in these situations). |
Would it be possible to consistently break ties like that (same starting point, same direction) by another measurement (e.g. squared length, x component, ...)? |
Yes, such a mechanism is part of the solution. The problem is that the situation can change from before and after computing overlaps/intersections:
|
Ah good that you could make sense of it. I'm still undecided if it is a good idea to make the library generic in the underlying numeric type. If you look at the (most robust?) polygon clipping library Clipper, they have "solved" the problem by allowing only integers in a fixed range -- not even float support. Since I'm not aware of a library that is generic and achieves a decent level of robustness I initially thought the problem may be too tough for generics and restricting to a single numeric type may be necessary. Currently I'm a bit more optimistic: I think I understand the problem in the issue above and I have an idea how to solve it. If that's the case, the question whether the test cases are in f32 or f64 shouldn't matter much, because I should have equivalent f64 test cases. |
I thought the bigger numerical instabilities arising from smaller floats call for more robust code in the library - therefore it should be beneficial. eg dont calculate intersectionpoints - but reference the lines which intersection is meant to be the point + geometric predicates, or maybe one could use symbolic perturbations a'la Magalh ̃aes in 10.1145/3139958.3140001 to decide. Or maybe throw memory at the problem and remember the originally calculated angle with the subdivided segment (so there's no need to recalculate a new - slightly off - angle) You made me curious - how do you plan to solve this? |
Yes true, the issues will show up earlier, but they still fall into the same categories. Currently it simplifies the test code quite a bit by not having to write everything generically there as well.
I already went down that rabbit hole of "higher precision intersections" ;). As a baseline I was using an arbitrary precision library: This makes intersection computation roughly a factor 100 slower, and since there aren't any pure Rust libraries, it is maybe not ideal. The best solution I came up with is this. This basically takes Shewchuk's idea behind his "adaptive" non-overlapping representations (used in "robust predicates" implementations), and limits them to just the second order, i.e., whereas the original adaptive representations can take much higher orders (=> slow like the full arbitrary precision library), the implementation I went for always reduces the results back to second order (=> stays relatively fast). In my experiments having two
The "sweep line misses event to be removed" issue only has to do with the fact that the elements in the tree get mutated in a way that messes up the ordering. The obvious fix is to "re-sort" the elements if a mutation happened. Note that not a full sort is needed -- the fluctuations from mutations are tiny, so typically we'd only have to swap like two elements to repair the order again. As far as I can see, this is just a bit costly/ugly to do with trees though, because next/prev operations are O(log N), and bubble sorting them is a bit of a pain. I have created this special data structure for that purpose: It has O(1) next/prev and should be fairly straightforward to bubble sort, while still scaling reasonably well to the sizes of common sweep lines. |
Are there any updates on this? We're currently in the final strides of finishing a release of our software, but we're blocked by this crash. Is there anything we could do to help fixing this bug? |
Yeah, good question. Basically this issue is a just a manifestation of a fundamental issue of the approach, and resolving it probably requires quite some effort. My plan was:
Unfortunately, the steps are all tangled up to some degree, e.g. the breaking test blocking 2 probably needs the adjustments to |
Is there some kind of quick fix that can be applied until the real fix is implemented? |
If you are in a debug build and just want to get rid of the panic, you can comment out the |
I've implemented rank fixing for the new data structure and the respective calls there in subdivide_segments after possible mutations of the events. This all happens in a pretty naive(=unconditional) way, so I expect the performance to be suboptimal at the moment. It could possibly be even faster to entangle the code even more and put that rank_fixing burden into possible_intersection somehow. Right now I'm pretty happy that I've not broken any test-cases. Maybe @anlumo could try to integrate this into his release last minute. jk scnr On a more serious note I'd love to know how to benchmark the performance. I've just stumbled upon Edit: posted prematurely due to accidental CtrlReturn |
You can first checkout the master branch and run
there. Then switch to the feature branch and run
Then you should see the relative impact on each benchmark test case. Typically you can ignore the benchmark outlier warnings. Criterion (the benchmark tool) is a bit verbose in that regard. When in doubt, re-run the benchmarks (on my machine I'm actually observing quite some fluctuations from run to run and stopping background processes can help). |
We've already fixed the issue by switching to geo-clipper, which is pretty much a drop-in replacement with the advantage of not crashing. So, we're not in a hurry for this fix any more, although I'd prefer a pure Rust implementation if it's equivalent. |
OT: I should probably include (geo-)clipper in my comparison. How does it compare performance wise? |
It feels faster than geo-booleanop, although that might be related to the C++-library being built with optimizations even for Rust debug builds. I don't have any objective numbers, though. |
Has there been any progress on this issue in the last 3 months? |
Not from my side (my use case is on hold). |
my contribution should be pretty ready to merge, at least it works for me :) I'm using the bluenotes arraystump instead of the splaytree successfully, with better performance and less bugs, but still not completely free of any 'sweepline misses event's, but didn't care to debug further as it's good 'nuf for the moment (for me) edit: clarified what I'm talking about |
I tried to debug this issue. It seems like this line of In this case, this ends up confusing the splay tree (or even a BST) because the |
I don't think so, because the order in the tree is fundamentally messed up, i.e., the tree no longer maintains its invariant ( |
@bluenote10 In another local branch, I tried to fix the splay tree mutation issue. The mutation relevant to tree-ordering seems to happen only in However, the above mentioned bug still remained, and it was still causing the panic on the test case mentioned here. I think the fix to |
fwiw, this branch fixes the mutations as explained above. It includes the fix to comparator too; removing that brings back the panic. Yet to run any comprehensive benchmarks, but I'm also not a big fan of the SplayTree; does it perform poorly compared to say vanilla BTreeMap ? |
I vaguely remember that I had a few more test cases prepared related to some open issues, and I never could get them all to work. Unfortunately, quite some time has passed, and I cannot remember the details anymore. I still have several test cases lying around as untracked files in my working dir -- some of them were uncommitted because I couldn't get them to work. Here they are if it helps: uncommitted_test_cases.zip
Yes, I considered that as well, but benchmarks didn't look good, because it's quite an overhead to remove+insert on every subdivision. But perhaps for now that is the best option. Good performance isn't worth much if the algorithm isn't correct. However I was also never 100% sure if this actually gives full correctness, because if
Yeah, unfortunately the SplayTree is relatively slow, see my benchmarks here (expect for the "find recent" case which they are optimized for, but that doesn't seem to be very relevant in the Martinez context). |
I added all the tests the zip file. They all seem to pass now (had to tweak the equality check in tests to allow for permuted but equivalent components of multi-polygon, polygon-interiors, and allow shifts in polygon rings). Interestingly, this is still without the mutation fix. I suppose with arbitrary precision, I suggest we merge the #28 PR as it fixes an independent bug. |
to me it seemed that using f32 instead of f64 brought up various issues that f64 also had - but much earlier. |
* swapped out geo-booleanop through geo-clipper, because of issue 21re/rust-geo-booleanop#17 * made clippy happy * added mutually exclusive feature-flags "wasm-compatible" and "wasm-incompatible" the wasm-compatible variant includes the lib geo-booleanop that is currently in some cases not reliable the wasm-incompatible variant includes the lib geo-clipper seems to be more reliable, faster but not wasm-compatible * cargo fmt
* swapped out geo-booleanop through geo-clipper, because of issue 21re/rust-geo-booleanop#17 * made clippy happy * added mutually exclusive feature-flags "wasm-compatible" and "wasm-incompatible" the wasm-compatible variant includes the lib geo-booleanop that is currently in some cases not reliable the wasm-incompatible variant includes the lib geo-clipper seems to be more reliable, faster but not wasm-compatible * cargo fmt
using a new method to split up bounds fixed line crossing that could not be handled by the CDT fixed some CDT edges that were wrongfully determined as constrained edges added 3 tests locking dependencies for tag rename as_navmeshes as as_navmesh rename add_obstacle as queue_subtract adding wasm-compatible and wasm-incompatible feature flags using a few small changes from a fork of bvh2d (earlier error detection) separate navmesh islands should now be possible using different approach for passable/unpassable terrain (obstacles): using addition/set union and subtraction/set difference for more fine-grained control over the mesh trying to make holes (unpassable polygons) possible in the navmesh swapped out geo-booleanop through geo-clipper, because of issue 21re/rust-geo-booleanop#17
When aggregating some simple (convex, <6 vertices) polygons via repeated unions I get this panic:
I managed to create a .geojson fixture that provokes the panic:
The black polygon is my aggregate, the orange one the one I want to union.
Nudging the marked vertex unpanics the situation.
EDIT: added image, added fixture, more informations
The text was updated successfully, but these errors were encountered: