-
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
Vector2DOps Trait - Proposal #1025
Conversation
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 for the delay - I thought I submitted a review a long time ago, but it seems like it didn't go through (or I'm hallucinating).
geo/src/algorithm/mod.rs
Outdated
/// # Vector Operations for 2D coordinates | ||
/// | ||
mod vector_ops; | ||
pub(crate) use vector_ops::Vector2DOps; |
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 there a reason for this not to be public?
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.
Hi @michaelkirk no stress about the delay, thankyou for your time :)
No problems making it public. I was thinking of this as an internal tool, but some crate users may find it useful.
One benefit of making it private is to reduce the complexity of the API exposed to the user. For example, a user might want to find the length of a Line
, but miss the EuclideanLength
trait in the docs, and mistakenly assume they should use Vector2DOps::magnitude
:
use geo::algorithim::Vector2DOps;
my_line.delta().magnitude()
instead of
use geo::EuclideanLength;
my_line.euclidean_length()
On the other hand making it private means 'cargo doc' may hide it away and make it harder for other crate contributors to use it. The user also misses out on some useful functionality.
I think changing it to be public is probably the way to go, and follows the established pattern in algorithm/mod.rs
?
Maybe it can just not be re-exported at the top level or something?
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 in favor of making them public and treating them like all our other algorithm modules. They seem like generally useful methods.
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.
Ok sounds like a plan to me :)
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 there a reason that this is a draft, or would you be happy to move forward with merging it?
geo/src/algorithm/vector_ops.rs
Outdated
where | ||
Self: Sized, | ||
{ | ||
type NumericType: CoordNum + Send + Sync; |
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.
type NumericType: CoordNum + Send + Sync; | |
type Scalar: CoordNum + Send + Sync; |
We have, I think, similar associated types elsewhere in the codebase which we call Scalar
- can we use that naming here too to stay conventional, or is that not accurate in this case?
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.
Changed to Scalar
, that is the correct terminology 👍
geo/src/algorithm/mod.rs
Outdated
/// # Vector Operations for 2D coordinates | ||
/// | ||
mod vector_ops; | ||
pub(crate) use vector_ops::Vector2DOps; |
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 in favor of making them public and treating them like all our other algorithm modules. They seem like generally useful methods.
I think coordinate traits will take a while to get stabilized, so I wouldn't put too much thought in that work for your PR |
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.
Other than some final nits, this LGTM.
geo/src/algorithm/vector_ops.rs
Outdated
// crate dependencies | ||
use crate::coord; | ||
|
||
// private imports |
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.
// private imports |
nit: comment out of date since this is no longer private
geo/src/algorithm/vector_ops.rs
Outdated
assert_eq!(a.try_normalize(f64_minimum_threshold), None); | ||
|
||
// Large vector Pass; | ||
// Note: this fails because the magnitude calculation overflows to infinity |
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.
This comment seems wrong - this example passes, right?
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.
ah yes correct that one is meant to pass... looks like I botched a copy paste
geo/src/algorithm/vector_ops.rs
Outdated
|
||
// Large Vector Fail; | ||
// Note: This test uses the unstable float_next_up_down feature | ||
// let a = coord! { x: f64::sqrt(f64::MAX/2.0), y: f64::next_up(f64::sqrt(f64::MAX/2.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.
Not a big deal, but if you wanted to show it failing how about something simpler like:
// let a = coord! { x: f64::sqrt(f64::MAX/2.0), y: f64::next_up(f64::sqrt(f64::MAX/2.0)) }; | |
let a = coord! { x: f64::sqrt(f64::MAX/2.0 + 1.0), y: f64::next_up(f64::sqrt(f64::MAX/2.0 + 1.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.
Hi @michaelkirk; I think you meant to remove the nightly-only f64::next_up
from your suggestion and use + 1.0
instead?
The trouble is that for very large floats we don't know the next largest representable number so we can get the unexpected result;
assert_eq!(f64::MAX/2.0 + 1.0, f64::MAX/2.0); // passes unexpectedly
I have just spotted that there is already a dependency on the float_next_after
crate. I will use that instead.
I just had a bit of a noodle and it turns out the difference between f64::MAX/2.0
and the next highest representable number is something like 9.9e291
which is kinda confusing to think about.
println!("{:e}", f64::MAX / 2.0);
println!("{:e}", (f64::MAX/2.0).next_up());
println!("{:e}", (f64::MAX/2.0).next_up() - f64::MAX / 2.0);
// 8.988465674311579e307
// 8.98846567431158e307
// 9.9792015476736e291
Now I am wondering about the value of some of the asserts in test_normalize()
; Perhaps some are really testing the f64
implementation more than the try_normalize
and should be removed?
Maybe the best thing is to try add some notes to the documentation rather than try to check everything in the tests? Just thinking out loud here; There are basically 2 ways it can go wrong;
- The user has some massive vector (plausibly arising from some algorithm that sums many vectors) where the sum of the squares overflows before the square root. This situation could be mitigated by detecting large vectors and scaling down before doing the calculation.
- The user normalizes a really small vector and has chosen a bad value for the threshold causing a divide by zero or overflow (both result in f64::INFINITY).
- Actually on further investigation
coord!{x:0,y:1.0/f64::MAX}.try_normalize(0.0)
returnsNone
becausecoord!{x:0,y:1.0/f64::MAX}.magnitude()
underflows to0.0
😟. This is the same problem in reverse, and it means there are many valid small vectors that will return None despite the magnitude being greater than the user's chosen threshold. This situation could be mitigated by first scaling up the vector before normalizing.
- Actually on further investigation
Ok after that 45 min decent into madness I have an idea; it is easier to just check if the magnitude and the final result are finite after the calculation. There is no point guarding against invalid float operations since they do not panic anyway. Also this new version is less annoying to use because you don't need to provide a minimum threshold;
fn try_normalize(self) -> Option<Self> {
let magnitude = self.magnitude();
let result = self / magnitude;
if result.is_finite() && magnitude.is_finite() {
Some(result)
} else {
None
}
}
fn is_finite(self) -> bool {
self.x.is_finite() && self.y.is_finite()
}
I have also improved the doc comment in my next commit.
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 for reading through my unreasonable suggestion and coming up with something better!
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.
LGTM, but I'll give it a couple more days before merging in case anyone else wanted to weigh in.
geo/src/algorithm/vector_ops.rs
Outdated
|
||
// Large Vector Fail; | ||
// Note: This test uses the unstable float_next_up_down feature | ||
// let a = coord! { x: f64::sqrt(f64::MAX/2.0), y: f64::next_up(f64::sqrt(f64::MAX/2.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.
Thanks for reading through my unreasonable suggestion and coming up with something better!
bors r+ |
1025: Vector2DOps Trait - Proposal r=michaelkirk a=thehappycheese - [x] I agree to follow the project's [code of conduct](https://github.com/georust/geo/blob/main/CODE_OF_CONDUCT.md). - [x] I added an entry to `CHANGES.md` if knowledge of this change could be valuable to users. --- Proposal to define `Vector2DOps` trait and implement it for the `Coord` struct. Intention is to facilitate general purpose linear algebra operations such as 'dot product' etc all in one place. Most of these functions are are already implemented in various places throughout the crate or sometimes hard-coded in place. This would bring them all together. For example - `wedge_product`( aka cross/perp/exterior product) `geotypes::Line::determinant`, - `geotypes::Point::cross_prod` (which takes three arguments for some reason) etc. - Also used in the default implementation of Kernel orient2d, although this serves a different purpose as it returns an enum - `dot_product` `geotypes::Point::dot`, - `magnitude` in `Line::line_euclidean_length` - `magnitude_squared` hard coded ( [here](https://github.com/thehappycheese/geo/blob/9b477fc7b24fcb8731bd5e01d559b613e79ada0c/geo/src/algorithm/line_locate_point.rs#L61C9-L61C29)) - `normalize` ... interestingly it is hard to find an example of this operation in the existing crate. Possibly because it is generally best avoided due to floating point instability. For more discussion about motivation for this proposal, see [this comment](#935 (comment)) on PR #935 . Possibly the timing of this proposal is weird, I am sure that the `geotraits::CoordTrait` will affect how this should be implemented. I suspect it may allow the `Vector2DOps` to define default implementations? Note: - Currently `Vector2DOps` isn't used anywhere so it will trigger dead code warnings. - For an example that illustrates how I imagine it would be used in the OffsetCurve trait, see [this snippet](https://github.com/thehappycheese/geo/blob/d95420f1f8d6b0d2dd1e6d803fe3d3e2a2b3dd13/geo/src/algorithm/offset_curve/offset_line_raw.rs#L46C1-L51C1) from this PR #935 - Note that PR #935 is a separate, older branch, in that branch I was calling it `VectorExtensions`. In this PR I renamed it to `Vector2DOps` so that it is more similar to the existing `AffineOps` Many thanks for your time and any feedback you can provide Co-authored-by: thehappycheese <[email protected]>
Build failed: |
Can you please run |
Hi @michaelkirk, appologies for that, I ran cargo fmt again and I think it is fixed now in 12155aa I'm pretty sure it has been committed using LF and not CRLF. |
Bors try |
tryBuild failed: |
This is a spurious Clippy lint failure imo, because the suggested change: --- a/geo/src/algorithm/vector_ops.rs
+++ b/geo/src/algorithm/vector_ops.rs
@@ -111,7 +111,7 @@ where
fn try_normalize(self) -> Option<Self>;
/// Returns true if both the x and y components are finite
- fn is_finite(self) -> bool;
+ fn is_finite(&self) -> bool;
}
impl<T> Vector2DOps for Coord<T>
@@ -165,7 +165,7 @@ where
}
}
- fn is_finite(self) -> bool {
+ fn is_finite(self: &geo_types::Coord<T>) -> bool {
self.x.is_finite() && self.y.is_finite()
}
} Introduces an unnecessary borrow of If you annotate the trait fn instead: --- a/geo/src/algorithm/vector_ops.rs
+++ b/geo/src/algorithm/vector_ops.rs
@@ -111,6 +111,7 @@ where
fn try_normalize(self) -> Option<Self>;
/// Returns true if both the x and y components are finite
+ #[allow(clippy::wrong_self_convention)]
fn is_finite(self) -> bool;
} All should be well. |
bors try |
tryBuild failed: |
I am confused... There should be a 12th commit where I added the annotation, but it is not showing up in this PR. Is it because it happened while bors was doing something? |
I'm confused too. I don't think it was bors, and it's…somewhere: |
Can you pull your remote branch and then push again, just to be sure? I can see the commit on your remote, but it's now not in the PR for some reason. |
I cant find a way to trigger it manually. I tried the open pull request button but it just navigates to this existing PR. I can try commit some minor change to a comment or something and see if that triggers it to get included? Maybe I need to rebase it or something? It still says it can be merged tho. |
Yeah, try an inconsequential commit. |
Ah I clicked "Sync Fork" and that created a 13th commit... so far that hasn't been needed. I wonder why it was needed this time |
bors r=michaelkirk |
1025: Vector2DOps Trait - Proposal r=michaelkirk a=thehappycheese - [x] I agree to follow the project's [code of conduct](https://github.com/georust/geo/blob/main/CODE_OF_CONDUCT.md). - [x] I added an entry to `CHANGES.md` if knowledge of this change could be valuable to users. --- Proposal to define `Vector2DOps` trait and implement it for the `Coord` struct. Intention is to facilitate general purpose linear algebra operations such as 'dot product' etc all in one place. Most of these functions are are already implemented in various places throughout the crate or sometimes hard-coded in place. This would bring them all together. For example - `wedge_product`( aka cross/perp/exterior product) `geotypes::Line::determinant`, - `geotypes::Point::cross_prod` (which takes three arguments for some reason) etc. - Also used in the default implementation of Kernel orient2d, although this serves a different purpose as it returns an enum - `dot_product` `geotypes::Point::dot`, - `magnitude` in `Line::line_euclidean_length` - `magnitude_squared` hard coded ( [here](https://github.com/thehappycheese/geo/blob/9b477fc7b24fcb8731bd5e01d559b613e79ada0c/geo/src/algorithm/line_locate_point.rs#L61C9-L61C29)) - `normalize` ... interestingly it is hard to find an example of this operation in the existing crate. Possibly because it is generally best avoided due to floating point instability. For more discussion about motivation for this proposal, see [this comment](#935 (comment)) on PR #935 . Possibly the timing of this proposal is weird, I am sure that the `geotraits::CoordTrait` will affect how this should be implemented. I suspect it may allow the `Vector2DOps` to define default implementations? Note: - Currently `Vector2DOps` isn't used anywhere so it will trigger dead code warnings. - For an example that illustrates how I imagine it would be used in the OffsetCurve trait, see [this snippet](https://github.com/thehappycheese/geo/blob/d95420f1f8d6b0d2dd1e6d803fe3d3e2a2b3dd13/geo/src/algorithm/offset_curve/offset_line_raw.rs#L46C1-L51C1) from this PR #935 - Note that PR #935 is a separate, older branch, in that branch I was calling it `VectorExtensions`. In this PR I renamed it to `Vector2DOps` so that it is more similar to the existing `AffineOps` Many thanks for your time and any feedback you can provide Co-authored-by: thehappycheese <[email protected]>
Build failed: |
Now it's timing out. This is hilarious. |
bors retry |
1025: Vector2DOps Trait - Proposal r=michaelkirk a=thehappycheese - [x] I agree to follow the project's [code of conduct](https://github.com/georust/geo/blob/main/CODE_OF_CONDUCT.md). - [x] I added an entry to `CHANGES.md` if knowledge of this change could be valuable to users. --- Proposal to define `Vector2DOps` trait and implement it for the `Coord` struct. Intention is to facilitate general purpose linear algebra operations such as 'dot product' etc all in one place. Most of these functions are are already implemented in various places throughout the crate or sometimes hard-coded in place. This would bring them all together. For example - `wedge_product`( aka cross/perp/exterior product) `geotypes::Line::determinant`, - `geotypes::Point::cross_prod` (which takes three arguments for some reason) etc. - Also used in the default implementation of Kernel orient2d, although this serves a different purpose as it returns an enum - `dot_product` `geotypes::Point::dot`, - `magnitude` in `Line::line_euclidean_length` - `magnitude_squared` hard coded ( [here](https://github.com/thehappycheese/geo/blob/9b477fc7b24fcb8731bd5e01d559b613e79ada0c/geo/src/algorithm/line_locate_point.rs#L61C9-L61C29)) - `normalize` ... interestingly it is hard to find an example of this operation in the existing crate. Possibly because it is generally best avoided due to floating point instability. For more discussion about motivation for this proposal, see [this comment](#935 (comment)) on PR #935 . Possibly the timing of this proposal is weird, I am sure that the `geotraits::CoordTrait` will affect how this should be implemented. I suspect it may allow the `Vector2DOps` to define default implementations? Note: - Currently `Vector2DOps` isn't used anywhere so it will trigger dead code warnings. - For an example that illustrates how I imagine it would be used in the OffsetCurve trait, see [this snippet](https://github.com/thehappycheese/geo/blob/d95420f1f8d6b0d2dd1e6d803fe3d3e2a2b3dd13/geo/src/algorithm/offset_curve/offset_line_raw.rs#L46C1-L51C1) from this PR #935 - Note that PR #935 is a separate, older branch, in that branch I was calling it `VectorExtensions`. In this PR I renamed it to `Vector2DOps` so that it is more similar to the existing `AffineOps` Many thanks for your time and any feedback you can provide Co-authored-by: thehappycheese <[email protected]>
This PR was included in a batch that successfully built, but then failed to merge into main. It will not be retried. Additional information: Response status code: 422
{"message":"Changes must be made through the merge queue","documentation_url":"https://docs.github.com/articles/about-protected-branches"} |
I though we turned off the merge queue, but apparently not, and it's now conflicting with bors. The PR passes so I'm merging using the queue, then going for a little walk. |
Hooray, thanks for all the help to get it merged, I'm looking forward to working on the next part of the other offset curve PR 😄 |
CHANGES.md
if knowledge of this change could be valuable to users.Proposal to define
Vector2DOps
trait and implement it for theCoord
struct.Intention is to facilitate general purpose linear algebra operations such as 'dot product' etc all in one place.
Most of these functions are are already implemented in various places throughout the crate or sometimes hard-coded in place. This would bring them all together.
For example
wedge_product
( aka cross/perp/exterior product)geotypes::Line::determinant
,geotypes::Point::cross_prod
(which takes three arguments for some reason) etc.dot_product
geotypes::Point::dot
,magnitude
inLine::line_euclidean_length
magnitude_squared
hard coded ( here)normalize
... interestingly it is hard to find an example of this operation in the existing crate. Possibly because it is generally best avoided due to floating point instability.For more discussion about motivation for this proposal, see this comment on PR #935 .
Possibly the timing of this proposal is weird, I am sure that the
geotraits::CoordTrait
will affect how this should be implemented. I suspect it may allow theVector2DOps
to define default implementations?Note:
Vector2DOps
isn't used anywhere so it will trigger dead code warnings.VectorExtensions
. In this PR I renamed it toVector2DOps
so that it is more similar to the existingAffineOps
Many thanks for your time and any feedback you can provide