use crate::traits::FloatScalar;
use super::{find_interval, validate_sorted, InterpError};
#[inline]
fn cubic_eval<T: FloatScalar>(dx: T, [a, b, c, d]: [T; 4]) -> T {
a + dx * (b + dx * (c + dx * d))
}
#[inline]
fn cubic_eval_with_derivative<T: FloatScalar>(dx: T, coeffs: [T; 4]) -> (T, T) {
let [_, b, c, d] = coeffs;
let two = T::one() + T::one();
let three = two + T::one();
let val = cubic_eval(dx, coeffs);
let dval = b + dx * (two * c + three * d * dx);
(val, dval)
}
fn natural_spline_coeffs<T: FloatScalar>(
xs: &[T],
ys: &[T],
h: &mut [T],
delta: &mut [T],
cp: &mut [T],
m: &mut [T],
coeffs: &mut [[T; 4]],
) -> Result<(), InterpError> {
let n = xs.len();
let two = T::one() + T::one();
let three = two + T::one();
let six = three + three;
for i in 0..n - 1 {
h[i] = xs[i + 1] - xs[i];
delta[i] = (ys[i + 1] - ys[i]) / h[i];
}
if n > 3 {
let diag = two * (h[0] + h[1]);
if diag.abs() < T::epsilon() {
return Err(InterpError::IllConditioned);
}
cp[1] = h[1] / diag;
m[1] = six * (delta[1] - delta[0]) / diag;
for i in 2..n - 1 {
let diag_i = two * (h[i - 1] + h[i]) - h[i - 1] * cp[i - 1];
if diag_i.abs() < T::epsilon() {
return Err(InterpError::IllConditioned);
}
let rhs_i = six * (delta[i] - delta[i - 1]) - h[i - 1] * m[i - 1];
cp[i] = h[i] / diag_i;
m[i] = rhs_i / diag_i;
}
for i in (1..n - 2).rev() {
m[i] = m[i] - cp[i] * m[i + 1];
}
} else {
let diag = two * (h[0] + h[1]);
if diag.abs() < T::epsilon() {
return Err(InterpError::IllConditioned);
}
m[1] = six * (delta[1] - delta[0]) / diag;
}
for i in 0..n - 1 {
coeffs[i] = [
ys[i],
delta[i] - h[i] * (two * m[i] + m[i + 1]) / six,
m[i] / two,
(m[i + 1] - m[i]) / (six * h[i]),
];
}
Ok(())
}
#[derive(Debug, Clone)]
pub struct CubicSpline<T, const N: usize> {
xs: [T; N],
coeffs: [[T; 4]; N],
}
impl<T: FloatScalar, const N: usize> CubicSpline<T, N> {
pub fn new(xs: [T; N], ys: [T; N]) -> Result<Self, InterpError> {
if N < 3 {
return Err(InterpError::TooFewPoints);
}
validate_sorted(&xs)?;
let mut h = [T::zero(); N];
let mut delta = [T::zero(); N];
let mut cp = [T::zero(); N];
let mut m = [T::zero(); N];
let mut coeffs = [[T::zero(); 4]; N];
natural_spline_coeffs(
&xs,
&ys,
&mut h,
&mut delta,
&mut cp,
&mut m,
&mut coeffs[..N - 1],
)?;
Ok(Self { xs, coeffs })
}
pub fn eval(&self, x: T) -> T {
let i = find_interval(&self.xs, x);
cubic_eval(x - self.xs[i], self.coeffs[i])
}
pub fn eval_derivative(&self, x: T) -> (T, T) {
let i = find_interval(&self.xs, x);
cubic_eval_with_derivative(x - self.xs[i], self.coeffs[i])
}
pub fn xs(&self) -> &[T; N] {
&self.xs
}
}
#[cfg(feature = "alloc")]
extern crate alloc;
#[cfg(feature = "alloc")]
use alloc::vec::Vec;
#[cfg(feature = "alloc")]
#[derive(Debug, Clone)]
pub struct DynCubicSpline<T> {
xs: Vec<T>,
coeffs: Vec<[T; 4]>,
}
#[cfg(feature = "alloc")]
impl<T: FloatScalar> DynCubicSpline<T> {
pub fn new(xs: Vec<T>, ys: Vec<T>) -> Result<Self, InterpError> {
if xs.len() != ys.len() {
return Err(InterpError::LengthMismatch);
}
if xs.len() < 3 {
return Err(InterpError::TooFewPoints);
}
validate_sorted(&xs)?;
let n = xs.len();
let mut h = alloc::vec![T::zero(); n];
let mut delta = alloc::vec![T::zero(); n];
let mut cp = alloc::vec![T::zero(); n];
let mut m = alloc::vec![T::zero(); n];
let mut coeffs = alloc::vec![[T::zero(); 4]; n - 1];
natural_spline_coeffs(&xs, &ys, &mut h, &mut delta, &mut cp, &mut m, &mut coeffs)?;
Ok(Self { xs, coeffs })
}
pub fn eval(&self, x: T) -> T {
let i = find_interval(&self.xs, x);
cubic_eval(x - self.xs[i], self.coeffs[i])
}
pub fn eval_derivative(&self, x: T) -> (T, T) {
let i = find_interval(&self.xs, x);
cubic_eval_with_derivative(x - self.xs[i], self.coeffs[i])
}
pub fn xs(&self) -> &[T] {
&self.xs
}
}