use crate::errors::CurveError;
pub mod convex_monotone;
pub mod cubic_spline;
pub mod hermite_bessel;
pub mod linear;
pub mod linear_in_zero;
pub mod log_linear;
pub mod monotone_cubic;
pub mod monotone_hyman;
pub mod monotone_steffen;
pub mod piecewise_constant_forward;
pub use convex_monotone::ConvexMonotone;
pub use cubic_spline::{CubicSpline, SplineBoundary};
pub use hermite_bessel::HermiteBessel;
pub use linear::Linear;
pub use linear_in_zero::LinearInZero;
pub use log_linear::LogLinear;
pub use monotone_cubic::MonotoneCubic;
pub use monotone_hyman::MonotoneHyman;
pub use monotone_steffen::MonotoneSteffen;
pub use piecewise_constant_forward::PiecewiseConstantForward;
pub trait Interpolator {
fn build(knots: &[(f64, f64)]) -> Result<Self, CurveError>
where
Self: Sized;
fn eval(&self, t: f64) -> f64;
fn deriv(&self, _t: f64) -> Option<f64> {
None
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
#[non_exhaustive]
pub enum Interpolation {
Linear,
LogLinear,
LinearInZero,
PiecewiseConstantForward,
CubicSpline(SplineBoundary),
ConvexMonotone,
HermiteBessel,
MonotoneCubic,
MonotoneHyman,
MonotoneSteffen,
}
impl Interpolation {
pub fn build(self, knots: &[(f64, f64)]) -> Result<InterpolationImpl, CurveError> {
match self {
Self::Linear => Linear::build(knots).map(InterpolationImpl::Linear),
Self::LogLinear => LogLinear::build(knots).map(InterpolationImpl::LogLinear),
Self::LinearInZero => LinearInZero::build(knots).map(InterpolationImpl::LinearInZero),
Self::PiecewiseConstantForward => PiecewiseConstantForward::build(knots)
.map(InterpolationImpl::PiecewiseConstantForward),
Self::CubicSpline(boundary) => {
CubicSpline::new(knots, boundary).map(InterpolationImpl::CubicSpline)
}
Self::ConvexMonotone => {
ConvexMonotone::build(knots).map(InterpolationImpl::ConvexMonotone)
}
Self::HermiteBessel => {
HermiteBessel::build(knots).map(InterpolationImpl::HermiteBessel)
}
Self::MonotoneCubic => {
MonotoneCubic::build(knots).map(InterpolationImpl::MonotoneCubic)
}
Self::MonotoneHyman => {
MonotoneHyman::build(knots).map(InterpolationImpl::MonotoneHyman)
}
Self::MonotoneSteffen => {
MonotoneSteffen::build(knots).map(InterpolationImpl::MonotoneSteffen)
}
}
}
}
#[derive(Debug, Clone)]
pub enum InterpolationImpl {
Linear(Linear),
LogLinear(LogLinear),
LinearInZero(LinearInZero),
PiecewiseConstantForward(PiecewiseConstantForward),
CubicSpline(CubicSpline),
ConvexMonotone(ConvexMonotone),
HermiteBessel(HermiteBessel),
MonotoneCubic(MonotoneCubic),
MonotoneHyman(MonotoneHyman),
MonotoneSteffen(MonotoneSteffen),
}
impl InterpolationImpl {
#[must_use]
pub fn eval(&self, t: f64) -> f64 {
match self {
Self::Linear(i) => i.eval(t),
Self::LogLinear(i) => i.eval(t),
Self::LinearInZero(i) => i.eval(t),
Self::PiecewiseConstantForward(i) => i.eval(t),
Self::CubicSpline(i) => i.eval(t),
Self::ConvexMonotone(i) => i.eval(t),
Self::HermiteBessel(i) => i.eval(t),
Self::MonotoneCubic(i) => i.eval(t),
Self::MonotoneHyman(i) => i.eval(t),
Self::MonotoneSteffen(i) => i.eval(t),
}
}
#[must_use]
pub fn deriv(&self, t: f64) -> Option<f64> {
match self {
Self::Linear(i) => i.deriv(t),
Self::LogLinear(i) => i.deriv(t),
Self::LinearInZero(i) => i.deriv(t),
Self::PiecewiseConstantForward(i) => i.deriv(t),
Self::CubicSpline(i) => i.deriv(t),
Self::ConvexMonotone(i) => i.deriv(t),
Self::HermiteBessel(i) => i.deriv(t),
Self::MonotoneCubic(i) => i.deriv(t),
Self::MonotoneHyman(i) => i.deriv(t),
Self::MonotoneSteffen(i) => i.deriv(t),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn interpolation_log_linear_copy_eq() {
let a = Interpolation::LogLinear;
let b = a;
assert_eq!(a, b);
}
#[test]
fn interpolation_debug_includes_variant() {
let s = format!("{:?}", Interpolation::LogLinear);
assert!(s.contains("LogLinear"));
}
#[test]
fn trait_build_via_log_linear() {
let interp = LogLinear::build(&[(0.0, 1.0), (1.0, 0.95)]).unwrap();
assert!((interp.eval(0.0) - 1.0).abs() < 1e-15);
assert!((interp.eval(1.0) - 0.95).abs() < 1e-15);
}
struct ConstantOne;
impl Interpolator for ConstantOne {
fn build(_knots: &[(f64, f64)]) -> Result<Self, CurveError> {
Ok(Self)
}
fn eval(&self, _t: f64) -> f64 {
1.0
}
}
#[test]
fn trait_default_deriv_is_none() {
let c = ConstantOne::build(&[]).unwrap();
assert!(c.deriv(0.5).is_none());
assert!((c.eval(0.5) - 1.0).abs() < 1e-15);
}
fn standard_knots() -> [(f64, f64); 4] {
[(0.0, 1.0), (0.5, 0.975), (1.0, 0.95), (2.0, 0.90)]
}
fn assert_knots_reproduced(interp: &InterpolationImpl, knots: &[(f64, f64)]) {
for &(t, y) in knots {
let v = interp.eval(t);
assert!(
(v - y).abs() < 1e-12,
"knot ({t}, {y}) not reproduced: got {v}"
);
}
}
#[test]
fn interpolation_build_linear() {
let knots = standard_knots();
let interp = Interpolation::Linear.build(&knots).unwrap();
assert!(matches!(interp, InterpolationImpl::Linear(_)));
assert_knots_reproduced(&interp, &knots);
}
#[test]
fn interpolation_build_log_linear() {
let knots = standard_knots();
let interp = Interpolation::LogLinear.build(&knots).unwrap();
assert!(matches!(interp, InterpolationImpl::LogLinear(_)));
assert_knots_reproduced(&interp, &knots);
}
#[test]
fn interpolation_build_linear_in_zero() {
let knots = standard_knots();
let interp = Interpolation::LinearInZero.build(&knots).unwrap();
assert!(matches!(interp, InterpolationImpl::LinearInZero(_)));
assert_knots_reproduced(&interp, &knots);
}
#[test]
fn interpolation_build_piecewise_constant_forward() {
let knots = standard_knots();
let interp = Interpolation::PiecewiseConstantForward
.build(&knots)
.unwrap();
assert!(matches!(
interp,
InterpolationImpl::PiecewiseConstantForward(_)
));
assert_knots_reproduced(&interp, &knots);
}
#[test]
fn interpolation_build_cubic_spline_natural() {
let knots = standard_knots();
let interp = Interpolation::CubicSpline(SplineBoundary::Natural)
.build(&knots)
.unwrap();
assert!(matches!(interp, InterpolationImpl::CubicSpline(_)));
assert_knots_reproduced(&interp, &knots);
}
#[test]
fn interpolation_build_cubic_spline_not_a_knot() {
let knots = standard_knots();
let interp = Interpolation::CubicSpline(SplineBoundary::NotAKnot)
.build(&knots)
.unwrap();
assert!(matches!(interp, InterpolationImpl::CubicSpline(_)));
assert_knots_reproduced(&interp, &knots);
}
#[test]
fn interpolation_build_convex_monotone() {
let knots = standard_knots();
let interp = Interpolation::ConvexMonotone.build(&knots).unwrap();
assert!(matches!(interp, InterpolationImpl::ConvexMonotone(_)));
assert_knots_reproduced(&interp, &knots);
}
#[test]
fn interpolation_build_hermite_bessel() {
let knots = standard_knots();
let interp = Interpolation::HermiteBessel.build(&knots).unwrap();
assert!(matches!(interp, InterpolationImpl::HermiteBessel(_)));
assert_knots_reproduced(&interp, &knots);
}
#[test]
fn interpolation_build_monotone_cubic() {
let knots = standard_knots();
let interp = Interpolation::MonotoneCubic.build(&knots).unwrap();
assert!(matches!(interp, InterpolationImpl::MonotoneCubic(_)));
assert_knots_reproduced(&interp, &knots);
}
#[test]
fn interpolation_build_monotone_hyman() {
let knots = standard_knots();
let interp = Interpolation::MonotoneHyman.build(&knots).unwrap();
assert!(matches!(interp, InterpolationImpl::MonotoneHyman(_)));
assert_knots_reproduced(&interp, &knots);
}
#[test]
fn interpolation_build_monotone_steffen() {
let knots = standard_knots();
let interp = Interpolation::MonotoneSteffen.build(&knots).unwrap();
assert!(matches!(interp, InterpolationImpl::MonotoneSteffen(_)));
assert_knots_reproduced(&interp, &knots);
}
#[test]
fn interpolation_build_propagates_too_few_nodes() {
let err = Interpolation::LogLinear.build(&[(0.0, 1.0)]).unwrap_err();
assert!(matches!(err, CurveError::TooFewNodes { found: 1 }));
}
#[test]
fn interpolation_impl_deriv_dispatches() {
let knots = standard_knots();
let interp = Interpolation::LogLinear.build(&knots).unwrap();
let d = interp.deriv(0.75).unwrap();
assert!(d.is_finite());
}
#[test]
fn interpolation_impl_clone_yields_equivalent_eval() {
let knots = standard_knots();
let interp = Interpolation::LogLinear.build(&knots).unwrap();
let copy = interp.clone();
assert!((interp.eval(0.75) - copy.eval(0.75)).abs() < 1e-15);
}
}