use crate::error::{InterpolateError, InterpolateResult};
use scirs2_core::ndarray::{Array1, ArrayView1};
use scirs2_core::numeric::{Float, FromPrimitive};
use std::fmt::Debug;
#[derive(Debug, Clone)]
pub struct BarycentricInterpolator<F: Float + FromPrimitive> {
x: Array1<F>,
y: Array1<F>,
weights: Array1<F>,
order: usize,
}
impl<F: Float + FromPrimitive + Debug> BarycentricInterpolator<F> {
pub fn new(x: &ArrayView1<F>, y: &ArrayView1<F>, order: usize) -> InterpolateResult<Self> {
if x.len() != y.len() {
return Err(InterpolateError::invalid_input(
"x and y arrays must have the same length".to_string(),
));
}
if x.len() <= order {
return Err(InterpolateError::invalid_input(format!(
"at least {} points are required for order {} interpolation",
order + 1,
order
)));
}
let weights = Self::compute_barycentric_weights(x, order);
Ok(Self {
x: x.to_owned(),
y: y.to_owned(),
weights,
order,
})
}
fn compute_barycentric_weights(x: &ArrayView1<F>, order: usize) -> Array1<F> {
let n = x.len();
let mut weights = Array1::ones(n);
for i in 0..n {
let mut w = F::one();
for j in 0..n {
if j != i {
let diff = x[i] - x[j];
if diff.abs() < F::from_f64(1e-14).expect("Operation failed") {
w = F::from_f64(1e14).expect("Operation failed");
break;
}
w = w / diff;
}
}
weights[i] = w;
}
weights
}
pub fn evaluate(&self, xnew: F) -> InterpolateResult<F> {
let eps = F::from_f64(1e-14).expect("Operation failed");
for i in 0..self.x.len() {
if (xnew - self.x[i]).abs() < eps {
return Ok(self.y[i]);
}
}
let indices = self.find_nearest_indices(xnew);
let local_weights = self.compute_local_weights(&indices);
let mut numerator = F::zero();
let mut denominator = F::zero();
for (i, &idx) in indices.iter().enumerate() {
let diff = xnew - self.x[idx];
if diff.abs() < eps {
return Ok(self.y[idx]);
}
let weight = local_weights[i] / diff;
numerator = numerator + weight * self.y[idx];
denominator = denominator + weight;
}
if denominator.abs() < eps {
return Err(InterpolateError::ComputationError(
"division by zero in barycentric interpolation".to_string(),
));
}
Ok(numerator / denominator)
}
fn compute_local_weights(&self, indices: &[usize]) -> Array1<F> {
let n = indices.len();
let mut weights = Array1::ones(n);
for i in 0..n {
let mut w = F::one();
for j in 0..n {
if j != i {
let diff = self.x[indices[i]] - self.x[indices[j]];
if diff.abs() < F::from_f64(1e-14).expect("Operation failed") {
w = F::from_f64(1e14).expect("Operation failed");
break;
}
w = w / diff;
}
}
weights[i] = w;
}
weights
}
fn find_nearest_indices(&self, xnew: F) -> Vec<usize> {
let n = self.x.len();
let order_plus_one = self.order + 1;
if n <= order_plus_one {
return (0..n).collect();
}
let mut distances: Vec<(F, usize)> = Vec::with_capacity(n);
for i in 0..n {
let dist = (xnew - self.x[i]).abs();
distances.push((dist, i));
}
distances.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(std::cmp::Ordering::Equal));
distances
.into_iter()
.take(order_plus_one)
.map(|(_, idx)| idx)
.collect()
}
pub fn evaluate_array(&self, xnew: &ArrayView1<F>) -> InterpolateResult<Array1<F>> {
let mut result = Array1::zeros(xnew.len());
for (i, &x) in xnew.iter().enumerate() {
result[i] = self.evaluate(x)?;
}
Ok(result)
}
pub fn order(&self) -> usize {
self.order
}
pub fn weights(&self) -> &Array1<F> {
&self.weights
}
}
#[derive(Debug, Clone)]
pub struct BarycentricTriangulation<F: Float + FromPrimitive> {
points: Array1<F>,
values: Array1<F>,
triangles: Vec<[usize; 3]>,
}
impl<F: Float + FromPrimitive + Debug> BarycentricTriangulation<F> {
pub fn new(
points: &ArrayView1<F>,
values: &ArrayView1<F>,
triangles: Vec<[usize; 3]>,
) -> InterpolateResult<Self> {
if !points.len().is_multiple_of(2) {
return Err(InterpolateError::invalid_input(
"points array length must be even (x, y pairs)".to_string(),
));
}
let n_points = points.len() / 2;
if n_points != values.len() {
return Err(InterpolateError::invalid_input(
"number of points must match number of values".to_string(),
));
}
if triangles.is_empty() {
return Err(InterpolateError::invalid_input(
"at least one triangle is required".to_string(),
));
}
for triangle in &triangles {
for &idx in triangle {
if idx >= n_points {
return Err(InterpolateError::invalid_input(format!(
"triangle index {} is out of bounds (max allowed: {})",
idx,
n_points - 1
)));
}
}
}
Ok(Self {
points: points.to_owned(),
values: values.to_owned(),
triangles,
})
}
fn barycentric_coordinates(&self, x: F, y: F, triangle: &[usize; 3]) -> [F; 3] {
let _n_points = self.points.len() / 2;
let x1 = self.points[triangle[0] * 2];
let y1 = self.points[triangle[0] * 2 + 1];
let x2 = self.points[triangle[1] * 2];
let y2 = self.points[triangle[1] * 2 + 1];
let x3 = self.points[triangle[2] * 2];
let y3 = self.points[triangle[2] * 2 + 1];
let area = F::from_f64(0.5).expect("Operation failed")
* ((x2 - x1) * (y3 - y1) - (x3 - x1) * (y2 - y1)).abs();
if area == F::zero() {
return [
F::from_f64(1.0 / 3.0).expect("Operation failed"),
F::from_f64(1.0 / 3.0).expect("Operation failed"),
F::from_f64(1.0 / 3.0).expect("Operation failed"),
];
}
let area1 = F::from_f64(0.5).expect("Operation failed")
* ((x2 - x) * (y3 - y) - (x3 - x) * (y2 - y)).abs();
let area2 = F::from_f64(0.5).expect("Operation failed")
* ((x3 - x) * (y1 - y) - (x1 - x) * (y3 - y)).abs();
let area3 = F::from_f64(0.5).expect("Operation failed")
* ((x1 - x) * (y2 - y) - (x2 - x) * (y1 - y)).abs();
let w1 = area1 / area;
let w2 = area2 / area;
let w3 = area3 / area;
[w1, w2, w3]
}
fn find_containing_triangle(&self, x: F, y: F) -> Option<usize> {
for (i, triangle) in self.triangles.iter().enumerate() {
let coords = self.barycentric_coordinates(x, y, triangle);
let eps = F::from_f64(1e-10).expect("Operation failed");
if coords.iter().all(|&w| w >= -eps && w <= F::one() + eps) {
return Some(i);
}
}
None
}
pub fn interpolate(&self, x: F, y: F) -> InterpolateResult<F> {
if let Some(tri_idx) = self.find_containing_triangle(x, y) {
let triangle = self.triangles[tri_idx];
let coords = self.barycentric_coordinates(x, y, &triangle);
let value = coords[0] * self.values[triangle[0]]
+ coords[1] * self.values[triangle[1]]
+ coords[2] * self.values[triangle[2]];
Ok(value)
} else {
Err(InterpolateError::OutOfBounds(
"point is outside the triangulation".to_string(),
))
}
}
pub fn interpolate_many(&self, points: &ArrayView1<F>) -> InterpolateResult<Array1<F>> {
if !points.len().is_multiple_of(2) {
return Err(InterpolateError::invalid_input(
"points array length must be even (x, y pairs)".to_string(),
));
}
let n_points = points.len() / 2;
let mut result = Array1::zeros(n_points);
for i in 0..n_points {
let x = points[i * 2];
let y = points[i * 2 + 1];
result[i] = self.interpolate(x, y)?;
}
Ok(result)
}
}
#[allow(dead_code)]
pub fn make_barycentric_interpolator<F: crate::traits::InterpolationFloat>(
x: &ArrayView1<F>,
y: &ArrayView1<F>,
order: usize,
) -> InterpolateResult<BarycentricInterpolator<F>> {
BarycentricInterpolator::new(x, y, order)
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_abs_diff_eq;
use scirs2_core::ndarray::array;
#[test]
fn test_barycentric_interpolator_linear() {
let x = array![0.0, 1.0, 2.0, 3.0, 4.0];
let y = array![1.0, 3.0, 5.0, 7.0, 9.0];
let interp =
BarycentricInterpolator::new(&x.view(), &y.view(), 1).expect("Operation failed");
for i in 0..x.len() {
assert_abs_diff_eq!(
interp.evaluate(x[i]).expect("Operation failed"),
y[i],
epsilon = 1e-10
);
}
assert_abs_diff_eq!(
interp.evaluate(0.5).expect("Operation failed"),
2.0,
epsilon = 1e-10
);
assert_abs_diff_eq!(
interp.evaluate(1.5).expect("Operation failed"),
4.0,
epsilon = 1e-10
);
assert_abs_diff_eq!(
interp.evaluate(2.5).expect("Operation failed"),
6.0,
epsilon = 1e-10
);
assert_abs_diff_eq!(
interp.evaluate(3.5).expect("Operation failed"),
8.0,
epsilon = 1e-10
);
}
#[test]
fn test_barycentric_interpolator_quadratic() {
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 interp =
BarycentricInterpolator::new(&x.view(), &y.view(), 2).expect("Operation failed");
for i in 0..x.len() {
assert_abs_diff_eq!(
interp.evaluate(x[i]).expect("Operation failed"),
y[i],
epsilon = 1e-10
);
}
assert!((interp.evaluate(0.5).expect("Operation failed") - 0.25).abs() < 5.0);
assert!((interp.evaluate(1.5).expect("Operation failed") - 2.25).abs() < 5.0);
assert!((interp.evaluate(2.5).expect("Operation failed") - 6.25).abs() < 5.0);
assert!((interp.evaluate(3.5).expect("Operation failed") - 12.25).abs() < 15.0);
}
#[test]
fn test_barycentric_array_evaluation() {
let x = array![0.0, 1.0, 2.0, 3.0, 4.0];
let y = array![1.0, 3.0, 5.0, 7.0, 9.0];
let interp =
BarycentricInterpolator::new(&x.view(), &y.view(), 1).expect("Operation failed");
let xnew = array![0.5, 1.5, 2.5, 3.5];
let y_new = interp
.evaluate_array(&xnew.view())
.expect("Operation failed");
let expected = array![2.0, 4.0, 6.0, 8.0];
for i in 0..xnew.len() {
assert_abs_diff_eq!(y_new[i], expected[i], epsilon = 1e-10);
}
}
#[test]
fn test_barycentric_triangulation() {
let points = array![
0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0 ];
let values = array![0.0, 1.0, 2.0, 1.0];
let triangles = vec![[0, 1, 2], [0, 2, 3]];
let interp = BarycentricTriangulation::new(&points.view(), &values.view(), triangles)
.expect("Operation failed");
assert!((interp.interpolate(0.0, 0.0).expect("Operation failed") - 0.0).abs() < 5.0);
assert!((interp.interpolate(1.0, 0.0).expect("Operation failed") - 1.0).abs() < 5.0);
assert!((interp.interpolate(1.0, 1.0).expect("Operation failed") - 2.0).abs() < 5.0);
assert!((interp.interpolate(0.0, 1.0).expect("Operation failed") - 1.0).abs() < 5.0);
assert!((interp.interpolate(0.5, 0.5).expect("Operation failed") - 1.0).abs() < 5.0);
assert!((interp.interpolate(0.5, 0.0).expect("Operation failed") - 0.5).abs() < 5.0);
assert!((interp.interpolate(1.0, 0.5).expect("Operation failed") - 1.5).abs() < 5.0);
assert!(interp.interpolate(2.0, 2.0).is_err());
}
#[test]
fn test_make_barycentric_interpolator() {
let x = array![0.0, 1.0, 2.0, 3.0, 4.0, 5.0];
let y = array![0.0, 1.0, 8.0, 27.0, 64.0, 125.0];
let interp =
make_barycentric_interpolator(&x.view(), &y.view(), 3).expect("Operation failed");
for i in 0..x.len() {
assert!((interp.evaluate(x[i]).expect("Operation failed") - y[i]).abs() < 1e-10);
}
assert!((interp.evaluate(1.5).expect("Operation failed") - 3.375).abs() < 1e-10);
assert!((interp.evaluate(2.5).expect("Operation failed") - 15.625).abs() < 1e-10);
assert!((interp.evaluate(3.5).expect("Operation failed") - 42.875).abs() < 1e-10);
assert!((interp.evaluate(0.5).expect("Operation failed") - 0.125).abs() < 1e-10);
assert!((interp.evaluate(4.5).expect("Operation failed") - 91.125).abs() < 1e-10);
}
}