Results change with sorting order for IntersectNodes that share the same X&Y? #126
Replies: 3 comments 13 replies
-
Hi Alexis. Firstly, good luck with your C port. Please let me know f you get it working reliably as I may well display a link to it from here. WRT differing results with stable vs unstable IntersectNode sorts, I'm OK with that as long as there's no significant difference in solution coverage (filling). If there is a significant difference, then this suggests there's a bug somewhere that needs fixing. Cheers A. |
Beta Was this translation helpful? Give feedback.
-
Hi Angus, thanks for the quick reply. Interesting. I think I see what you mean by "no significant difference in solution coverage"... Between std::sort and std::stable_sort, I can see a bunch of vertices are missing from a contour, but then they appear to have been stitched to another nearby contour instead. That would be fine, I'll keep an eye out if it's always the case. The C code seems to be working reliably (results are identical to Clipper2 if I switch your sorts to std::stable_sort). Optimization is next, along with seeing how CUDA-friendly it can get. It's already a little faster than the C++ version, thanks to details like switching to a radix sort (especially when millions of items are involved), which you could also certainly do in C++/Delphi/others. Thanks! |
Beta Was this translation helpful? Give feedback.
-
Hi Angus, I'm posting under the same discussion as it's still mostly the same topic. I think we have established that the order of intersections sharing the same X and Y slightly alters the results, but it's (probably) inconsequential. Now, is it possible that the sorting order on X (when Y is equal) is also irrelevant, as long as the Y ordering is respected? I feel like I now understand the whole code, and my current understanding tells me that it should never matter. (And that makes a big difference: radix sort on only 64 bits instead of 128, or a branchless merge sort that can use conditional move instructions) I'm still crunching on optimization for the C/CUDA rewrite/port as it would be used in a time-sensitive context. I am a bit annoyed to see bug fixes being committed, which I now also have to fix, but that's life ;) You may still be hunting/fixing bugs, but... here are some optimizations done in the C rewrite:
With a random vertex soup (not a realistic scenario), >50% of the time is spent in BuildIntersectList(), so I want to see if anything can be done about that. Something really bugs me about building, sorting and flushing all the intersection nodes at every scanbeam... Any thoughts? Thanks! |
Beta Was this translation helpful? Give feedback.
-
Hi Angus, thanks for the nice library!
I have noticed that when you have multiple IntersectNodes sharing the same X and Y values in your ->intersect_nodes_ list, then the sorted order for these is undefined (sort isn't stable and IntersectListSort() doesn't express a preference), but the results differ depending on the order being picked for these IntersectNodes sharing the same X and Y values.
The output appears significantly different, like a whole contour being absent.
I wonder, could it reflect a deeper problem? Intuitively, if your algorithm doesn't care about the sorting order of intersections sharing the same X and Y, then the result shouldn't change with a different order for these (you can switch between std::sort and std::stable_sort and occasionally catch a difference).
In case you are curious why I would bother with these internal details, I'm rewriting the code in C, as a preamble before seeing if I can reasonably make it SIMD-friendly and malloc-free, for a later port to CUDA (GPUs). If my sorts (radix, merge, etc.) pick a different order than std::sort for IntersectNodes with identical X and Y, then final results differ, and red flags are raised (I use your library as reference implementation, expecting identical results).
If you need more details, I'm basically doing:
#define FACTORMUL (1000)
srand( 3 );
for( i = 0 ; i < 512 ; i++ )
{
x = rand() & 63;
if( !( i & 1 ) )
y = rand() & 63;
input0.push_back( BuildPoint64( x*FACTORMUL, y*FACTORMUL ) );
}
subject.push_back( input0 );
clipper.AddSubject( subject );
clipper.Execute( Clipper2Lib::ClipType::Union, Clipper2Lib::FillRule::Negative, solution, open_paths );
And, for example, Clipper2's final results differ depending if:
std::sort(intersect_nodes_.begin(), intersect_nodes_.end(), IntersectListSort);
or
std::stable_sort(intersect_nodes_.begin(), intersect_nodes_.end(), IntersectListSort);
was called in clipper.engine.cpp
The sample above assumes the Linux glibc's rand() implementation, but I'm sure you can reproduce that easily just by picking a different srand() seed until results change.
Thanks for your time, let me know if I can help with anything!
Beta Was this translation helpful? Give feedback.
All reactions