use std::{fmt::Debug, ops::Sub};
use ndarray::{
Array, ArrayBase, ArrayView, Axis, AxisDescription, Data, DimAdd, Dimension, IntoDimension,
Ix1, OwnedRepr, RemoveAxis, Slice,
};
use num_traits::{cast, Num, NumCast};
use crate::{
vector_extensions::{Monotonic, VectorExtensions},
BuilderError, InterpolateError,
};
mod strategies;
pub use strategies::{CubicSpline, Interp1DStrategy, Interp1DStrategyBuilder, Linear};
#[derive(Debug)]
pub struct Interp1D<Sd, Sx, D, Strat>
where
Sd: Data,
Sd::Elem: Num + Debug,
Sx: Data<Elem = Sd::Elem>,
D: Dimension,
Strat: Interp1DStrategy<Sd, Sx, D>,
{
x: ArrayBase<Sx, Ix1>,
data: ArrayBase<Sd, D>,
strategy: Strat,
}
impl<Sd, Sx, Strat> Interp1D<Sd, Sx, Ix1, Strat>
where
Sd: Data,
Sd::Elem: Num + PartialOrd + NumCast + Copy + Debug + Sub,
Sx: Data<Elem = Sd::Elem>,
Strat: Interp1DStrategy<Sd, Sx, Ix1>,
{
pub fn interp_scalar(&self, x: Sx::Elem) -> Result<Sd::Elem, InterpolateError> {
Ok(*self.interp(x)?.first().unwrap_or_else(|| unreachable!()))
}
}
impl<Sd, D> Interp1D<Sd, OwnedRepr<Sd::Elem>, D, Linear>
where
Sd: Data,
Sd::Elem: Num + PartialOrd + NumCast + Copy + Debug,
D: Dimension + RemoveAxis,
{
pub fn builder(data: ArrayBase<Sd, D>) -> Interp1DBuilder<Sd, OwnedRepr<Sd::Elem>, D, Linear> {
Interp1DBuilder::new(data)
}
}
impl<Sd, Sx, D, Strat> Interp1D<Sd, Sx, D, Strat>
where
Sd: Data,
Sd::Elem: Num + PartialOrd + NumCast + Copy + Debug + Sub,
Sx: Data<Elem = Sd::Elem>,
D: Dimension + RemoveAxis,
Strat: Interp1DStrategy<Sd, Sx, D>,
{
pub fn interp(&self, x: Sx::Elem) -> Result<Array<Sd::Elem, D::Smaller>, InterpolateError> {
let dim = self.data.raw_dim().remove_axis(Axis(0));
let mut target: Array<Sd::Elem, _> = Array::zeros(dim);
self.strategy
.interp_into(self, target.view_mut(), x)
.map(|_| target)
}
pub fn interp_array<Sq, Dq>(
&self,
xs: &ArrayBase<Sq, Dq>,
) -> Result<Array<Sd::Elem, <Dq as DimAdd<D::Smaller>>::Output>, InterpolateError>
where
Sq: Data<Elem = Sd::Elem>,
Dq: Dimension + DimAdd<D::Smaller>,
{
let mut dim = <Dq as DimAdd<D::Smaller>>::Output::default();
dim.as_array_view_mut()
.into_iter()
.zip(xs.shape().iter().chain(self.data.shape()[1..].iter()))
.for_each(|(new_axis, &len)| {
*new_axis = len;
});
let mut ys = Array::zeros(dim);
for (index, &x) in xs.indexed_iter() {
let current_dim = index.clone().into_dimension();
let subview =
ys.slice_each_axis_mut(|AxisDescription { axis: Axis(nr), .. }| match current_dim
.as_array_view()
.get(nr)
{
Some(idx) => Slice::from(*idx..*idx + 1),
None => Slice::from(..),
});
self.strategy.interp_into(
self,
subview
.into_shape(self.data.raw_dim().remove_axis(Axis(0)))
.unwrap_or_else(|_| unreachable!()),
x,
)?;
}
Ok(ys)
}
pub fn index_point(&self, index: usize) -> (Sx::Elem, ArrayView<Sd::Elem, D::Smaller>) {
let view = self.data.index_axis(Axis(0), index);
(self.x[index], view)
}
pub fn get_index_left_of(&self, x: Sx::Elem) -> usize {
self.x.get_lower_index(x)
}
pub fn is_in_range(&self, x: Sx::Elem) -> bool {
self.x[0] <= x && x <= self.x[self.x.len() - 1]
}
}
#[derive(Debug)]
pub struct Interp1DBuilder<Sd, Sx, D, Strat>
where
Sd: Data,
Sd::Elem: Num + Debug,
Sx: Data<Elem = Sd::Elem>,
D: Dimension,
{
x: ArrayBase<Sx, Ix1>,
data: ArrayBase<Sd, D>,
strategy: Strat,
}
impl<Sd, D> Interp1DBuilder<Sd, OwnedRepr<Sd::Elem>, D, Linear>
where
Sd: Data,
Sd::Elem: Num + PartialOrd + NumCast + Copy + Debug,
D: Dimension,
{
pub fn new(data: ArrayBase<Sd, D>) -> Self {
let len = data.shape()[0];
Interp1DBuilder {
x: Array::from_iter((0..len).map(|n| {
cast(n).unwrap_or_else(|| {
unimplemented!("casting from usize to a number should always work")
})
})),
data,
strategy: Linear::new(),
}
}
}
impl<Sd, Sx, D, Strat> Interp1DBuilder<Sd, Sx, D, Strat>
where
Sd: Data,
Sd::Elem: Num + PartialOrd + NumCast + Copy + Debug,
Sx: Data<Elem = Sd::Elem>,
D: Dimension,
Strat: Interp1DStrategyBuilder<Sd, Sx, D>,
{
pub fn x<NewSx>(self, x: ArrayBase<NewSx, Ix1>) -> Interp1DBuilder<Sd, NewSx, D, Strat>
where
NewSx: Data<Elem = Sd::Elem>,
{
let Interp1DBuilder { data, strategy, .. } = self;
Interp1DBuilder { x, data, strategy }
}
pub fn strategy<NewStrat>(self, strategy: NewStrat) -> Interp1DBuilder<Sd, Sx, D, NewStrat>
where
NewStrat: Interp1DStrategyBuilder<Sd, Sx, D>,
{
let Interp1DBuilder { x, data, .. } = self;
Interp1DBuilder { x, data, strategy }
}
pub fn build(self) -> Result<Interp1D<Sd, Sx, D, Strat::FinishedStrat>, BuilderError> {
use self::Monotonic::*;
use BuilderError::*;
let Interp1DBuilder { x, data, strategy } = self;
if data.ndim() < 1 {
return Err(DimensionError(
"data dimension is 0, needs to be at least 1".into(),
));
}
if data.shape()[0] < Strat::MINIMUM_DATA_LENGHT {
return Err(NotEnoughData(format!(
"The chosen Interpolation strategy needs at least {} data points",
Strat::MINIMUM_DATA_LENGHT
)));
}
if !matches!(x.monotonic_prop(), Rising { strict: true }) {
return Err(Monotonic(
"Values in the x axis need to be strictly monotonic rising".into(),
));
}
if x.len() != data.shape()[0] {
return Err(BuilderError::AxisLenght(format!(
"Lengths of x and data axis need to match. Got x: {:}, data: {:}",
x.len(),
data.shape()[0],
)));
}
let strategy = strategy.build(&x, &data)?;
Ok(Interp1D { x, data, strategy })
}
}