use super::error::LinearError;
use super::{KnotElementInequality, Linear, TooFewElements};
use crate::builder::{Type, Unknown, WithWeight, WithoutWeight};
use crate::weights::{IntoWeight, Weighted, Weights};
use crate::{Chain, Equidistant, Identity, Signal, Sorted, SortedChain};
use core::marker::PhantomData;
use core::ops::Mul;
use num_traits::FromPrimitive;
use num_traits::identities::Zero;
use num_traits::real::Real;
use topology_traits::Merge;
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub struct LinearDirector<K, E, F, W> {
knots: K,
elements: E,
easing: F,
_phantom: PhantomData<*const W>,
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub struct LinearBuilder<K, E, F, W> {
inner: Result<LinearDirector<K, E, F, W>, LinearError>,
}
impl Default for LinearDirector<Unknown, Unknown, Identity, Unknown> {
fn default() -> Self {
LinearDirector::new()
}
}
impl Default for LinearBuilder<Unknown, Unknown, Identity, Unknown> {
fn default() -> Self {
LinearBuilder::new()
}
}
impl LinearDirector<Unknown, Unknown, Identity, Unknown> {
pub const fn new() -> Self {
LinearDirector {
knots: Unknown,
elements: Unknown,
easing: Identity::new(),
_phantom: PhantomData,
}
}
}
impl LinearBuilder<Unknown, Unknown, Identity, Unknown> {
pub const fn new() -> Self {
LinearBuilder {
inner: Ok(LinearDirector::new()),
}
}
}
impl<F> LinearDirector<Unknown, Unknown, F, Unknown> {
pub fn elements<E>(
self,
elements: E,
) -> Result<LinearDirector<Unknown, E, F, WithoutWeight>, TooFewElements>
where
E: Chain,
{
if elements.len() < 2 {
return Err(TooFewElements::new(elements.len()));
}
Ok(LinearDirector {
knots: self.knots,
elements,
easing: self.easing,
_phantom: PhantomData,
})
}
pub fn elements_with_weights<G>(
self,
signal: G,
) -> Result<LinearDirector<Unknown, Weights<G>, F, WithWeight>, TooFewElements>
where
G: Chain,
G::Output: IntoWeight,
<G::Output as IntoWeight>::Element:
Mul<<G::Output as IntoWeight>::Weight, Output = <G::Output as IntoWeight>::Element>,
<G::Output as IntoWeight>::Weight: Zero + Copy,
{
if signal.len() < 2 {
return Err(TooFewElements::new(signal.len()));
}
Ok(LinearDirector {
knots: self.knots,
elements: Weights::new(signal),
easing: self.easing,
_phantom: PhantomData,
})
}
}
impl<F> LinearBuilder<Unknown, Unknown, F, Unknown> {
pub fn elements<E>(self, elements: E) -> LinearBuilder<Unknown, E, F, WithoutWeight>
where
E: Chain,
{
LinearBuilder {
inner: self
.inner
.and_then(|director| director.elements(elements).map_err(|err| err.into())),
}
}
pub fn elements_with_weights<G>(
self,
signal: G,
) -> LinearBuilder<Unknown, Weights<G>, F, WithWeight>
where
G: Chain,
G::Output: IntoWeight,
<G::Output as IntoWeight>::Element:
Mul<<G::Output as IntoWeight>::Weight, Output = <G::Output as IntoWeight>::Element>,
<G::Output as IntoWeight>::Weight: Zero + Copy,
{
LinearBuilder {
inner: self.inner.and_then(|director| {
director
.elements_with_weights(signal)
.map_err(|err| err.into())
}),
}
}
}
impl<E, F, W> LinearDirector<Unknown, E, F, W> {
pub fn knots<K>(self, knots: K) -> Result<LinearDirector<Sorted<K>, E, F, W>, LinearError>
where
E: Chain,
K: Chain,
K::Output: PartialOrd,
{
if self.elements.len() != knots.len() {
return Err(KnotElementInequality::new(self.elements.len(), knots.len()).into());
}
Ok(LinearDirector {
knots: Sorted::new(knots)?,
elements: self.elements,
easing: self.easing,
_phantom: self._phantom,
})
}
pub fn equidistant<R>(self) -> LinearDirector<Type<R>, E, F, W> {
LinearDirector {
knots: Type::new(),
elements: self.elements,
easing: self.easing,
_phantom: self._phantom,
}
}
}
impl<E, F, W> LinearBuilder<Unknown, E, F, W> {
pub fn knots<K>(self, knots: K) -> LinearBuilder<Sorted<K>, E, F, W>
where
E: Chain,
K: Chain,
K::Output: PartialOrd,
{
LinearBuilder {
inner: self.inner.and_then(|director| director.knots(knots)),
}
}
pub fn equidistant<R>(self) -> LinearBuilder<Type<R>, E, F, W> {
LinearBuilder {
inner: self.inner.map(|director| director.equidistant()),
}
}
}
impl<R, E, F, W> LinearDirector<Type<R>, E, F, W>
where
E: Chain,
R: Real + FromPrimitive,
{
pub fn domain(self, start: R, end: R) -> LinearDirector<Equidistant<R>, E, F, W> {
LinearDirector {
knots: Equidistant::new(self.elements.len(), start, end),
elements: self.elements,
easing: self.easing,
_phantom: self._phantom,
}
}
pub fn normalized(self) -> LinearDirector<Equidistant<R>, E, F, W> {
LinearDirector {
knots: Equidistant::normalized(self.elements.len()),
elements: self.elements,
easing: self.easing,
_phantom: self._phantom,
}
}
pub fn distance(self, start: R, step: R) -> LinearDirector<Equidistant<R>, E, F, W> {
LinearDirector {
knots: Equidistant::step(self.elements.len(), start, step),
elements: self.elements,
easing: self.easing,
_phantom: self._phantom,
}
}
}
impl<R, E, F, W> LinearBuilder<Type<R>, E, F, W>
where
E: Chain,
R: Real + FromPrimitive,
{
pub fn domain(self, start: R, end: R) -> LinearBuilder<Equidistant<R>, E, F, W> {
LinearBuilder {
inner: self.inner.map(|director| director.domain(start, end)),
}
}
pub fn normalized(self) -> LinearBuilder<Equidistant<R>, E, F, W> {
LinearBuilder {
inner: self.inner.map(|director| director.normalized()),
}
}
pub fn distance(self, start: R, step: R) -> LinearBuilder<Equidistant<R>, E, F, W> {
LinearBuilder {
inner: self.inner.map(|director| director.distance(start, step)),
}
}
}
impl<K, E, F, W> LinearDirector<K, E, F, W>
where
K: SortedChain,
{
pub fn easing<FF>(self, easing: FF) -> LinearDirector<K, E, FF, W> {
LinearDirector {
knots: self.knots,
elements: self.elements,
easing,
_phantom: self._phantom,
}
}
}
impl<K, E, F, W> LinearBuilder<K, E, F, W>
where
K: SortedChain,
{
pub fn easing<FF>(self, easing: FF) -> LinearBuilder<K, E, FF, W> {
LinearBuilder {
inner: self.inner.map(|director| director.easing(easing)),
}
}
}
impl<K, E, F> LinearDirector<K, E, F, WithoutWeight>
where
E: Chain,
K: SortedChain,
E::Output: Merge<K::Output>,
K::Output: Real,
{
pub fn build(self) -> Linear<K, E, F> {
Linear::new_unchecked(self.elements, self.knots, self.easing)
}
}
impl<K, E, F> LinearBuilder<K, E, F, WithoutWeight>
where
E: Chain,
K: SortedChain,
E::Output: Merge<K::Output>,
K::Output: Real,
{
pub fn build(self) -> Result<Linear<K, E, F>, LinearError> {
match self.inner {
Err(err) => Err(err),
Ok(director) => Ok(director.build()),
}
}
}
impl<K, G, F> LinearDirector<K, Weights<G>, F, WithWeight>
where
K: SortedChain,
K::Output: Real + Copy,
G: Chain,
G::Output: IntoWeight,
<Weights<G> as Signal<usize>>::Output: Merge<K::Output>,
{
pub fn build(self) -> WeightedLinear<K, G, F> {
Weighted::new(Linear::new_unchecked(
self.elements,
self.knots,
self.easing,
))
}
}
impl<K, G, F> LinearBuilder<K, Weights<G>, F, WithWeight>
where
K: SortedChain,
K::Output: Real + Copy,
G: Chain,
G::Output: IntoWeight,
<Weights<G> as Signal<usize>>::Output: Merge<K::Output>,
{
pub fn build(self) -> Result<WeightedLinear<K, G, F>, LinearError> {
match self.inner {
Err(err) => Err(err),
Ok(director) => Ok(director.build()),
}
}
}
type WeightedLinear<K, G, F> = Weighted<Linear<K, Weights<G>, F>>;
#[cfg(test)]
mod test {
use super::LinearBuilder;
use crate::{Signal, linear::LinearDirector, weights::Homogeneous};
#[test]
fn building_weights() {
LinearBuilder::new()
.elements_with_weights([(1.0, 1.0), (2.0, 2.0), (3.0, 0.0)])
.equidistant::<f64>()
.normalized()
.build()
.unwrap();
LinearBuilder::new()
.elements_with_weights([1.0, 2.0, 3.0].stack([1.0, 2.0, 0.0]))
.equidistant::<f64>()
.normalized()
.build()
.unwrap();
LinearBuilder::new()
.elements_with_weights([
Homogeneous::new(1.0),
Homogeneous::weighted_unchecked(2.0, 2.0),
Homogeneous::infinity(3.0),
])
.knots([1.0, 2.0, 3.0])
.build()
.unwrap();
LinearBuilder::new()
.elements([0.1, 0.2, 0.3])
.equidistant::<f64>()
.normalized()
.build()
.unwrap();
}
#[test]
fn builder_errors() {
assert!(
LinearBuilder::new()
.elements::<[f64; 0]>([])
.knots::<[f64; 0]>([])
.build()
.is_err()
);
assert!(
LinearBuilder::new()
.elements([1.0])
.knots([1.0])
.build()
.is_err()
);
assert!(
LinearBuilder::new()
.elements([1.0, 2.0])
.knots([1.0, 2.0, 3.0])
.build()
.is_err()
);
}
#[test]
fn director_errors() {
assert!(LinearDirector::new().elements([0.0]).is_err());
assert!(
LinearDirector::new()
.elements([0.0, 1.0])
.unwrap()
.knots([1.0])
.is_err()
);
assert!(
LinearDirector::new()
.elements([1.0, 2.0])
.unwrap()
.knots([1.0, 2.0, 3.0])
.is_err()
);
assert!(
LinearDirector::new()
.elements([1.0, 2.0])
.unwrap()
.knots([1.0, 2.0])
.is_ok()
);
}
}