use crate::core::{nodes, ChebyError, ChebyScalar, ChebyTime, Domain, NodeKind};
#[derive(Debug, Clone, PartialEq)]
pub struct BarycentricInterpolator<T, X, const N: usize> {
nodes: [X; N],
values: [T; N],
weights: [f64; N],
scale: X,
}
impl<T, X, const N: usize> BarycentricInterpolator<T, X, N>
where
T: ChebyScalar,
X: ChebyTime,
{
pub fn new(nodes: [X; N], values: [T; N]) -> Result<Self, ChebyError> {
if N == 0 {
return Err(ChebyError::InvalidDegree);
}
if N == 1 {
return Ok(Self {
nodes,
values,
weights: [1.0; N],
scale: X::zero(),
});
}
let scale = nodes[0] - nodes[N - 1];
if scale == X::zero() {
return Err(ChebyError::InvalidDomain);
}
let mut weights = [0.0; N];
for j in 0..N {
let mut product = 1.0;
for k in 0..N {
if j != k {
let d = (nodes[j] - nodes[k]) / scale;
if d == 0.0 {
return Err(ChebyError::InvalidDomain);
}
product *= d;
}
}
weights[j] = 1.0 / product;
}
Ok(Self {
nodes,
values,
weights,
scale,
})
}
pub fn on_chebyshev_roots(domain: Domain<X>, values: [T; N]) -> Result<Self, ChebyError> {
if N == 0 {
return Err(ChebyError::InvalidDegree);
}
let xs = nodes::<N>(NodeKind::Roots);
let mapped: [X; N] = core::array::from_fn(|k| domain.denormalize(xs[k]));
if N == 1 {
return Ok(Self {
nodes: mapped,
values,
weights: [1.0; N],
scale: X::zero(),
});
}
let mut weights = [0.0; N];
let mut sign = 1.0_f64;
for (k, w) in weights.iter_mut().enumerate() {
let theta = core::f64::consts::PI * (2.0 * k as f64 + 1.0) / (2.0 * N as f64);
*w = sign * theta.sin();
sign = -sign;
}
let scale = mapped[0] - mapped[N - 1];
Ok(Self {
nodes: mapped,
values,
weights,
scale,
})
}
pub fn on_lobatto_nodes(domain: Domain<X>, values: [T; N]) -> Result<Self, ChebyError> {
if N == 0 {
return Err(ChebyError::InvalidDegree);
}
let xs = nodes::<N>(NodeKind::Lobatto);
let mapped: [X; N] = core::array::from_fn(|k| domain.denormalize(xs[k]));
if N == 1 {
return Ok(Self {
nodes: mapped,
values,
weights: [1.0; N],
scale: X::zero(),
});
}
let mut weights = [0.0; N];
let mut sign = 1.0_f64;
for w in weights.iter_mut() {
*w = sign;
sign = -sign;
}
weights[0] *= 0.5;
weights[N - 1] *= 0.5;
let scale = mapped[0] - mapped[N - 1];
Ok(Self {
nodes: mapped,
values,
weights,
scale,
})
}
pub fn evaluate(&self, x: X) -> Result<T, ChebyError> {
if N == 1 {
return Ok(self.values[0]);
}
let mut numerator = T::zero();
let mut denominator = 0.0;
for k in 0..N {
let d = (x - self.nodes[k]) / self.scale;
if d.abs() <= 8.0 * f64::EPSILON {
return Ok(self.values[k]);
}
let term = self.weights[k] / d;
numerator = numerator + self.values[k] * term;
denominator += term;
}
Ok(numerator / denominator)
}
}