Skip to content
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

Rewrite Extremes algorithm to iteratively find the extreme coordinates using CoordsIter #592

Merged
merged 11 commits into from
Jan 10, 2021

Conversation

frewsxcv
Copy link
Member

@frewsxcv frewsxcv commented Jan 2, 2021

  • I agree to follow the project's code of conduct.
  • I added an entry to CHANGES.md if knowledge of this change could be valuable to users.

  • Added
    • geo::algorithm::extremes::Extremes (trait)
    • geo::algorithm::extremes::Outcome (struct)
    • geo::algorithm::extremes::Extreme (struct)
  • Removed
    • geo::algorithm::extremes::ExtremeIndices (trait)
    • geo::algorithm::extremes::ExtremePoints (trait)
    • geo::Extremes (struct)

The Extremes algorithm is now based on CoordsIter instead of ConvexHull. The trait now runs 6x faster than the previous implementation:

Screen Shot 2021-01-06 at 12 26 13 PM

This speeds up any algorithms that incorporate Extremes, like Polygon to Polygon distance:

Screen Shot 2021-01-06 at 12 28 59 PM

bors bot added a commit that referenced this pull request Jan 4, 2021
593: Migrate CoordsIter::T to be an associated type. r=michaelkirk a=frewsxcv

- [x] I agree to follow the project's [code of conduct](https://github.com/georust/geo/blob/master/CODE_OF_CONDUCT.md).
- [x] I added an entry to `CHANGES.md` if knowledge of this change could be valuable to users.
---

# Previously

With the previous design, this would compile:

```rust
impl<'a> CoordsIter<'a, f32> for Polygon<f32> {
    ...
}

impl<'a> CoordsIter<'a, f64> for Polygon<f32> {
    ...
}
```

Even though this is not what our implementations actually look like, to the compiler it's possible for there to be multiple implementations of `CoordsIter` for `Polygon<f32>`.

