-
Notifications
You must be signed in to change notification settings - Fork 199
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
Avoid atan2
in minimum_rotated_rect
#1094
base: main
Are you sure you want to change the base?
Conversation
No benchmarks yet, and the output might be less precise, not sure if we want to merge this, but I wanted to put the code out. |
let (mut min_x, mut max_x) = (<T as Bounded>::max_value(), <T as Bounded>::min_value()); | ||
let (mut min_y, mut max_y) = (<T as Bounded>::max_value(), <T as Bounded>::min_value()); | ||
for point in hull.exterior().points() { | ||
let x = point.y() * edge.1 + point.x() * edge.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.
Herbie thinks you can improve the accuracy of these two statements using fma:
let x = point.x().mul_add(edge.0, (point.y() * edge.1));
let y = point.y().mul_add(edge.0, (-point.x() * edge.1));
Does it make any difference?
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.
Ooh, good idea, forgot about FMA.
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.
Tried it, slightly better but still no luck :(.
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.
Bah.
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.
Looks like I can still extract one ULP or so by dividing by norm
after the multiplications. Reverting FMA still doesn't fix those two vertices.
left: LineString([Coord { x: 1.7000000000000002, y: 24.6 }, Coord { x: 14.649854163628412, y: 25.15341257109523 }, Coord { x: 14.400000000000002, y: 31.000000000000004 }, Coord { x: 1.4501458363715911, y: 30.446587428904774 }, Coord { x: 1.7000000000000002, y: 24.6 }])
right: LineString([Coord { x: 1.7000000000000004, y: 24.600000000000005 }, Coord { x: 14.64985416362841, y: 25.153412571095235 }, Coord { x: 14.400000000000002, y: 31.000000000000004 }, Coord { x: 1.4501458363715916, y: 30.446587428904774 }, Coord { x: 1.7000000000000004, y: 24.600000000000005 }])
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.
Sorry to resurrect an old PR, but is herbie something we should consider adding to our CI lint?
I'd propose it should be non-failing, but advisory.
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.
If only! The Herbie lint in Rust hasn't worked since sometime in 2016 when the lint framework got rewritten, and nobody has had the energy to resurrect it. I actually asked about it a couple of years ago on URLO and everyone (including one of the Herbie authors) was enthusiastic, but it was clear that I was gonna be on my own. I generated the suggestion in this PR by manually writing it out and feeding it to the online tool.
bf8ea2a
to
6f9708d
Compare
(14.649854163628408, 25.153412571095235), | ||
(1.7000000000000006, 24.6), | ||
(1.7000000000000004, 24.600000000000005), | ||
(14.64985416362841, 25.153412571095235), |
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.
Maybe you noticed this but it looks like the y
value for the 2nd and 4th point have been swapped. 😕
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 points have been swapped. I think the vertices are in the reverse order.
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.
Oh right, I stared at it too long and confused myself. 😄
Is the new ordering correct or is that something that needs to be fixed yet?
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.
Is the new ordering correct or is that something that needs to be fixed yet?
It's the same rectangle in the opposite winding order (P4 P3 P2 P1 P4 instead of P1 P2 P3 P4 P1). I can probably match the order, but only up to the starting vertex, e.g. P2 P3 P4 P1 P2.
@lnicola Can you add more context to the description of the PR (or in a comment) about why we're avoiding |
(This was all my idea). Atan2 is a comparatively very expensive operation (around 100 cpu cycles on x86, vs around 20 for something like sqrt). Using this approach (inspired by the approach from https://gis.stackexchange.com/a/22934) avoids that. Unfortunately, it's currently suffering from floating point precision issues. |
Do we see any path forward with the precision issues here? If not, @lnicola what do you think about converting this to a draft PR or closing it? |
I finally got to run some benchmarks (on the plain version, not the one using FMA):
So it's about 33% faster, which is noticeable, but not earthshaking. As for the precision issue, I can't prove it, but I suspect the new method is more precise in some cases and less in others. Looking at the numbers, they're something like:
The only reason I suspect the new method is less accurate is that one vertex which is part of the input. Anyway, I don't know how to improve on this, but I don't insist on merging it either, so we can close this if nobody wants it. |
From a practical perspective MRR calculation tends to be something done at scale (e.g. building footprints in a city etc), so a 33 % improvement on something that might take 10 seconds is pretty respectable. I'm gonna try some more comparisons with JTS and GEOS tomorrow and see whether it's a consistent accuracy issue, although it's hard to see what else we could do without re-introducing complexity. |
input:
JTS (
GEOS (3.12.0)
|
Oh but wait, GEOS had a bug until 3.12.1: I can't easily access 3.12.1, anyone else got it? |
I have 3.12.1, can you send me some code? |
SELECT ST_AsText(ST_OrientedEnvelope('MULTIPOINT ((3.3 30.4), (1.7 24.6), (13.4 25.1), (14.4 31.0), (3.3 30.4))')); |
Same as before:
|
It's exactly the same output as with 3.12.0. |
Yeah I just saw 🤦♂️. Did you run it via PostGIS or using the cpp function? |
Using |
CHANGES.md
if knowledge of this change could be valuable to users.