mod adaptors;
mod builder;
mod error;
pub use adaptors::{BorderBuffer, BorderDeletion};
pub use builder::{BSplineBuilder, BSplineDirector};
pub use error::{
BSplineError, IncongruousElementsDegree, IncongruousElementsKnots, InvalidDegree, NotSorted,
TooFewElements, TooSmallWorkspace,
};
use crate::builder::Unknown;
use crate::{Chain, Curve, Signal, SortedChain, Space};
use builder::Open;
use num_traits::real::Real;
use topology_traits::Merge;
use core::fmt::Debug;
#[derive(Debug, Copy, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub struct BSpline<K, E, S> {
elements: E,
knots: K,
space: S,
degree: usize,
}
impl BSpline<Unknown, Unknown, Unknown> {
pub fn builder() -> BSplineBuilder<Unknown, Unknown, Unknown, Unknown, Open> {
BSplineBuilder::new()
}
}
impl<K, E, S> BSpline<K, E, S>
where
E: Chain,
S: Space<E::Output>,
{
fn workspace(&self, index: usize) -> impl AsMut<[E::Output]> {
let mut workspace = self.space.workspace();
let mut_workspace = workspace.as_mut();
for (i, val) in mut_workspace.iter_mut().enumerate().take(self.degree + 1) {
*val = self.elements.eval(index - self.degree + i);
}
workspace
}
}
impl<K, E, S, R> Signal<R> for BSpline<K, E, S>
where
E: Chain,
S: Space<E::Output>,
E::Output: Merge<R> + Copy,
R: Real + Debug,
K: SortedChain<Output = R>,
{
type Output = E::Output;
fn eval(&self, scalar: R) -> E::Output {
let lower_cut = self.degree;
let upper_cut = self.knots.len() - self.degree;
let index = self
.knots
.strict_upper_bound_clamped(scalar, lower_cut, upper_cut);
let mut workspace = self.workspace(index);
let elements = workspace.as_mut();
for r in 1..=self.degree {
for j in 0..=(self.degree - r) {
let i = j + r + index - self.degree;
let factor = (scalar - self.knots.eval(i - 1))
/ (self.knots.eval(i + self.degree - r) - self.knots.eval(i - 1));
elements[j] = elements[j].merge(elements[j + 1], factor);
}
}
elements[0]
}
}
impl<K, E, S, R> Curve<R> for BSpline<K, E, S>
where
E: Chain,
S: Space<E::Output>,
E::Output: Merge<R> + Copy,
R: Real + Debug,
K: SortedChain<Output = R>,
{
fn domain(&self) -> [R; 2] {
[
self.knots.eval(self.degree - 1),
self.knots.eval(self.knots.len() - self.degree),
]
}
}
impl<K, E, S> BSpline<K, E, S>
where
E: Chain,
K: SortedChain,
S: Space<E::Output>,
{
pub fn new(elements: E, knots: K, space: S) -> Result<Self, BSplineError> {
if elements.len() < 2 {
return Err(TooFewElements::new(elements.len()).into());
}
if knots.len() < elements.len() {
return Err(IncongruousElementsKnots::open(elements.len(), knots.len()).into());
}
if elements.len() <= knots.len() - elements.len() + 1 {
return Err(IncongruousElementsKnots::open(elements.len(), knots.len()).into());
}
let degree = knots.len() - elements.len() + 1;
if space.len() <= degree {
return Err(TooSmallWorkspace::new(space.len(), degree).into());
}
Ok(BSpline {
elements,
knots,
space,
degree,
})
}
}
impl<K, E, S> BSpline<K, E, S>
where
E: Chain,
K: SortedChain,
S: Space<E::Output>,
{
pub fn new_unchecked(elements: E, knots: K, space: S) -> Self {
let degree = knots.len() - elements.len() + 1;
BSpline {
elements,
knots,
space,
degree,
}
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn linear_bspline() {
let expect = [
(-1.0, -1.0),
(0.0, 0.0),
(0.2, 0.2),
(0.4, 0.4),
(0.6, 0.6),
(0.8, 0.8),
(1.0, 1.0),
(2.0, 2.0),
];
let points = [0.0f32, 1.0];
let knots = [0.0f32, 1.0];
let spline = BSpline::builder()
.elements(points)
.knots(knots)
.constant::<2>()
.build()
.unwrap();
for (input, output) in expect {
assert_f32_near!(spline.eval(input), output);
}
}
#[test]
fn quadratic_bspline() {
let expect = [
(0.0, 0.0),
(0.5, 0.125),
(1.0, 0.5),
(1.4, 0.74),
(1.5, 0.75),
(1.6, 0.74),
(2.0, 0.5),
(2.5, 0.125),
(3.0, 0.0),
];
let points = [0.0f32, 0.0, 1.0, 0.0, 0.0];
let knots = [0.0f32, 0.0, 1.0, 2.0, 3.0, 3.0];
let spline = BSpline::builder()
.elements(points)
.knots(knots)
.constant::<3>()
.build()
.unwrap();
for (input, output) in expect {
assert_f32_near!(spline.eval(input), output);
}
}
#[test]
fn cubic_bspline() {
let expect = [
(-2.0, 0.0),
(-1.5, 0.125),
(-1.0, 1.0),
(-0.6, 2.488),
(0.0, 4.0),
(0.5, 2.875),
(1.5, 0.12500001),
(2.0, 0.0),
];
let points = [0.0f32, 0.0, 0.0, 6.0, 0.0, 0.0, 0.0];
let knots = [-2.0f32, -2.0, -2.0, -1.0, 0.0, 1.0, 2.0, 2.0, 2.0];
let spline = BSpline::builder()
.elements(points)
.knots(knots)
.constant::<4>()
.build()
.unwrap();
for (input, output) in expect {
assert_f32_near!(spline.eval(input), output);
}
}
#[test]
fn quartic_bspline() {
let expect = [
(0.0, 0.0),
(0.4, 0.0010666668),
(1.0, 0.041666668),
(1.5, 0.19791667),
(2.0, 0.4583333),
(2.5, 0.5989583),
(3.0, 0.4583333),
(3.2, 0.35206667),
(4.1, 0.02733751),
(4.5, 0.002604167),
(5.0, 0.0),
];
let points = [0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0];
let knots = [0.0, 0.0, 0.0, 0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 5.0, 5.0, 5.0];
let spline = BSpline::builder()
.elements(points)
.knots(knots)
.constant::<5>()
.build()
.unwrap();
for (input, output) in expect {
assert_f32_near!(spline.eval(input), output);
}
}
#[test]
fn quartic_bspline_f64() {
let expect = [
(0.0, 0.0),
(0.4, 0.001066666666666667),
(1.0, 0.041666666666666664),
(1.5, 0.19791666666666666),
(2.0, 0.45833333333333337),
(2.5, 0.5989583333333334),
(3.0, 0.4583333333333333),
(3.2, 0.3520666666666666),
(4.1, 0.027337500000000046),
(4.5, 0.002604166666666666),
(5.0, 0.0),
];
let points = [0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0];
let knots = [0.0, 0.0, 0.0, 0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 5.0, 5.0, 5.0];
let spline = BSpline::builder()
.elements(points)
.knots(knots)
.constant::<5>()
.build()
.unwrap();
for (input, output) in expect {
assert_f64_near!(spline.eval(input), output);
}
}
#[test]
fn partial_eq() {
let spline = BSpline::builder()
.elements([0.0f32, 1.0])
.knots([0.0f32, 1.0])
.constant::<2>()
.build()
.unwrap();
let spline2 = BSpline::builder()
.elements([0.0f32, 1.0])
.knots([0.0f32, 1.0])
.constant::<2>()
.build()
.unwrap();
assert_eq!(spline, spline2);
}
}