This was problematic when [I started rewriting](#592) our `ExtremeIndices` trait to be generic over any type that implements `CoordsIter`:

```rust
impl<'a, T, G> ExtremeIndices for G
where
    T: CoordinateType,
    G: CoordsIter<'a, T>,
{
    fn extreme_indices(&self) -> Result<Extremes, ()> {
        ...
    }
}
```

This is the compilation error:

```
the type parameter `T` is not constrained by the impl trait, self type, or predicates
```

The issue is that if someone writes this with this new `ExtremeIndices` implementation:

```rust
let p: Polygon<f32> = polygon![...];
p.extreme_indices()
```

The compiler doesn't know which implementation to choose: `impl<'a> CoordsIter<'a, f32> for Polygon<f32>` or `impl<'a> CoordsIter<'a, f64> for Polygon<f32>`.

So we need a way to tell the compiler we only have _one_ implementation of `CoordsIter` per type. The root issue being that there is a free generic parameter in the `CoordsIter` definition.

# Now

To fix this, we will move the `T` type parameter to an associated type. Writing this would be a compiler error:

```rust
impl<'a> CoordsIter<'a> for Polygon<f32> {
    type Scalar = f32;
    ...
}

impl<'a> CoordsIter<'a> for Polygon<f32> {
    type Scalar = f64;
    ...
}
```

```
conflicting implementations of trait `algorithm::coords_iter::CoordsIter<'_>` for type `geo_types::Polygon<f32>`
```

This unblocks #592

Co-authored-by: Corey Farwell <coreyf@rwell.org>
bors bot added a commit that referenced this pull request Jan 5, 2021
594: Add `CoordsIter::exterior_coords_iter` r=michaelkirk a=frewsxcv

- [x] I agree to follow the project's [code of conduct](https://github.com/georust/geo/blob/master/CODE_OF_CONDUCT.md).
- [x] I added an entry to `CHANGES.md` if knowledge of this change could be valuable to users.
---

This will get incorporated into #592

Fixes #542

~Based on and blocked by #593

Co-authored-by: Corey Farwell <coreyf@rwell.org>
///
/// assert_eq!(extremes.y_max.index, 2);
/// assert_eq!(extremes.y_max.coord.x, 1.);
/// assert_eq!(extremes.y_max.coord.y, 2.);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It just occurred to me, but this is effectively the same thing as a bounding box, except that we also return the indices, right?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@michaelkirk Extremes returns the most extreme coordinates of the geometry. In the case of a diamond, it will return each of the four coordinates. BoundingRect returns a minimum bounding Rect encompassing the geometry. The coordinates of the Rect are not necessarily vertices. In the case of a diamond, it will not return any of the four coordinates. The minimum bounding rectangle can be deduced from the extremes, but the reverse is not true. Does that answer your question?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the case of a diamond, it will return each of the four coordinates

Ah, it returns the entire coord for each extreme, not just the extreme component of the coord. Thanks for explaining!

We could similarly use exterior_coords for bounding_rect. It seems like it's already doing everything optimally, so it'd just be a little code cleanup.

I think the only case where it would be less optimal is for Rects, for which we already know the min/max. You could apply a similar optimization for Rect in extremes to save yourself a couple comparisons if you're feeling like shaving off some nanograms, but seems like small potets.

@frewsxcv frewsxcv changed the title Rewrite Extremes algorithm Rewrite Extremes algorithm to iteratively find the extreme coordinates using CoordsIter Jan 10, 2021
@frewsxcv frewsxcv marked this pull request as ready for review January 10, 2021 15:11
@frewsxcv
Copy link
Member Author

This is ready for review! I'll squash the commits in a little bit

@frewsxcv
Copy link
Member Author

I'm just gonna squash with GitHub when we're ready to merge.

bors try

bors bot added a commit that referenced this pull request Jan 10, 2021
@bors
Copy link
Contributor

bors bot commented Jan 10, 2021

try

Build succeeded:

Copy link
Member

@michaelkirk michaelkirk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice perf increase!

find_extreme_indices(polymax_naive_indices, self)
}
}
fn extremes(&'a self) -> Option<Outcome<T>> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the new API!

How arduous would it be to maintain a deprecated version of the old API for compatibility?

I know we're pre-v1 so technically we can "do whatever we want", but whenever we bump semver (for pre v1 like us, that's the X in 0.X.Y), people get left behind for a while, which means it's more likely they're using old code until their crate maintainers update, or are stuck with multiple incompatible versions.

That's why when it's easy, I'd prefer to do things in a way that uses deprecation so downstream users don't get stranded or need multiple versions of geo for compatibility reasons. Then we can batch all the breaking changes together, by deleting the deprecated methods, only once in a while.

I also think in-line deprecation notices are more helpful upgrade documentation than release notes for the people who update before the deprecated methods are removed.

(If it's not easy, then I'm OK with merging as is and bumping the minor version)

Copy link
Member Author

@frewsxcv frewsxcv Jan 10, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Before I started implementing this, I glanced at these GitHub queries: 1 2. Assuming these are doing the right thing, there's (essentially) no open source project utilizing the old traits. There could be users, but we at least have some indication there's minimal breakage in the GeoRust ecosystem. And I also took a look at the current changelog which already has at least one breaking change, so no matter what we're gonna need to bump to 0.17.0 with the next release (unless we were to branch off master). So I'd say it's not difficult to do the deprecation dance, and I do understand the benefits, but it still takes time for myself and other maintainers. So all in all I'm neutral about doing it in this case, but I have capacity to do it if you still prefer.

@michaelkirk
Copy link
Member

michaelkirk commented Jan 10, 2021 via email

@frewsxcv frewsxcv merged commit dde7b6c into master Jan 10, 2021
@frewsxcv frewsxcv deleted the frewsxcv-extremes branch January 10, 2021 21:12
@michaelkirk
Copy link
Member

Also, cool trick about using the GH search that way. I should utilize it more.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants