use std::f64::consts::PI;
use scirs2_interpolate::extrapolation_trait::{
ClosureInterpolator, Extrapolate, ExtrapolationBehavior,
};
fn linear_fn(x_min: f64, x_max: f64, slope: f64) -> ClosureInterpolator<impl Fn(f64) -> f64> {
ClosureInterpolator {
x_min,
x_max,
f: move |x| slope * x,
}
}
#[test]
fn extrap_nearest_returns_boundary_value() {
let interp = ClosureInterpolator {
x_min: 1.0,
x_max: 5.0,
f: |x| 3.0 * x,
};
let val_below = interp.extrapolate(0.0, &ExtrapolationBehavior::Nearest);
assert!(
(val_below - 3.0).abs() < 1e-10,
"Nearest below: expected 3.0, got {val_below}"
);
let val_above = interp.extrapolate(7.0, &ExtrapolationBehavior::Nearest);
assert!(
(val_above - 15.0).abs() < 1e-10,
"Nearest above: expected 15.0, got {val_above}"
);
let val_inside = interp.extrapolate(3.0, &ExtrapolationBehavior::Nearest);
assert!(
(val_inside - 9.0).abs() < 1e-10,
"Inside domain: expected 9.0, got {val_inside}"
);
}
#[test]
fn extrap_linear_matches_analytic_line() {
let interp = linear_fn(0.0, 1.0, 2.0);
let val_below = interp.extrapolate(-0.5, &ExtrapolationBehavior::Linear);
assert!(
(val_below - (-1.0)).abs() < 1e-4,
"Linear extrap below: expected -1.0, got {val_below}"
);
let val_above = interp.extrapolate(2.0, &ExtrapolationBehavior::Linear);
assert!(
(val_above - 4.0).abs() < 1e-4,
"Linear extrap above: expected 4.0, got {val_above}"
);
}
#[test]
fn extrap_periodic_wraps_sin() {
let interp = ClosureInterpolator {
x_min: 0.0,
x_max: 2.0 * PI,
f: |x| x.sin(),
};
let v1 = interp.extrapolate(PI, &ExtrapolationBehavior::Periodic);
let v2 = interp.extrapolate(3.0 * PI, &ExtrapolationBehavior::Periodic);
assert!(
(v1 - v2).abs() < 1e-10,
"sin(π) ≈ sin(3π) via Periodic: {v1} vs {v2}"
);
let v3 = interp.extrapolate(4.0 * PI, &ExtrapolationBehavior::Periodic);
let v0 = interp.extrapolate(0.0, &ExtrapolationBehavior::Periodic);
assert!(
(v3 - v0).abs() < 1e-10,
"sin(4π) ≈ sin(0) via Periodic: {v3} vs {v0}"
);
}
#[test]
fn extrap_reflection_symmetry() {
let interp = ClosureInterpolator {
x_min: 0.0,
x_max: 1.0,
f: |x| x,
};
let val = interp.extrapolate(-0.3, &ExtrapolationBehavior::Reflection);
assert!(
(val - 0.3).abs() < 1e-10,
"Reflection of -0.3 into [0,1]: expected 0.3, got {val}"
);
let val2 = interp.extrapolate(1.4, &ExtrapolationBehavior::Reflection);
assert!(
(val2 - 0.6).abs() < 1e-10,
"Reflection of 1.4 into [0,1]: expected 0.6, got {val2}"
);
}
#[test]
fn extrap_polynomial_extrapolates_quadratic() {
let interp = ClosureInterpolator {
x_min: 0.0,
x_max: 4.0,
f: |x| x * x,
};
let val = interp.extrapolate(5.0, &ExtrapolationBehavior::Polynomial(2));
assert!(
val.is_finite(),
"Polynomial extrap at x=5 must be finite, got {val}"
);
assert!(
(val - 25.0).abs() < 1.0,
"Polynomial extrap at x=5: expected ~25, got {val}"
);
}
#[test]
fn extrap_inside_domain_passthrough() {
let interp = ClosureInterpolator {
x_min: 0.0,
x_max: 10.0,
f: |x| x * x,
};
let modes = [
ExtrapolationBehavior::Nearest,
ExtrapolationBehavior::Linear,
ExtrapolationBehavior::Polynomial(3),
ExtrapolationBehavior::Reflection,
ExtrapolationBehavior::Periodic,
];
for mode in &modes {
let val = interp.extrapolate(3.0, mode);
assert!(
(val - 9.0).abs() < 1e-10,
"Mode {:?}: expected 9.0 for x=3 inside [0,10], got {val}",
mode
);
}
}