#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Interp {
Linear,
Cubic,
}
pub fn interp_linear(xq: f64, xp: &[f64], fp: &[f64]) -> f64 {
let n = xp.len();
debug_assert_eq!(n, fp.len());
if n == 0 {
return f64::NAN;
}
if xq <= xp[0] {
return fp[0];
}
if xq >= xp[n - 1] {
return fp[n - 1];
}
let i = match xp.binary_search_by(|v| v.partial_cmp(&xq).unwrap()) {
Ok(i) => return fp[i],
Err(i) => i,
};
let (x0, x1) = (xp[i - 1], xp[i]);
let (y0, y1) = (fp[i - 1], fp[i]);
y0 + (y1 - y0) * (xq - x0) / (x1 - x0)
}
#[derive(Debug, Clone)]
pub struct CubicSpline {
x: Vec<f64>,
a: Vec<f64>,
b: Vec<f64>,
c: Vec<f64>,
d: Vec<f64>,
}
impl CubicSpline {
pub fn new(x: &[f64], y: &[f64]) -> Self {
assert_eq!(x.len(), y.len(), "x and y length mismatch");
let n = x.len();
assert!(n >= 2, "need at least 2 points");
let h: Vec<f64> = (0..n - 1).map(|i| x[i + 1] - x[i]).collect();
let m = if n < 4 {
natural_second_derivs(&h, y, n)
} else {
not_a_knot_second_derivs(&h, y, n)
};
let mut a = vec![0.0; n - 1];
let mut b = vec![0.0; n - 1];
let mut c = vec![0.0; n - 1];
let mut d = vec![0.0; n - 1];
for i in 0..n - 1 {
a[i] = y[i];
b[i] = (y[i + 1] - y[i]) / h[i] - h[i] * (2.0 * m[i] + m[i + 1]) / 6.0;
c[i] = m[i] / 2.0;
d[i] = (m[i + 1] - m[i]) / (6.0 * h[i]);
}
CubicSpline {
x: x.to_vec(),
a,
b,
c,
d,
}
}
pub fn eval(&self, t: f64) -> f64 {
let nseg = self.a.len();
let i = match self.x.binary_search_by(|v| v.partial_cmp(&t).unwrap()) {
Ok(i) => i.min(nseg - 1),
Err(0) => 0,
Err(pos) => (pos - 1).min(nseg - 1),
};
let dx = t - self.x[i];
((self.d[i] * dx + self.c[i]) * dx + self.b[i]) * dx + self.a[i]
}
}
fn natural_second_derivs(h: &[f64], y: &[f64], n: usize) -> Vec<f64> {
let mut m = vec![0.0; n];
if n < 3 {
return m; }
let mut sub = vec![0.0; n];
let mut diag = vec![0.0; n];
let mut sup = vec![0.0; n];
let mut rhs = vec![0.0; n];
diag[0] = 1.0;
diag[n - 1] = 1.0;
for i in 1..n - 1 {
sub[i] = h[i - 1];
diag[i] = 2.0 * (h[i - 1] + h[i]);
sup[i] = h[i];
rhs[i] = 6.0 * ((y[i + 1] - y[i]) / h[i] - (y[i] - y[i - 1]) / h[i - 1]);
}
thomas(&sub, &diag, &sup, &rhs, &mut m);
m
}
fn not_a_knot_second_derivs(h: &[f64], y: &[f64], n: usize) -> Vec<f64> {
let nint = n - 2; let mut sub = vec![0.0; nint];
let mut diag = vec![0.0; nint];
let mut sup = vec![0.0; nint];
let mut rhs = vec![0.0; nint];
let slope = |i: usize| (y[i + 1] - y[i]) / h[i];
for k in 0..nint {
let i = k + 1; let lo = h[i - 1];
let hi = h[i];
sub[k] = lo;
diag[k] = 2.0 * (lo + hi);
sup[k] = hi;
rhs[k] = 6.0 * (slope(i) - slope(i - 1));
}
let r = h[0] / h[1];
diag[0] += h[0] * (1.0 + r); if nint >= 2 {
sup[0] += -h[0] * r; }
sub[0] = 0.0;
let rl = h[n - 2] / h[n - 3];
let last = nint - 1;
diag[last] += h[n - 2] * (1.0 + rl); if nint >= 2 {
sub[last] += -h[n - 2] * rl; }
sup[last] = 0.0;
let mut mint = vec![0.0; nint];
thomas(&sub, &diag, &sup, &rhs, &mut mint);
let mut m = vec![0.0; n];
m[1..n - 1].copy_from_slice(&mint);
m[0] = (1.0 + r) * m[1] - r * m[2];
m[n - 1] = (1.0 + rl) * m[n - 2] - rl * m[n - 3];
m
}
fn thomas(sub: &[f64], diag: &[f64], sup: &[f64], rhs: &[f64], out: &mut [f64]) {
let n = diag.len();
if n == 0 {
return;
}
let mut cp = vec![0.0; n];
let mut dp = vec![0.0; n];
cp[0] = sup[0] / diag[0];
dp[0] = rhs[0] / diag[0];
for i in 1..n {
let denom = diag[i] - sub[i] * cp[i - 1];
cp[i] = sup[i] / denom;
dp[i] = (rhs[i] - sub[i] * dp[i - 1]) / denom;
}
out[n - 1] = dp[n - 1];
for i in (0..n - 1).rev() {
out[i] = dp[i] - cp[i] * out[i + 1];
}
}