#[cfg(not(feature = "std"))]
use alloc::vec;
#[must_use]
pub fn lagrange_interpolate(xs: &[f64], ys: &[f64], t: f64) -> f64 {
assert_eq!(
xs.len(),
ys.len(),
"lagrange: x and y must have same length"
);
assert!(
!xs.is_empty(),
"lagrange: must have at least one data point"
);
let n = xs.len();
let mut result = 0.0;
for i in 0..n {
let mut basis = 1.0;
for j in 0..n {
if i != j {
basis *= (t - xs[j]) / (xs[i] - xs[j]);
}
}
result += ys[i] * basis;
}
result
}
#[must_use]
#[allow(clippy::many_single_char_names, clippy::needless_range_loop)]
pub fn hermite_interpolate(xs: &[f64], ys: &[f64], dys: &[f64], t: f64) -> (f64, f64) {
assert_eq!(xs.len(), ys.len(), "hermite: x and y must have same length");
assert_eq!(
xs.len(),
dys.len(),
"hermite: x and dy must have same length"
);
assert!(!xs.is_empty(), "hermite: must have at least one knot point");
let n = xs.len();
if n == 1 {
let dt = t - xs[0];
return (ys[0] + dys[0] * dt, dys[0]);
}
let m = 2 * n;
let mut z = vec![0.0_f64; m];
let mut q = vec![vec![0.0_f64; m]; m];
for i in 0..n {
z[2 * i] = xs[i];
z[2 * i + 1] = xs[i];
q[2 * i][0] = ys[i];
q[2 * i + 1][0] = ys[i];
q[2 * i + 1][1] = dys[i];
if i > 0 {
q[2 * i][1] = (q[2 * i][0] - q[2 * i - 1][0]) / (z[2 * i] - z[2 * i - 1]);
}
}
for j in 2..m {
for i in j..m {
if (z[i] - z[i - j]).abs() < f64::EPSILON {
q[i][j] = 0.0;
} else {
q[i][j] = (q[i][j - 1] - q[i - 1][j - 1]) / (z[i] - z[i - j]);
}
}
}
let mut pos = q[0][0];
let mut product = 1.0_f64;
for i in 1..m {
product *= t - z[i - 1];
pos += q[i][i] * product;
}
let mut vel = 0.0_f64;
#[allow(clippy::needless_range_loop)]
for i in 1..m {
let mut sum = 0.0_f64;
for k in 0..i {
let mut prod = 1.0_f64;
for j in 0..i {
if j != k {
prod *= t - z[j];
}
}
sum += prod;
}
vel += q[i][i] * sum;
}
(pos, vel)
}
#[cfg(test)]
mod tests {
use super::*;
const EPS: f64 = 1e-12;
#[test]
fn lagrange_linear() {
let xs = [0.0, 1.0];
let ys = [1.0, 3.0];
let result = lagrange_interpolate(&xs, &ys, 0.5);
assert!((result - 2.0).abs() < EPS, "expected 2.0, got {result}");
}
#[test]
fn lagrange_quadratic() {
let xs = [0.0, 1.0, 2.0];
let ys = [0.0, 1.0, 4.0];
let result = lagrange_interpolate(&xs, &ys, 0.5);
assert!((result - 0.25).abs() < EPS, "expected 0.25, got {result}");
}
#[test]
fn lagrange_exact_at_knots() {
let xs = [0.0, 1.0, 2.0, 3.0];
let ys: Vec<f64> = xs.iter().map(|&v| v * v * v).collect();
for (&xi, &yi) in xs.iter().zip(ys.iter()) {
let result = lagrange_interpolate(&xs, &ys, xi);
assert!(
(result - yi).abs() < EPS,
"at x={xi}: expected {yi}, got {result}"
);
}
}
#[test]
fn lagrange_cubic() {
let xs = [0.0, 1.0, 2.0, 3.0];
let ys: Vec<f64> = xs.iter().map(|&v| v * v * v).collect();
let result = lagrange_interpolate(&xs, &ys, 2.5);
assert!(
(result - 15.625).abs() < 1e-10,
"expected 15.625, got {result}"
);
}
#[test]
fn hermite_linear() {
let xs = [0.0, 1.0];
let ys = [1.0, 4.0];
let dys = [3.0, 3.0];
let (pos, vel) = hermite_interpolate(&xs, &ys, &dys, 0.5);
assert!((pos - 2.5).abs() < EPS, "expected pos=2.5, got {pos}");
assert!((vel - 3.0).abs() < EPS, "expected vel=3.0, got {vel}");
}
#[test]
fn hermite_quadratic() {
let xs = [0.0, 2.0];
let ys = [0.0, 4.0];
let dys = [0.0, 4.0];
let (pos, vel) = hermite_interpolate(&xs, &ys, &dys, 1.0);
assert!((pos - 1.0).abs() < EPS, "expected pos=1.0, got {pos}");
assert!((vel - 2.0).abs() < EPS, "expected vel=2.0, got {vel}");
}
#[test]
fn hermite_exact_at_knots() {
let xs = [0.0, 1.0, 2.0];
let ys: Vec<f64> = xs.iter().map(|&v| v * v * v).collect();
let dys: Vec<f64> = xs.iter().map(|&v| 3.0 * v * v).collect();
for (&xi, &yi) in xs.iter().zip(ys.iter()) {
let (pos, _) = hermite_interpolate(&xs, &ys, &dys, xi);
assert!(
(pos - yi).abs() < 1e-10,
"at x={xi}: expected {yi}, got {pos}"
);
}
}
#[test]
fn hermite_cubic() {
let xs = [0.0, 1.0];
let ys = [0.0, 1.0];
let dys = [0.0, 3.0];
let (pos, vel) = hermite_interpolate(&xs, &ys, &dys, 0.5);
assert!((pos - 0.125).abs() < EPS, "expected pos=0.125, got {pos}");
assert!((vel - 0.75).abs() < EPS, "expected vel=0.75, got {vel}");
}
#[test]
fn hermite_single_knot() {
let xs = [2.0];
let ys = [5.0];
let dys = [3.0];
let (pos, vel) = hermite_interpolate(&xs, &ys, &dys, 4.0);
assert!((pos - 11.0).abs() < EPS, "expected pos=11.0, got {pos}");
assert!((vel - 3.0).abs() < EPS, "expected vel=3.0, got {vel}");
}
}