-
Notifications
You must be signed in to change notification settings - Fork 1.4k
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
Round-to-nearest support for intervals #5510
Conversation
Thanks for the PR, this is something that was on my TODO list but I did not find time to do it. We should add some cmake machinery so that the there is an option to allow to not change the rounding mode + do not add the |
Number_types/include/CGAL/FPU.h
Outdated
#define CGAL_IA_SUB(a,b) CGAL_IA_FORCE_TO_DOUBLE(CGAL_IA_STOP_CPROP(a)-(b)) | ||
#define CGAL_IA_MUL(a,b) CGAL_IA_FORCE_TO_DOUBLE(CGAL_IA_STOP_CPROP(a)*CGAL_IA_STOP_CPROP(b)) | ||
#define CGAL_IA_DIV(a,b) CGAL_IA_FORCE_TO_DOUBLE(CGAL_IA_STOP_CPROP(a)/CGAL_IA_STOP_CPROP(b)) | ||
#ifdef CGAL_ALWAYS_ROUND_TO_NEAREST |
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.
IMO, if we are adding an extra case, we should simply assume that we do not know what the rounding mode is and not rely on to-nearest. I'm in favor of something like CGAL_NO_FPU_ROUNDING_MODE_CHANGE
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 guess, this implies more changes but that should be the way to go.
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.
It's an interesting point.
I think it's difficult to predict correct behavior with all existing and future rounding modes.
So I'd avoid a claim like CGAL_NO_FPU_ROUNDING_MODE_CHANGE.
But something like CGAL_CONSERVATIVE_INTERVALS might describe what's actually happening in this change, and then you could decide if that covers whatever floating point modes you care about or not.
I also notice that there are systems other than interval which care about floating point mode, and it's not clear to me that there will be a generic fix for all of them.
In which case CGAL_ALWAYS_ROUND_TO_NEAREST would be the right kind of flag to tell them about the particular environment they're operating in -- maybe some would need a CGAL_ALWAYS_ROUND_UPWARD, etc
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 don't think so. I think as long as you use nextafter()
for the upper and -lower bound of the interval you are safe side no matter how the value was rounded
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.
For intervals, certainly -- but CGAL_NO_FPU_ROUNDING_MODE_CHANGE seems to make a more universal claim that everything in CGAL will always work without changing rounding mode.
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.
AFAIK, this is the only place where is is 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.
Static filters do require round-to-nearest AFAIK (the bounds would be worse with arbitrary rounding), not sure if that's what you are talking about.
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.
Hmm, I see a number of cases that aren't clear to me.
Bounding_volumes/include/CGAL/Approximate_min_ellipsoid_d.h -- toward-zero and upward
Apollonius_graph_2/include/CGAL/Apollonius_graph_2/compare_quadratic.h -- upward
Number_types/include/CGAL/leda_bigfloat.h -- upward
Mesh_3/include/CGAL/Mesh_3/Robust_intersection_traits_3.h -- to nearest
Skin_surface_3/include/CGAL/Skin_surface_base_3.h -- to nearest
Straight_skeleton_2/include/CGAL/Straight_skeleton_2/Straight_skeleton_builder_traits_2_aux.h -- to nearest
Polynomial/test/Polynomial/Polynomial_traits_d_Residue.cpp -- upward
Polynomial/test/Polynomial/Polynomial_using_core.cpp -- upward
Polynomial/test/Polynomial/resultant.cpp -- upward
Polynomial/test/Polynomial/Polynomial_using_leda.cpp -- upward
Convex_hull_3/include/CGAL/convex_hull_3.h
Some of these don't seem to be part of filters, e.g., Approximate_min_ellipsoid.
My suggestion is to require explicit opt-in to any forced rounding mode.
i.e. If we use CGAL_ALWAYS_ROUND_TO_NEAREST then we can have CGAL_IA_SETFPCW throw an exception (or a compile-time error) when something tries to use a different mode, which will require systems to explicitly opt into CGAL_ALWAYS_AROUND_TO_NEAREST.
This should minimize surprising behavior, and allow gradual adoption, and allow alternate implementation for particular modes.
On the other hand, CGAL_NO_FPU_ROUNDING_MODE_CHANGE seems like it might require code to dynamically dispatch on the current rounding mode in the worst case, since it would need to operate in any existing or future rounding mode.
In any case, I'm operating from a position of ignorance, so maybe I'm being overly conservative -- but please make sure you're confident that you've considered all of the possibilities. :)
template <bool Protected = true> struct Protect_FPU_rounding; | ||
|
||
template <> | ||
struct Protect_FPU_rounding<true> | ||
{ | ||
Protect_FPU_rounding(FPU_CW_t r = CGAL_FE_UPWARD) | ||
Protect_FPU_rounding(FPU_CW_t r = CGAL_FE_PROTECTED) |
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 protector should not do anything in the new mode. The partial specialization for the protected mode could be hidden behind the macro.
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 think we should consider testing that we are using the expected rounding mode, and error otherwise.
// TODO: Alternative for computing CGAL_IA_SQRT_DOWN(d.inf()) exactly | ||
// without changing the rounding mode: | ||
// - compute x = CGAL_IA_SQRT(d.inf()) | ||
// - compute y = CGAL_IA_SQUARE(x) | ||
// - if y==d.inf() use x, else use -CGAL_IA_SUB(CGAL_IA_MIN_DOUBLE,x) | ||
#ifdef CGAL_ALWAYS_ROUND_TO_NEAREST |
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.
#elif
is usually nicer than nested #if
.
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'm happy to change it, but was concerned that the context of the comment about the alternative approach would become confused.
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 think this is obsoleted by the abstraction into IA_sqrt_toward_zero.
@@ -347,16 +346,32 @@ inline double IA_bug_sqrt(double d) | |||
// With GCC, we can do slightly better : test with __builtin_constant_p() | |||
// that both arguments are constant before stopping one of them. | |||
// Use inline functions instead ? | |||
#define CGAL_IA_ADD(a,b) CGAL_IA_FORCE_TO_DOUBLE((a)+CGAL_IA_STOP_CPROP(b)) | |||
#define CGAL_IA_SUB(a,b) CGAL_IA_FORCE_TO_DOUBLE(CGAL_IA_STOP_CPROP(a)-(b)) | |||
#define CGAL_IA_MUL(a,b) CGAL_IA_FORCE_TO_DOUBLE(CGAL_IA_STOP_CPROP(a)*CGAL_IA_STOP_CPROP(b)) |
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.
You dropped CGAL_IA_FORCE_TO_DOUBLE for the normal case, that looks wrong.
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, I think you're right -- I didn't look deep enough into all of the cases it handles.
How about putting that into the other branch of CGAL_IA_UP?
Or is that getting too overloaded?
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.
Putting it in the other branch of CGAL_IA_UP sounds fine.
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.
Done
Number_types/include/CGAL/FPU.h
Outdated
#define CGAL_IA_MUL(a,b) CGAL_IA_FORCE_TO_DOUBLE(CGAL_IA_STOP_CPROP(a)*CGAL_IA_STOP_CPROP(b)) | ||
#define CGAL_IA_DIV(a,b) CGAL_IA_FORCE_TO_DOUBLE(CGAL_IA_STOP_CPROP(a)/CGAL_IA_STOP_CPROP(b)) | ||
#ifdef CGAL_ALWAYS_ROUND_TO_NEAREST | ||
inline double CGAL_IA_UP(double d) |
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.
Since it isn't a macro and it is already in namespace CGAL, you don't need to prefix it with CGAL and write it in all caps. I see functions called IA_opacify or IA_force_to_double nearby.
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.
Thanks -- I've changed to IA_up.
Please let me know if you have a better name.
Number_types/include/CGAL/FPU.h
Outdated
} | ||
#define CGAL_IA_SQRT(a) \ | ||
CGAL_IA_FORCE_TO_DOUBLE(CGAL_BUG_SQRT(CGAL_IA_STOP_CPROP(a))) | ||
CGAL_IA_UP(CGAL_BUG_SQRT(CGAL_IA_STOP_CPROP(a))) |
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.
It may be slightly confusing that in the new mode CGAL_IA_SQRT means up(sqrt) while in the old mode, it means sqrt with dynamic rounding, and it is actually used once with downward rounding (the other operations are only ever used with upward rounding).
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've abstracted IA_sqrt_up and IA_sqrt_toward_zero.
It's a little awkward because of scoping.
Let me know what you think.
You mentioned benchmarks in the issue, could you post some benchmark results, explaining exactly what you tested? It would be interesting to know how this compares to current intervals, and how it compares to not using intervals at all. |
I just did a simple test which produced a sphere, then subtracted a half size sphere incrementally around the equator at each 360 / 7 degrees. https://imgur.com/a/ucZu3cl The time taken with this approach was about 65% of the time taken without intervals, which is a significant improvement, but not as much as hoped. It looks like the overhead of supporting exceptions was the main reason that it wasn't significantly faster. I looked into the possibility of replacing exceptions within CGAL, but it seems like it may be infeasible. I was concerned about this, but it looks like llvm has added support for native wasm exceptions, and these should become available via emscripten in a few months -- hopefully with this it should become much faster. I haven't benchmarked this change in x86, but can do so if you like -- under wasm I don't think we can produce a meaningful comparison. |
Yes, I was thinking of a bench on x86_64. It would allow a comparison with the current code, and it would give some idea of what to expect compared to no-intervals once exceptions are properly supported on wasm. |
I'm probably missing something completely obvious, but I don't see any obvious way to run a benchmark suite. Should I run some ad hoc benchmarks (if so, which packages do you think would be most useful)? |
Indeed, there is no benchmark suite. Some of the per package benchmarks are outdated and don't even compile... |
I think in CGAL we should treat benchmarks directories as demos directories:
That would at least force us to update the code of the benchmarks in case of breaking changes. (Cc @CGAL/geometryfactory) |
Well, I'm having trouble finding a significant difference. I couldn't find a reasonable benchmark, so I adapted corefinement_mesh_union to run
time ./corefinement_mesh_union real 0m23.875s time ./corefinement_mesh_union_nearest real 0m23.757s time ./corefinement_mesh_union real 0m23.048s time ./corefinement_mesh_union_nearest real 0m23.030s I then went and broke FPU.h inside a CGAL_ALWAYS_ROUND_TO_NEAREST just to make sure corefinement_mesh_union_nearest was using it. Let me know if you have a better idea for measuring it, but it doesn't seem like there's much of a hit. |
I am not sure how you tested exactly, but by default, we have CGAL_USE_SSE2 which enables a different code path that avoids CGAL_IA_ADD, etc. By the way, the combination CGAL_USE_SSE2 and CGAL_ALWAYS_ROUND_TO_NEAREST is likely broken in your branch. Anyway, running So it may still help to filter rationals, but the difference is significant. |
Ah, you're absolutely right. time ./corefinement_mesh_union_nearest real 0m30.595s $ time ./corefinement_mesh_union Union was successfully computed real 0m24.609s $ time ./corefinement_mesh_union_nearest real 0m32.082s $ time ./corefinement_mesh_union Union was successfully computed real 0m23.572s So, CGAL_ALWAYS_ROUND_TO_NEAREST seems to take about 130% as long for this. That makes much more sense. Thanks. :) |
Could be, since it touches those files, although it wasn't supposed to affect that target. By the way, is there any non-x86_64 platform in the testsuite? At least one arm (32 or 64) or x86 (32) would be nice to check that the path without CGAL_USE_SSE2 is not broken. |
We have a Linux Fedora running 32 bits, in the testsuite, but it seems to show up quite late in the day. We also have a new Apple M1 machine (ARM64) running MacOS BigSur. But that one seems to be off for about a week. I will check with @maxGimeno... |
I updated master from upstream and merged into a sub-branch to test. On my machine, at least, both Interval_nt and Interval_nt_nearest pass. Would it be difficult to run the test suite with just this branch patched in? |
I have tested this PR in isolation on my x86_64 Apple machine and I reproduce the error from the test suite. So it is not caused by merging with other branches. Both tests
in the file It is not a solution obviously but rather an idea of where to look to fix the actual error. Please let me know if more testing on my platform should be done. |
Number_types/include/CGAL/FPU.h
Outdated
return (d > 0.0) ? nextafter(std::sqrt(d), 0.) : 0.0; | ||
#else | ||
FPU_set_cw(CGAL_FE_DOWNWARD); | ||
double i = (d > 0.0) ? CGAL_BUG_SQRT(CGAL_IA_STOP_CPROP(d)) : 0.0; |
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.
Did you lose CGAL_IA_FORCE_TO_DOUBLE somewhere?
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, I think so.
I went over the substitutions manually and have included them in the code as temporary notes.
I found one CGAL_IA_FORCE_TO_DOUBLE that looks like it was omitted.
Thanks for isolating the issue. I went over the code and I've put in a tentative fix, and added some temporary notes. Please let me know how it goes on the x86_64 Apple target. |
Ok, now, it seems to work locally on my x86_64 machine. Both tests succeed. |
successfully tested in https://cgal.geometryfactory.com/CGAL/testsuite/results-5.3-Ic-98.shtml |
With -DCGAL_ALWAYS_ROUND_TO_NEAREST on master, the test Interval_nt_new.cpp fails because of _test_algebraic_structure.h |
I guess the test expectations should consider CGAL_ALWAYS_ROUND_TO_NEAREST? |
I think we should move forward and try to make |
What about Fedora-rawhide ? It tests almost everything and it is usually all green. |
Summary of Changes
This adds support for using round-to-nearest for intervals, as an alternative to round-to-infinity, suitable for use with WASM.
Note: I'm a bit unclear on how this ought to be best organized for CGAL -- please let me know what would fit better.
Release Management