pub(crate) fn interp_one(x: f64, xp: &[f64], fp: &[f64]) -> f64 {
let n = xp.len().min(fp.len());
if n == 0 {
return f64::NAN;
}
if n == 1 || x <= xp[0] {
return fp[0];
}
if x >= xp[n - 1] {
return fp[n - 1];
}
let idx = xp[..n].partition_point(|&v| v < x);
if idx == 0 {
return fp[0];
}
if idx >= n {
return fp[n - 1];
}
if (xp[idx] - x).abs() < f64::EPSILON * xp[idx].abs() {
return fp[idx];
}
let lo = idx - 1;
let span = xp[idx] - xp[lo];
if span == 0.0 {
return fp[idx];
}
let t = (x - xp[lo]) / span;
fp[lo] + t * (fp[idx] - fp[lo])
}
#[cfg(test)]
pub(crate) fn interp(x: &[f64], xp: &[f64], fp: &[f64]) -> Vec<f64> {
x.iter().map(|&xi| interp_one(xi, xp, fp)).collect()
}
pub(crate) fn interp_loglog_pre(x: f64, log_xp: &[f64], log_fp: &[f64]) -> f64 {
if x <= 0.0 {
return f64::NAN;
}
interp_one(x.ln(), log_xp, log_fp).exp()
}
pub(crate) fn safe_ln(v: f64) -> f64 {
const FLOOR: f64 = 1e-99;
if v.abs() < FLOOR {
FLOOR.ln()
} else {
v.abs().ln()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_interp_basic() {
let xp = [0.0, 1.0, 2.0];
let fp = [0.0, 10.0, 20.0];
let result = interp(&[0.5, 1.5], &xp, &fp);
assert!((result[0] - 5.0).abs() < 1e-10);
assert!((result[1] - 15.0).abs() < 1e-10);
}
#[test]
fn test_interp_clamping() {
let xp = [1.0, 2.0, 3.0];
let fp = [10.0, 20.0, 30.0];
let result = interp(&[0.0, 4.0], &xp, &fp);
assert!((result[0] - 10.0).abs() < 1e-10);
assert!((result[1] - 30.0).abs() < 1e-10);
}
#[test]
fn empty_table_yields_nan_not_panic() {
assert!(interp_one(1.0, &[], &[]).is_nan());
assert!(interp_one(1.0, &[1.0], &[]).is_nan());
}
#[test]
fn single_point_table_returns_that_point() {
assert_eq!(interp_one(-5.0, &[1.0], &[7.0]), 7.0);
assert_eq!(interp_one(500.0, &[1.0], &[7.0]), 7.0);
}
#[test]
fn mismatched_lengths_use_the_shorter() {
assert_eq!(interp_one(10.0, &[0.0, 1.0], &[0.0, 5.0, 9.0]), 5.0);
}
#[test]
fn duplicate_knots_do_not_divide_by_zero() {
let v = interp_one(1.0, &[0.0, 1.0, 1.0, 2.0], &[0.0, 3.0, 4.0, 5.0]);
assert!(v.is_finite(), "got {v}");
}
#[test]
fn loglog_matches_naive_transform() {
let xp: [f64; 3] = [1.0, 10.0, 100.0];
let fp: [f64; 3] = [1.0, 100.0, 10000.0];
let log_xp: Vec<f64> = xp.iter().map(|v| v.ln()).collect();
let log_fp: Vec<f64> = fp.iter().map(|v| v.ln()).collect();
let got = interp_loglog_pre(50.0, &log_xp, &log_fp);
assert!((got - 2500.0).abs() < 1e-6, "got {got}");
}
#[test]
fn loglog_rejects_non_positive_x() {
assert!(interp_loglog_pre(0.0, &[0.0, 1.0], &[0.0, 1.0]).is_nan());
assert!(interp_loglog_pre(-1.0, &[0.0, 1.0], &[0.0, 1.0]).is_nan());
}
#[test]
fn safe_ln_floors_zero() {
assert!(safe_ln(0.0).is_finite());
assert_eq!(safe_ln(0.0), 1e-99_f64.ln());
assert!((safe_ln(std::f64::consts::E) - 1.0).abs() < 1e-12);
}
}