use crate::error::{InterpolateError, InterpolateResult};
#[inline]
fn dist2(ax: f64, ay: f64, bx: f64, by: f64) -> f64 {
(ax - bx) * (ax - bx) + (ay - by) * (ay - by)
}
#[inline]
fn dist(ax: f64, ay: f64, bx: f64, by: f64) -> f64 {
dist2(ax, ay, bx, by).sqrt()
}
#[derive(Debug, Clone)]
pub struct ShepardInterp {
pub px: Vec<f64>,
pub py: Vec<f64>,
pub values: Vec<f64>,
pub power: f64,
}
impl ShepardInterp {
pub fn new(
points: &[(f64, f64)],
values: &[f64],
power: f64,
) -> InterpolateResult<ShepardInterp> {
if points.len() != values.len() {
return Err(InterpolateError::ShapeMismatch {
expected: format!("{}", points.len()),
actual: format!("{}", values.len()),
object: "values".into(),
});
}
if power <= 0.0 {
return Err(InterpolateError::InvalidInput {
message: format!("power must be positive, got {}", power),
});
}
if points.is_empty() {
return Err(InterpolateError::InvalidInput {
message: "need at least one data point".into(),
});
}
let px = points.iter().map(|p| p.0).collect();
let py = points.iter().map(|p| p.1).collect();
Ok(ShepardInterp { px, py, values: values.to_vec(), power })
}
pub fn eval(&self, x: f64, y: f64) -> f64 {
let mut wsum = 0.0_f64;
let mut fwsum = 0.0_f64;
for i in 0..self.px.len() {
let d = dist(x, y, self.px[i], self.py[i]);
if d < 1e-15 {
return self.values[i];
}
let w = 1.0 / d.powf(self.power);
wsum += w;
fwsum += w * self.values[i];
}
if wsum == 0.0 {
return 0.0;
}
fwsum / wsum
}
}
#[derive(Debug, Clone)]
pub struct Triangulation {
pub vertices: Vec<(f64, f64)>,
pub triangles: Vec<[usize; 3]>,
pub adjacency: Vec<[Option<usize>; 3]>,
}
impl Triangulation {
pub fn build(points: &[(f64, f64)]) -> InterpolateResult<Triangulation> {
let n = points.len();
if n < 3 {
return Err(InterpolateError::InvalidInput {
message: format!("need at least 3 points for triangulation, got {}", n),
});
}
let (sx, sy, sr) = super_triangle(points);
let sv0 = n;
let sv1 = n + 1;
let sv2 = n + 2;
let mut verts: Vec<(f64, f64)> = points.to_vec();
verts.push((sx - sr, sy - sr));
verts.push((sx + sr + sr, sy));
verts.push((sx, sy + sr + sr));
let mut tris: Vec<[usize; 3]> = vec![[sv0, sv1, sv2]];
for pi in 0..n {
let px = verts[pi].0;
let py = verts[pi].1;
let mut bad: Vec<usize> = Vec::new();
for (ti, tri) in tris.iter().enumerate() {
if in_circumcircle(
verts[tri[0]],
verts[tri[1]],
verts[tri[2]],
(px, py),
) {
bad.push(ti);
}
}
let mut boundary: Vec<(usize, usize)> = Vec::new();
for &ti in &bad {
let tri = tris[ti];
let edges = [(tri[0], tri[1]), (tri[1], tri[2]), (tri[2], tri[0])];
for &(ea, eb) in &edges {
let shared = bad.iter().any(|&tj| {
if tj == ti {
return false;
}
let tj_tri = tris[tj];
triangle_has_edge(tj_tri, ea, eb)
});
if !shared {
boundary.push((ea, eb));
}
}
}
let mut bad_sorted = bad.clone();
bad_sorted.sort_unstable_by(|a, b| b.cmp(a));
for bi in bad_sorted {
tris.swap_remove(bi);
}
for (ea, eb) in boundary {
tris.push([ea, eb, pi]);
}
}
tris.retain(|tri| tri[0] < n && tri[1] < n && tri[2] < n);
for tri in &mut tris {
if !is_ccw(points[tri[0]], points[tri[1]], points[tri[2]]) {
tri.swap(1, 2);
}
}
let nt = tris.len();
let adjacency = compute_adjacency(&tris, nt);
Ok(Triangulation {
vertices: points.to_vec(),
triangles: tris,
adjacency,
})
}
}
fn super_triangle(pts: &[(f64, f64)]) -> (f64, f64, f64) {
let (mut xmin, mut ymin) = pts[0];
let (mut xmax, mut ymax) = pts[0];
for &(x, y) in pts {
xmin = xmin.min(x);
ymin = ymin.min(y);
xmax = xmax.max(x);
ymax = ymax.max(y);
}
let cx = (xmin + xmax) * 0.5;
let cy = (ymin + ymax) * 0.5;
let r = ((xmax - xmin).powi(2) + (ymax - ymin).powi(2)).sqrt() + 1.0;
(cx, cy, r * 2.0)
}
fn in_circumcircle(a: (f64, f64), b: (f64, f64), c: (f64, f64), p: (f64, f64)) -> bool {
let ax = a.0 - p.0;
let ay = a.1 - p.1;
let bx = b.0 - p.0;
let by = b.1 - p.1;
let cx = c.0 - p.0;
let cy = c.1 - p.1;
let det = ax * (by * (cx * cx + cy * cy) - cy * (bx * bx + by * by))
- ay * (bx * (cx * cx + cy * cy) - cx * (bx * bx + by * by))
+ (ax * ax + ay * ay) * (bx * cy - by * cx);
det > 0.0
}
fn triangle_has_edge(tri: [usize; 3], ea: usize, eb: usize) -> bool {
let e = [(tri[0], tri[1]), (tri[1], tri[2]), (tri[2], tri[0])];
e.iter()
.any(|&(a, b)| (a == ea && b == eb) || (a == eb && b == ea))
}
fn is_ccw(a: (f64, f64), b: (f64, f64), c: (f64, f64)) -> bool {
(b.0 - a.0) * (c.1 - a.1) - (b.1 - a.1) * (c.0 - a.0) > 0.0
}
fn compute_adjacency(tris: &[[usize; 3]], nt: usize) -> Vec<[Option<usize>; 3]> {
let mut adj: Vec<[Option<usize>; 3]> = vec![[None; 3]; nt];
for ti in 0..nt {
let edges_ti = [
(tris[ti][0], tris[ti][1]),
(tris[ti][1], tris[ti][2]),
(tris[ti][2], tris[ti][0]),
];
for (ei, &(ea, eb)) in edges_ti.iter().enumerate() {
if adj[ti][ei].is_some() {
continue;
}
for tj in 0..nt {
if tj == ti {
continue;
}
let edges_tj = [
(tris[tj][0], tris[tj][1]),
(tris[tj][1], tris[tj][2]),
(tris[tj][2], tris[tj][0]),
];
for (ej, &(fa, fb)) in edges_tj.iter().enumerate() {
if (ea == fa && eb == fb) || (ea == fb && eb == fa) {
adj[ti][ei] = Some(tj);
adj[tj][ej] = Some(ti);
}
}
}
}
}
adj
}
fn barycentric(
p: (f64, f64),
a: (f64, f64),
b: (f64, f64),
c: (f64, f64),
) -> (f64, f64, f64) {
let denom = (b.1 - c.1) * (a.0 - c.0) + (c.0 - b.0) * (a.1 - c.1);
if denom.abs() < 1e-15 {
return (1.0 / 3.0, 1.0 / 3.0, 1.0 / 3.0);
}
let u = ((b.1 - c.1) * (p.0 - c.0) + (c.0 - b.0) * (p.1 - c.1)) / denom;
let v = ((c.1 - a.1) * (p.0 - c.0) + (a.0 - c.0) * (p.1 - c.1)) / denom;
let w = 1.0 - u - v;
(u, v, w)
}
#[derive(Debug, Clone)]
pub struct LinearTriangulationInterp {
pub triangulation: Triangulation,
pub values: Vec<f64>,
}
impl LinearTriangulationInterp {
pub fn new(
points: &[(f64, f64)],
values: &[f64],
) -> InterpolateResult<LinearTriangulationInterp> {
if points.len() != values.len() {
return Err(InterpolateError::ShapeMismatch {
expected: format!("{}", points.len()),
actual: format!("{}", values.len()),
object: "values".into(),
});
}
let triangulation = Triangulation::build(points)?;
Ok(LinearTriangulationInterp {
triangulation,
values: values.to_vec(),
})
}
pub fn eval(&self, x: f64, y: f64) -> Option<f64> {
let p = (x, y);
let verts = &self.triangulation.vertices;
for tri in &self.triangulation.triangles {
let a = verts[tri[0]];
let b = verts[tri[1]];
let c = verts[tri[2]];
let (u, v, w) = barycentric(p, a, b, c);
let eps = -1e-10;
if u >= eps && v >= eps && w >= eps {
let val = u * self.values[tri[0]]
+ v * self.values[tri[1]]
+ w * self.values[tri[2]];
return Some(val);
}
}
None
}
}
#[derive(Debug, Clone)]
pub struct NaturalNeighborInterp {
pub px: Vec<f64>,
pub py: Vec<f64>,
pub values: Vec<f64>,
}
impl NaturalNeighborInterp {
pub fn new(
points: &[(f64, f64)],
values: &[f64],
) -> InterpolateResult<NaturalNeighborInterp> {
if points.len() != values.len() {
return Err(InterpolateError::ShapeMismatch {
expected: format!("{}", points.len()),
actual: format!("{}", values.len()),
object: "values".into(),
});
}
if points.len() < 1 {
return Err(InterpolateError::InvalidInput {
message: "need at least one data point".into(),
});
}
let px = points.iter().map(|p| p.0).collect();
let py = points.iter().map(|p| p.1).collect();
Ok(NaturalNeighborInterp { px, py, values: values.to_vec() })
}
pub fn eval(&self, x: f64, y: f64) -> f64 {
let n = self.px.len();
if n == 1 {
return self.values[0];
}
let mut weights = vec![0.0_f64; n];
let mut total = 0.0_f64;
for i in 0..n {
let d2 = dist2(x, y, self.px[i], self.py[i]);
if d2 < 1e-28 {
return self.values[i]; }
let w = 1.0 / d2;
weights[i] = w;
total += w;
}
if total == 0.0 {
return self.values[0];
}
(0..n).map(|i| weights[i] * self.values[i]).sum::<f64>() / total
}
}
#[cfg(test)]
mod tests {
use super::*;
fn four_corners() -> (Vec<(f64, f64)>, Vec<f64>) {
let pts = vec![(0.0, 0.0), (1.0, 0.0), (0.0, 1.0), (1.0, 1.0), (0.5, 0.5)];
let vals: Vec<f64> = pts.iter().map(|(x, y)| x + y).collect();
(pts, vals)
}
#[test]
fn test_shepard_exact_at_data() {
let (pts, vals) = four_corners();
let s = ShepardInterp::new(&pts, &vals, 2.0).expect("new failed");
for (i, &(px, py)) in pts.iter().enumerate() {
let v = s.eval(px, py);
assert!((v - vals[i]).abs() < 1e-12, "Shepard exact at ({},{}) {} {}", px, py, v, vals[i]);
}
}
#[test]
fn test_shepard_linear_exactness() {
let pts = vec![(0.0, 0.0), (1.0, 0.0), (0.0, 1.0), (1.0, 1.0)];
let vals: Vec<f64> = pts.iter().map(|(x, y)| x + y).collect();
let s = ShepardInterp::new(&pts, &vals, 2.0).expect("new");
let v = s.eval(0.5, 0.5);
assert!((v - 1.0).abs() < 0.15, "Shepard center: {} != 1.0", v);
}
#[test]
fn test_shepard_error_on_mismatch() {
let pts = vec![(0.0, 0.0), (1.0, 0.0)];
let vals = vec![0.0_f64]; assert!(ShepardInterp::new(&pts, &vals, 2.0).is_err());
}
#[test]
fn test_shepard_error_on_nonpositive_power() {
let pts = vec![(0.0, 0.0), (1.0, 0.0)];
let vals = vec![0.0_f64, 1.0];
assert!(ShepardInterp::new(&pts, &vals, 0.0).is_err());
assert!(ShepardInterp::new(&pts, &vals, -1.0).is_err());
}
#[test]
fn test_triangulation_builds() {
let pts = vec![(0.0, 0.0), (1.0, 0.0), (0.0, 1.0), (1.0, 1.0)];
let tri = Triangulation::build(&pts).expect("build");
assert!(tri.triangles.len() >= 2, "expected ≥2 triangles");
}
#[test]
fn test_triangulation_error_on_few_points() {
assert!(Triangulation::build(&[(0.0, 0.0), (1.0, 0.0)]).is_err());
}
#[test]
fn test_linear_tri_exact_at_data() {
let pts = vec![(0.0, 0.0), (1.0, 0.0), (0.0, 1.0), (1.0, 1.0)];
let vals: Vec<f64> = pts.iter().map(|(x, y)| x + y).collect();
let interp = LinearTriangulationInterp::new(&pts, &vals).expect("new");
for (i, &(px, py)) in pts.iter().enumerate() {
let v = interp.eval(px, py);
assert!(
v.is_some() && (v.expect("test: should succeed") - vals[i]).abs() < 1e-10,
"linear tri exact at ({},{}) {:?} vs {}",
px, py, v, vals[i]
);
}
}
#[test]
fn test_linear_tri_outside_returns_none() {
let pts = vec![(0.0, 0.0), (1.0, 0.0), (0.5, 1.0)];
let vals = vec![0.0_f64, 1.0, 0.5];
let interp = LinearTriangulationInterp::new(&pts, &vals).expect("new");
let v = interp.eval(5.0, 5.0); assert!(v.is_none(), "expected None outside hull, got {:?}", v);
}
#[test]
fn test_linear_tri_linear_exactness() {
let pts = vec![
(0.0, 0.0), (1.0, 0.0), (0.0, 1.0), (1.0, 1.0), (0.5, 0.5),
];
let vals: Vec<f64> = pts.iter().map(|(x, y)| 2.0 * x + 3.0 * y).collect();
let interp = LinearTriangulationInterp::new(&pts, &vals).expect("new");
let test_pts = [(0.3, 0.3), (0.7, 0.2), (0.2, 0.7)];
for &(tx, ty) in &test_pts {
let v = interp.eval(tx, ty);
if let Some(val) = v {
let exact = 2.0 * tx + 3.0 * ty;
assert!((val - exact).abs() < 1e-9, "linear at ({},{}) {} vs {}", tx, ty, val, exact);
}
}
}
#[test]
fn test_natural_neighbor_exact_at_data() {
let (pts, vals) = four_corners();
let nn = NaturalNeighborInterp::new(&pts, &vals).expect("new");
for (i, &(px, py)) in pts.iter().enumerate() {
let v = nn.eval(px, py);
assert!((v - vals[i]).abs() < 1e-12, "NN exact at ({},{}) {} vs {}", px, py, v, vals[i]);
}
}
#[test]
fn test_natural_neighbor_error_on_mismatch() {
let pts = vec![(0.0, 0.0), (1.0, 0.0)];
let vals = vec![0.0_f64]; assert!(NaturalNeighborInterp::new(&pts, &vals).is_err());
}
#[test]
fn test_barycentric_center() {
let a = (0.0, 0.0);
let b = (1.0, 0.0);
let c = (0.0, 1.0);
let centroid = (1.0 / 3.0, 1.0 / 3.0);
let (u, v, w) = barycentric(centroid, a, b, c);
assert!((u - 1.0 / 3.0).abs() < 1e-10);
assert!((v - 1.0 / 3.0).abs() < 1e-10);
assert!((w - 1.0 / 3.0).abs() < 1e-10);
}
}