-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
12 changed files
with
200 additions
and
94 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,39 +1,31 @@ | ||
name: build | ||
on: | ||
schedule: | ||
- cron: '0 5 * * *' | ||
push: | ||
merge_group: | ||
pull_request: | ||
push: | ||
env: | ||
CARGO_TERM_COLOR: always | ||
jobs: | ||
default: | ||
runs-on: ubuntu-latest | ||
steps: | ||
- name: checkout | ||
uses: actions/checkout@v2 | ||
uses: actions/checkout@v4 | ||
- name: msrv | ||
run: | | ||
msrv=$(cargo metadata --no-deps --format-version 1 | | ||
jq --raw-output '.packages[] | select(.name=="miniball") | .rust_version') | ||
echo "MSRV=$msrv" >> $GITHUB_ENV | ||
- name: toolchain | ||
uses: actions-rs/toolchain@v1 | ||
uses: dtolnay/rust-toolchain@master | ||
with: | ||
toolchain: nightly | ||
profile: minimal | ||
toolchain: ${{ env.MSRV }} | ||
components: rustfmt, rust-docs, clippy | ||
override: true | ||
- name: test | ||
uses: actions-rs/cargo@v1 | ||
with: | ||
command: test | ||
run: cargo test | ||
- name: clippy | ||
uses: actions-rs/cargo@v1 | ||
with: | ||
command: clippy | ||
args: --tests -- --deny clippy::pedantic | ||
run: cargo clippy --tests --examples -- -D clippy::all -D clippy::pedantic -D clippy::nursery | ||
- name: doc | ||
uses: actions-rs/cargo@v1 | ||
with: | ||
command: doc | ||
run: cargo doc | ||
- name: fmt | ||
uses: actions-rs/cargo@v1 | ||
with: | ||
command: fmt | ||
args: --check | ||
run: cargo fmt --check |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,7 +2,8 @@ | |
name = "miniball" | ||
description = "Minimum enclosing ball" | ||
authors = ["Rouven Spreckels <[email protected]>"] | ||
version = "0.2.0" | ||
version = "0.3.0" | ||
rust-version = "1.61.0" | ||
edition = "2021" | ||
documentation = "https://docs.rs/miniball" | ||
repository = "https://github.com/qu1x/miniball" | ||
|
@@ -31,8 +32,7 @@ include = [ | |
] | ||
|
||
[dependencies] | ||
arrayvec = "0.7.2" | ||
nalgebra = { version = "0.32.2", features = ["rand"] } | ||
nalgebra = { version = "0.32.4", features = ["rand"] } | ||
stacker = "0.1.15" | ||
|
||
[profile.test] | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,18 +6,16 @@ Minimum enclosing ball. | |
[![Documentation][]](https://docs.rs/miniball) | ||
[![Downloads][]](https://crates.io/crates/miniball) | ||
[![Version][]](https://crates.io/crates/miniball) | ||
[![Rust][]](https://www.rust-lang.org) | ||
[![Rust][]](https://blog.rust-lang.org/2022/05/19/Rust-1.61.0.html) | ||
[![License][]](https://mozilla.org/MPL) | ||
|
||
[Build]: https://github.com/qu1x/miniball/actions/workflows/build.yml/badge.svg | ||
[Documentation]: https://docs.rs/miniball/badge.svg | ||
[Downloads]: https://img.shields.io/crates/d/miniball.svg | ||
[Version]: https://img.shields.io/crates/v/miniball.svg | ||
[Rust]: https://img.shields.io/badge/rust-nightly-orange.svg | ||
[Rust]: https://img.shields.io/badge/rust-v1.61.0-brightgreen.svg | ||
[License]: https://img.shields.io/crates/l/miniball | ||
|
||
**NOTE**: This crate requires nightly Rust. | ||
|
||
* Finds circumscribed *n*-ball of set of bounds. | ||
* Finds minimum *n*-ball enclosing set of points. | ||
|
||
|
@@ -33,7 +31,7 @@ See the [release history] to keep track of the development. | |
|
||
## License | ||
|
||
Copyright © 2022 Rouven Spreckels <[email protected]> | ||
Copyright © 2022-2024 Rouven Spreckels <[email protected]> | ||
|
||
Licensed under the terms of the [`MPL-2.0`](LICENSES/MPL-2.0). | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,2 @@ | ||
hard_tabs = true | ||
format_code_in_doc_comments = true | ||
#format_code_in_doc_comments = true |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,45 +1,63 @@ | ||
// Copyright © 2022 Rouven Spreckels <[email protected]> | ||
// Copyright © 2022-2024 Rouven Spreckels <[email protected]> | ||
// | ||
// This Source Code Form is subject to the terms of the Mozilla Public | ||
// License, v. 2.0. If a copy of the MPL was not distributed with this | ||
// file, You can obtain one at https://mozilla.org/MPL/2.0/. | ||
|
||
use super::Enclosing; | ||
use nalgebra::{distance_squared, Point, RealField, SMatrix, SVector}; | ||
use nalgebra::{ | ||
base::allocator::Allocator, DefaultAllocator, DimNameAdd, DimNameSum, OMatrix, OPoint, OVector, | ||
RealField, U1, | ||
}; | ||
|
||
/// Ball over real field `R` of dimension `D` with center and radius squared. | ||
#[derive(Debug, Clone, Copy, PartialEq)] | ||
pub struct Ball<R: RealField, const D: usize> { | ||
#[derive(Debug, Clone, PartialEq)] | ||
pub struct Ball<R: RealField, D: DimNameAdd<U1>> | ||
where | ||
DefaultAllocator: Allocator<R, D>, | ||
{ | ||
/// Ball's center. | ||
pub center: Point<R, D>, | ||
pub center: OPoint<R, D>, | ||
/// Ball's radius squared. | ||
pub radius_squared: R, | ||
} | ||
|
||
impl<R: RealField, const D: usize> Enclosing<R, D> for Ball<R, D> { | ||
impl<R: RealField + Copy, D: DimNameAdd<U1>> Copy for Ball<R, D> | ||
where | ||
OPoint<R, D>: Copy, | ||
DefaultAllocator: Allocator<R, D>, | ||
{ | ||
} | ||
|
||
impl<R: RealField, D: DimNameAdd<U1>> Enclosing<R, D> for Ball<R, D> | ||
where | ||
DefaultAllocator: | ||
Allocator<R, D> + Allocator<R, D, D> + Allocator<OPoint<R, D>, DimNameSum<D, U1>>, | ||
<DefaultAllocator as Allocator<OPoint<R, D>, DimNameSum<D, U1>>>::Buffer: Default, | ||
{ | ||
#[inline] | ||
fn contains(&self, point: &Point<R, D>) -> bool { | ||
distance_squared(&self.center, point) <= self.radius_squared | ||
fn contains(&self, point: &OPoint<R, D>) -> bool { | ||
(point - &self.center).norm_squared() <= self.radius_squared | ||
} | ||
fn with_bounds(bounds: &[Point<R, D>]) -> Option<Self> { | ||
let length = bounds.len().checked_sub(1).filter(|&length| length <= D)?; | ||
let points = SMatrix::<R, D, D>::from_fn(|row, column| { | ||
fn with_bounds(bounds: &[OPoint<R, D>]) -> Option<Self> { | ||
let length = bounds.len().checked_sub(1).filter(|&len| len <= D::USIZE)?; | ||
let points = OMatrix::<R, D, D>::from_fn(|row, column| { | ||
if column < length { | ||
bounds[column + 1].coords[row].clone() - bounds[0].coords[row].clone() | ||
} else { | ||
R::zero() | ||
} | ||
}); | ||
let points = points.view((0, 0), (D, length)); | ||
let matrix = SMatrix::<R, D, D>::from_fn(|row, column| { | ||
let points = points.view((0, 0), (D::USIZE, length)); | ||
let matrix = OMatrix::<R, D, D>::from_fn(|row, column| { | ||
if row < length && column < length { | ||
points.column(row).dot(&points.column(column)) * (R::one() + R::one()) | ||
} else { | ||
R::zero() | ||
} | ||
}); | ||
let matrix = matrix.view((0, 0), (length, length)); | ||
let vector = SVector::<R, D>::from_fn(|row, _column| { | ||
let vector = OVector::<R, D>::from_fn(|row, _column| { | ||
if row < length { | ||
points.column(row).norm_squared() | ||
} else { | ||
|
@@ -49,11 +67,11 @@ impl<R: RealField, const D: usize> Enclosing<R, D> for Ball<R, D> { | |
let vector = vector.view((0, 0), (length, 1)); | ||
matrix.try_inverse().map(|matrix| { | ||
let vector = matrix * vector; | ||
let mut center = SVector::<R, D>::zeros(); | ||
let mut center = OVector::<R, D>::zeros(); | ||
for point in 0..length { | ||
center += points.column(point) * vector[point].clone(); | ||
} | ||
Ball { | ||
Self { | ||
center: &bounds[0] + ¢er, | ||
radius_squared: center.norm_squared(), | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
// Copyright © 2022 Rouven Spreckels <[email protected]> | ||
// Copyright © 2022-2024 Rouven Spreckels <[email protected]> | ||
// | ||
// This Source Code Form is subject to the terms of the Mozilla Public | ||
// License, v. 2.0. If a copy of the MPL was not distributed with this | ||
|
@@ -32,49 +32,49 @@ pub trait Deque<T> { | |
impl<T> Deque<T> for VecDeque<T> { | ||
#[inline] | ||
fn len(&self) -> usize { | ||
VecDeque::len(self) | ||
Self::len(self) | ||
} | ||
|
||
#[inline] | ||
fn pop_front(&mut self) -> Option<T> { | ||
VecDeque::pop_front(self) | ||
Self::pop_front(self) | ||
} | ||
#[inline] | ||
fn pop_back(&mut self) -> Option<T> { | ||
VecDeque::pop_back(self) | ||
Self::pop_back(self) | ||
} | ||
|
||
#[inline] | ||
fn push_front(&mut self, value: T) { | ||
VecDeque::push_front(self, value); | ||
Self::push_front(self, value); | ||
} | ||
#[inline] | ||
fn push_back(&mut self, value: T) { | ||
VecDeque::push_back(self, value); | ||
Self::push_back(self, value); | ||
} | ||
} | ||
|
||
impl<T> Deque<T> for LinkedList<T> { | ||
#[inline] | ||
fn len(&self) -> usize { | ||
LinkedList::len(self) | ||
Self::len(self) | ||
} | ||
|
||
#[inline] | ||
fn pop_front(&mut self) -> Option<T> { | ||
LinkedList::pop_front(self) | ||
Self::pop_front(self) | ||
} | ||
#[inline] | ||
fn pop_back(&mut self) -> Option<T> { | ||
LinkedList::pop_back(self) | ||
Self::pop_back(self) | ||
} | ||
|
||
#[inline] | ||
fn push_front(&mut self, value: T) { | ||
LinkedList::push_front(self, value); | ||
Self::push_front(self, value); | ||
} | ||
#[inline] | ||
fn push_back(&mut self, value: T) { | ||
LinkedList::push_back(self, value); | ||
Self::push_back(self, value); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,27 +1,34 @@ | ||
// Copyright © 2022 Rouven Spreckels <[email protected]> | ||
// Copyright © 2022-2024 Rouven Spreckels <[email protected]> | ||
// | ||
// This Source Code Form is subject to the terms of the Mozilla Public | ||
// License, v. 2.0. If a copy of the MPL was not distributed with this | ||
// file, You can obtain one at https://mozilla.org/MPL/2.0/. | ||
|
||
use super::Deque; | ||
use arrayvec::ArrayVec; | ||
use nalgebra::{Point, RealField}; | ||
use super::{Deque, OVec}; | ||
use nalgebra::{ | ||
base::allocator::Allocator, DefaultAllocator, DimNameAdd, DimNameSum, OPoint, RealField, U1, | ||
}; | ||
use stacker::maybe_grow; | ||
use std::mem::size_of; | ||
|
||
/// Minimum enclosing ball. | ||
pub trait Enclosing<R: RealField, const D: usize>: Clone { | ||
pub trait Enclosing<R: RealField, D: DimNameAdd<U1>> | ||
where | ||
Self: Clone, | ||
DefaultAllocator: Allocator<R, D> + Allocator<OPoint<R, D>, DimNameSum<D, U1>>, | ||
<DefaultAllocator as Allocator<OPoint<R, D>, DimNameSum<D, U1>>>::Buffer: Default, | ||
{ | ||
#[doc(hidden)] | ||
/// Guaranteed stack size per recursion step. | ||
const RED_ZONE: usize = 32 * 1_024 + (8 * D + 2 * D.pow(2)) * size_of::<Point<R, D>>(); | ||
const RED_ZONE: usize = | ||
32 * 1_024 + (8 * D::USIZE + 2 * D::USIZE.pow(2)) * size_of::<OPoint<R, D>>(); | ||
#[doc(hidden)] | ||
/// New stack space to allocate if within [`Self::RED_ZONE`]. | ||
const STACK_SIZE: usize = Self::RED_ZONE * 1_024; | ||
|
||
/// Whether ball contains `point`. | ||
#[must_use] | ||
fn contains(&self, point: &Point<R, D>) -> bool; | ||
fn contains(&self, point: &OPoint<R, D>) -> bool; | ||
/// Returns circumscribed ball with all `bounds` on surface or `None` if it does not exist. | ||
/// | ||
/// # Example | ||
|
@@ -52,7 +59,7 @@ pub trait Enclosing<R: RealField, const D: usize>: Clone { | |
/// assert_eq!(radius_squared, 3.0); | ||
/// ``` | ||
#[must_use] | ||
fn with_bounds(bounds: &[Point<R, D>]) -> Option<Self>; | ||
fn with_bounds(bounds: &[OPoint<R, D>]) -> Option<Self>; | ||
|
||
/// Returns minimum ball enclosing `points`. | ||
/// | ||
|
@@ -123,13 +130,13 @@ pub trait Enclosing<R: RealField, const D: usize>: Clone { | |
/// ``` | ||
#[must_use] | ||
#[inline] | ||
fn enclosing_points(points: &mut impl Deque<Point<R, D>>) -> Self | ||
where | ||
ArrayVec<Point<R, D>, { D + 1 }>:, | ||
{ | ||
fn enclosing_points(points: &mut impl Deque<OPoint<R, D>>) -> Self { | ||
maybe_grow(Self::RED_ZONE, Self::STACK_SIZE, || { | ||
Self::enclosing_points_with_bounds(points, &mut ArrayVec::new()) | ||
.expect("Empty point set") | ||
Self::enclosing_points_with_bounds( | ||
points, | ||
&mut OVec::<OPoint<R, D>, DimNameSum<D, U1>>::new(), | ||
) | ||
.expect("Empty point set") | ||
}) | ||
} | ||
/// Returns minimum ball enclosing `points` with `bounds`. | ||
|
@@ -138,20 +145,16 @@ pub trait Enclosing<R: RealField, const D: usize>: Clone { | |
#[doc(hidden)] | ||
#[must_use] | ||
fn enclosing_points_with_bounds( | ||
points: &mut impl Deque<Point<R, D>>, | ||
bounds: &mut ArrayVec<Point<R, D>, { D + 1 }>, | ||
points: &mut impl Deque<OPoint<R, D>>, | ||
bounds: &mut OVec<OPoint<R, D>, DimNameSum<D, U1>>, | ||
) -> Option<Self> { | ||
// Take point from back. | ||
if let Some(point) = points.pop_back() | ||
&& !bounds.is_full() | ||
{ | ||
if let Some(point) = points.pop_back().filter(|_| !bounds.is_full()) { | ||
let ball = maybe_grow(Self::RED_ZONE, Self::STACK_SIZE, || { | ||
// Branch with one point less. | ||
Self::enclosing_points_with_bounds(points, bounds) | ||
}); | ||
if let Some(ball) = ball | ||
&& ball.contains(&point) | ||
{ | ||
if let Some(ball) = ball.filter(|ball| ball.contains(&point)) { | ||
// Move point to back. | ||
points.push_back(point); | ||
Some(ball) | ||
|
@@ -168,7 +171,7 @@ pub trait Enclosing<R: RealField, const D: usize>: Clone { | |
} | ||
} else { | ||
// Circumscribed ball with bounds. | ||
Self::with_bounds(bounds) | ||
Self::with_bounds(bounds.as_slice()) | ||
} | ||
} | ||
} |
Oops, something went wrong.