use crate::error::{InterpolateError, InterpolateResult};
use crate::traits::InterpolationFloat;
use scirs2_core::ndarray::{Array1, ArrayView1};
#[derive(Debug, Clone)]
pub struct FloaterHormann<F: InterpolationFloat> {
x: Array1<F>,
y: Array1<F>,
w: Array1<F>,
d: usize,
}
impl<F: InterpolationFloat> FloaterHormann<F> {
pub fn new(x: &ArrayView1<F>, y: &ArrayView1<F>, d: usize) -> InterpolateResult<Self> {
let n = x.len();
if n != y.len() {
return Err(InterpolateError::invalid_input(
"x and y must have the same length".to_string(),
));
}
if n == 0 {
return Err(InterpolateError::empty_data("Floater-Hormann"));
}
for i in 1..n {
if x[i] <= x[i - 1] {
return Err(InterpolateError::invalid_input(
"x must be strictly increasing".to_string(),
));
}
}
if d >= n {
return Err(InterpolateError::invalid_input(format!(
"blending order d = {} must be < n = {}",
d, n
)));
}
let w = compute_floater_hormann_weights(x, d)?;
Ok(Self {
x: x.to_owned(),
y: y.to_owned(),
w,
d,
})
}
pub fn evaluate(&self, xnew: F) -> InterpolateResult<F> {
let n = self.x.len();
for i in 0..n {
if (xnew - self.x[i]).abs() < F::epsilon() * (F::one() + self.x[i].abs()) {
return Ok(self.y[i]);
}
}
let mut numer = F::zero();
let mut denom = F::zero();
for i in 0..n {
let t = self.w[i] / (xnew - self.x[i]);
numer = numer + t * self.y[i];
denom = denom + t;
}
if denom.abs() < F::epsilon() {
return Err(InterpolateError::NumericalError(
"Barycentric denominator near zero".to_string(),
));
}
Ok(numer / denom)
}
pub fn evaluate_array(&self, xnew: &ArrayView1<F>) -> InterpolateResult<Array1<F>> {
let mut result = Array1::zeros(xnew.len());
for (i, &xi) in xnew.iter().enumerate() {
result[i] = self.evaluate(xi)?;
}
Ok(result)
}
pub fn evaluate_derivative(&self, xnew: F) -> InterpolateResult<F> {
let n = self.x.len();
for i in 0..n {
if (xnew - self.x[i]).abs() < F::epsilon() * (F::one() + self.x[i].abs()) {
return self.derivative_at_node(i);
}
}
let rx = self.evaluate(xnew)?;
let mut numer = F::zero();
let mut denom = F::zero();
for i in 0..n {
let diff = xnew - self.x[i];
let t = self.w[i] / diff;
denom = denom + t;
numer = numer + t * (rx - self.y[i]) / diff;
}
if denom.abs() < F::epsilon() {
return Err(InterpolateError::NumericalError(
"Barycentric denominator near zero in derivative".to_string(),
));
}
Ok(numer / denom)
}
fn derivative_at_node(&self, k: usize) -> InterpolateResult<F> {
let n = self.x.len();
if self.w[k].abs() < F::from_f64(1e-30).unwrap_or(F::epsilon()) {
let h = F::from_f64(1e-8).unwrap_or(F::epsilon());
let x_plus = self.x[k] + h;
let x_minus = self.x[k] - h;
let two = F::from_f64(2.0).unwrap_or(F::one() + F::one());
let f_plus = self.evaluate_away_from_node(x_plus)?;
let f_minus = self.evaluate_away_from_node(x_minus)?;
return Ok((f_plus - f_minus) / (two * h));
}
let mut result = F::zero();
for j in 0..n {
if j == k {
continue;
}
let diff = self.x[k] - self.x[j];
if diff.abs() < F::from_f64(1e-30).unwrap_or(F::epsilon()) {
continue;
}
result = result + (self.w[j] / self.w[k]) * (self.y[k] - self.y[j]) / diff;
}
Ok(-result)
}
fn evaluate_away_from_node(&self, xnew: F) -> InterpolateResult<F> {
let n = self.x.len();
let mut numer = F::zero();
let mut denom = F::zero();
for i in 0..n {
let diff = xnew - self.x[i];
if diff.abs() < F::from_f64(1e-30).unwrap_or(F::epsilon()) {
return Ok(self.y[i]);
}
let t = self.w[i] / diff;
numer = numer + t * self.y[i];
denom = denom + t;
}
if denom.abs() < F::from_f64(1e-30).unwrap_or(F::epsilon()) {
return Err(InterpolateError::NumericalError(
"Barycentric denominator near zero".to_string(),
));
}
Ok(numer / denom)
}
pub fn order(&self) -> usize {
self.d
}
pub fn weights(&self) -> &Array1<F> {
&self.w
}
pub fn x(&self) -> &Array1<F> {
&self.x
}
pub fn y(&self) -> &Array1<F> {
&self.y
}
}
fn compute_floater_hormann_weights<F: InterpolationFloat>(
x: &ArrayView1<F>,
d: usize,
) -> InterpolateResult<Array1<F>> {
let n = x.len();
let mut w = Array1::<F>::zeros(n);
for i in 0..n {
let k_min = if i > d { i - d } else { 0 };
let k_max_bound = if n > d { n - 1 - d } else { 0 };
let k_max = i.min(k_max_bound);
let mut sum = F::zero();
for k in k_min..=k_max {
let mut prod = F::one();
let end = (k + d).min(n - 1);
for j in k..=end {
if j != i {
let diff = x[i] - x[j];
if diff.abs() < F::from_f64(1e-30).unwrap_or(F::epsilon()) {
return Err(InterpolateError::NumericalError(
"Duplicate or nearly-duplicate x values".to_string(),
));
}
prod = prod * diff;
}
}
if prod.abs() < F::from_f64(1e-30).unwrap_or(F::epsilon()) {
continue;
}
let sign = if k % 2 == 0 { F::one() } else { -F::one() };
sum = sum + sign / prod;
}
w[i] = sum;
}
Ok(w)
}
pub fn auto_select_order<F: InterpolationFloat>(
x: &ArrayView1<F>,
y: &ArrayView1<F>,
max_d: Option<usize>,
) -> InterpolateResult<usize> {
let n = x.len();
if n < 2 {
return Ok(0);
}
let d_max = match max_d {
Some(d) => d.min(n - 1),
None => (n - 1).min(20),
};
let mut best_d = 0;
let mut best_err = F::infinity();
for d in 0..=d_max {
let err = loocv_error(x, y, d)?;
if err < best_err {
best_err = err;
best_d = d;
}
}
Ok(best_d)
}
fn loocv_error<F: InterpolationFloat>(
x: &ArrayView1<F>,
y: &ArrayView1<F>,
d: usize,
) -> InterpolateResult<F> {
let n = x.len();
if d >= n {
return Ok(F::infinity());
}
let mut total_err = F::zero();
for leave_out in 0..n {
let mut x_sub = Vec::with_capacity(n - 1);
let mut y_sub = Vec::with_capacity(n - 1);
for i in 0..n {
if i != leave_out {
x_sub.push(x[i]);
y_sub.push(y[i]);
}
}
let x_arr = Array1::from_vec(x_sub);
let y_arr = Array1::from_vec(y_sub);
let d_eff = d.min(n - 2);
match FloaterHormann::new(&x_arr.view(), &y_arr.view(), d_eff) {
Ok(interp) => {
match interp.evaluate(x[leave_out]) {
Ok(predicted) => {
let err = (predicted - y[leave_out]).abs();
total_err = total_err + err * err;
}
Err(_) => {
total_err = total_err + F::from_f64(1e10).unwrap_or(F::infinity());
}
}
}
Err(_) => {
total_err = total_err + F::from_f64(1e10).unwrap_or(F::infinity());
}
}
}
let n_f = F::from_usize(n)
.ok_or_else(|| InterpolateError::ComputationError("float conversion".to_string()))?;
Ok((total_err / n_f).sqrt())
}
pub fn make_floater_hormann_auto<F: InterpolationFloat>(
x: &ArrayView1<F>,
y: &ArrayView1<F>,
) -> InterpolateResult<FloaterHormann<F>> {
let d = auto_select_order(x, y, None)?;
FloaterHormann::new(x, y, d)
}
pub fn make_floater_hormann<F: InterpolationFloat>(
x: &ArrayView1<F>,
y: &ArrayView1<F>,
d: usize,
) -> InterpolateResult<FloaterHormann<F>> {
FloaterHormann::new(x, y, d)
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_abs_diff_eq;
use scirs2_core::ndarray::array;
#[test]
fn test_fh_interpolates_data_d0() {
let x = array![0.0, 1.0, 2.0, 3.0, 4.0];
let y = array![0.0, 1.0, 4.0, 9.0, 16.0];
let fh = FloaterHormann::new(&x.view(), &y.view(), 0).expect("construction failed");
for i in 0..x.len() {
let val = fh.evaluate(x[i]).expect("evaluate");
assert_abs_diff_eq!(val, y[i], epsilon = 1e-10);
}
}
#[test]
fn test_fh_interpolates_data_d1() {
let x = array![0.0, 1.0, 2.0, 3.0, 4.0];
let y = array![0.0, 1.0, 4.0, 9.0, 16.0];
let fh = FloaterHormann::new(&x.view(), &y.view(), 1).expect("construction failed");
for i in 0..x.len() {
let val = fh.evaluate(x[i]).expect("evaluate");
assert_abs_diff_eq!(val, y[i], epsilon = 1e-10);
}
}
#[test]
fn test_fh_interpolates_data_d2() {
let x = array![0.0, 1.0, 2.0, 3.0, 4.0];
let y = array![0.0, 1.0, 4.0, 9.0, 16.0]; let fh = FloaterHormann::new(&x.view(), &y.view(), 2).expect("construction failed");
for i in 0..x.len() {
let val = fh.evaluate(x[i]).expect("evaluate");
assert_abs_diff_eq!(val, y[i], epsilon = 1e-10);
}
}
#[test]
fn test_fh_high_order_accuracy() {
let x = array![0.0_f64, 1.0, 2.0, 3.0, 4.0];
let y = array![0.0_f64, 1.0, 4.0, 9.0, 16.0];
let fh = FloaterHormann::new(&x.view(), &y.view(), 3).expect("construction failed");
let val: f64 = fh.evaluate(2.5).expect("evaluate");
assert!((val - 6.25).abs() < 2.0, "Expected near 6.25, got {}", val);
}
#[test]
fn test_fh_between_points() {
let x = array![0.0_f64, 1.0, 2.0, 3.0];
let y = array![0.0_f64, 1.0, 4.0, 9.0];
let fh = FloaterHormann::new(&x.view(), &y.view(), 1).expect("construction failed");
let val: f64 = fh.evaluate(1.5).expect("evaluate");
assert!(val.is_finite(), "Expected finite value, got {}", val);
assert!((val - 2.25).abs() < 2.0, "Expected near 2.25, got {}", val);
}
#[test]
fn test_fh_length_mismatch() {
let x = array![0.0, 1.0, 2.0];
let y = array![0.0, 1.0];
assert!(FloaterHormann::new(&x.view(), &y.view(), 1).is_err());
}
#[test]
fn test_fh_unsorted() {
let x = array![0.0, 2.0, 1.0];
let y = array![0.0, 4.0, 1.0];
assert!(FloaterHormann::new(&x.view(), &y.view(), 1).is_err());
}
#[test]
fn test_fh_d_too_large() {
let x = array![0.0, 1.0, 2.0];
let y = array![0.0, 1.0, 4.0];
assert!(FloaterHormann::new(&x.view(), &y.view(), 3).is_err());
}
#[test]
fn test_fh_empty() {
let x = Array1::<f64>::zeros(0);
let y = Array1::<f64>::zeros(0);
assert!(FloaterHormann::new(&x.view(), &y.view(), 0).is_err());
}
#[test]
fn test_fh_single_point() {
let x = array![1.0];
let y = array![5.0];
let fh = FloaterHormann::new(&x.view(), &y.view(), 0).expect("construction");
let val = fh.evaluate(1.0).expect("evaluate");
assert_abs_diff_eq!(val, 5.0, epsilon = 1e-12);
}
#[test]
fn test_fh_derivative_linear() {
let x = array![0.0_f64, 1.0, 2.0, 3.0, 4.0];
let y = array![1.0_f64, 3.0, 5.0, 7.0, 9.0];
let fh = FloaterHormann::new(&x.view(), &y.view(), 1).expect("construction");
let xp = 2.5_f64;
let h = 1e-6_f64;
let f_plus: f64 = fh.evaluate(xp + h).expect("eval");
let f_minus: f64 = fh.evaluate(xp - h).expect("eval");
let fd_deriv = (f_plus - f_minus) / (2.0 * h);
let analytic: f64 = fh.evaluate_derivative(xp).expect("derivative");
assert_abs_diff_eq!(analytic, fd_deriv, epsilon = 0.1);
}
#[test]
fn test_fh_derivative_at_node() {
let x = array![0.0_f64, 1.0, 2.0, 3.0, 4.0];
let y = array![0.0_f64, 1.0, 4.0, 9.0, 16.0]; let fh = FloaterHormann::new(&x.view(), &y.view(), 2).expect("construction");
let deriv: f64 = fh.evaluate_derivative(2.0).expect("derivative");
assert!(
(deriv - 4.0).abs() < 2.0,
"Expected near 4.0, got {}",
deriv
);
}
#[test]
fn test_fh_derivative_between_nodes() {
let x = array![0.0_f64, 1.0, 2.0, 3.0, 4.0];
let y = array![0.0_f64, 1.0, 4.0, 9.0, 16.0]; let fh = FloaterHormann::new(&x.view(), &y.view(), 2).expect("construction");
let deriv: f64 = fh.evaluate_derivative(1.5).expect("derivative");
assert!(
deriv.is_finite(),
"Expected finite derivative, got {}",
deriv
);
}
#[test]
fn test_fh_derivative_constant() {
let x = array![0.0_f64, 1.0, 2.0, 3.0];
let y = array![5.0_f64, 5.0, 5.0, 5.0];
let fh = FloaterHormann::new(&x.view(), &y.view(), 2).expect("construction");
let deriv: f64 = fh.evaluate_derivative(1.5).expect("derivative");
assert_abs_diff_eq!(deriv, 0.0, epsilon = 1e-6);
}
#[test]
fn test_fh_derivative_finite() {
let x = array![0.0_f64, 1.0, 2.0, 3.0, 4.0];
let y = array![1.0_f64, 2.5, 0.5, 3.0, 1.5];
let fh = FloaterHormann::new(&x.view(), &y.view(), 2).expect("construction");
for k in 1..39 {
let xk = k as f64 * 0.1 + 0.05;
if xk >= 0.0 && xk <= 4.0 {
let d: f64 = fh.evaluate_derivative(xk).expect("derivative");
assert!(d.is_finite(), "Derivative not finite at x = {}", xk);
}
}
}
#[test]
fn test_auto_order_selects_valid() {
let x = array![0.0, 1.0, 2.0, 3.0, 4.0, 5.0];
let y = array![0.0, 1.0, 4.0, 9.0, 16.0, 25.0];
let d = auto_select_order(&x.view(), &y.view(), None).expect("auto_select");
assert!(d < x.len(), "Order must be < n");
}
#[test]
fn test_auto_order_with_max() {
let x = array![0.0, 1.0, 2.0, 3.0, 4.0, 5.0];
let y = array![0.0, 1.0, 4.0, 9.0, 16.0, 25.0];
let d = auto_select_order(&x.view(), &y.view(), Some(2)).expect("auto_select");
assert!(d <= 2);
}
#[test]
fn test_auto_order_two_points() {
let x = array![0.0, 1.0];
let y = array![0.0, 1.0];
let d = auto_select_order(&x.view(), &y.view(), None).expect("auto_select");
assert_eq!(d, 0);
}
#[test]
fn test_auto_order_smooth_data() {
let x = array![0.0_f64, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0];
let y: Array1<f64> = x.mapv(|xi: f64| xi * xi);
let d = auto_select_order(&x.view(), &y.view(), None).expect("auto_select");
assert!(d < x.len(), "Order must be < n");
}
#[test]
fn test_auto_order_noisy_data() {
let x = array![0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0];
let y = array![0.0, 1.5, 0.3, 2.8, 0.1, 3.2, 0.5, 2.0];
let d = auto_select_order(&x.view(), &y.view(), None).expect("auto_select");
assert!(d < x.len());
}
#[test]
fn test_make_floater_hormann() {
let x = array![0.0, 1.0, 2.0, 3.0, 4.0];
let y = array![0.0, 1.0, 4.0, 9.0, 16.0];
let fh = make_floater_hormann(&x.view(), &y.view(), 2).expect("construction");
let val = fh.evaluate(2.0).expect("evaluate");
assert_abs_diff_eq!(val, 4.0, epsilon = 1e-10);
}
#[test]
fn test_make_floater_hormann_auto() {
let x = array![0.0, 1.0, 2.0, 3.0, 4.0];
let y = array![0.0, 1.0, 4.0, 9.0, 16.0];
let fh = make_floater_hormann_auto(&x.view(), &y.view()).expect("construction");
let val = fh.evaluate(2.0).expect("evaluate");
assert_abs_diff_eq!(val, 4.0, epsilon = 1e-10);
assert!(fh.order() < x.len());
}
#[test]
fn test_evaluate_array() {
let x = array![0.0_f64, 1.0, 2.0, 3.0, 4.0];
let y = array![0.0_f64, 1.0, 4.0, 9.0, 16.0];
let fh = FloaterHormann::new(&x.view(), &y.view(), 2).expect("construction");
let xnew = array![0.5_f64, 1.5, 2.5, 3.5];
let vals = fh.evaluate_array(&xnew.view()).expect("evaluate_array");
assert_eq!(vals.len(), 4);
for v in vals.iter() {
assert!(v.is_finite());
}
}
#[test]
fn test_weights_all_finite() {
let x = array![0.0_f64, 1.0, 2.0, 3.0, 4.0];
let y = array![0.0_f64, 1.0, 4.0, 9.0, 16.0];
let fh = FloaterHormann::new(&x.view(), &y.view(), 2).expect("construction");
for i in 0..fh.weights().len() {
let w: f64 = fh.weights()[i];
assert!(w.is_finite(), "Weight {} should be finite, got {}", i, w);
}
}
#[test]
fn test_weights_d0_have_values() {
let x = array![0.0_f64, 1.0, 2.0, 3.0, 4.0];
let y = array![0.0_f64, 1.0, 4.0, 9.0, 16.0];
let fh = FloaterHormann::new(&x.view(), &y.view(), 0).expect("construction");
for i in 0..fh.weights().len() {
let w: f64 = fh.weights()[i];
assert!(
w.abs() > 1e-20,
"Weight {} should be nonzero for d=0, got {}",
i,
w
);
}
}
#[test]
fn test_order_accessor() {
let x = array![0.0, 1.0, 2.0, 3.0];
let y = array![0.0, 1.0, 4.0, 9.0];
let fh = FloaterHormann::new(&x.view(), &y.view(), 1).expect("construction");
assert_eq!(fh.order(), 1);
}
}