use crate::errors::QlResult;
use crate::fail;
use crate::math::interpolations::{Interpolation, Interpolator};
use crate::types::{Real, Size};
#[derive(Clone, Copy, Default)]
pub struct Linear;
impl Interpolator for Linear {
type Output = LinearInterpolation;
fn interpolate(&self, x: &[Real], y: &[Real]) -> QlResult<LinearInterpolation> {
LinearInterpolation::new(x.to_vec(), y.to_vec())
}
}
pub struct LinearInterpolation {
x: Vec<Real>,
y: Vec<Real>,
slopes: Vec<Real>,
primitive_const: Vec<Real>,
allow_extrapolation: bool,
}
impl LinearInterpolation {
pub fn new(x: Vec<Real>, y: Vec<Real>) -> QlResult<Self> {
if x.len() != y.len() {
fail!(
"x and y must have equal length ({} vs {})",
x.len(),
y.len()
);
}
if x.len() < 2 {
fail!(
"linear interpolation needs at least 2 points, got {}",
x.len()
);
}
for &xi in &x {
if !xi.is_finite() {
fail!("x values must be finite, got {xi}");
}
}
for &yi in &y {
if !yi.is_finite() {
fail!("y values must be finite, got {yi}");
}
}
for w in x.windows(2) {
if w[1] <= w[0] {
fail!("x values must be strictly increasing");
}
}
let n = x.len();
let mut slopes = vec![0.0; n - 1];
let mut primitive_const = vec![0.0; n];
for i in 1..n {
let dx = x[i] - x[i - 1];
slopes[i - 1] = (y[i] - y[i - 1]) / dx;
primitive_const[i] =
primitive_const[i - 1] + dx * (y[i - 1] + 0.5 * dx * slopes[i - 1]);
}
Ok(LinearInterpolation {
x,
y,
slopes,
primitive_const,
allow_extrapolation: false,
})
}
pub fn with_extrapolation(mut self, allow: bool) -> Self {
self.allow_extrapolation = allow;
self
}
pub fn allows_extrapolation(&self) -> bool {
self.allow_extrapolation
}
fn locate(&self, x: Real) -> Size {
let n = self.x.len();
if x < self.x[0] {
0
} else if x > self.x[n - 1] {
n - 2
} else {
self.x[..n - 1].partition_point(|&xi| xi <= x) - 1
}
}
fn check_range(&self, x: Real) -> QlResult<()> {
if x.is_nan() {
fail!("interpolation cannot be evaluated at NaN");
}
if !self.allow_extrapolation && !self.is_in_range(x) {
fail!(
"interpolation range is [{}, {}]: extrapolation at {x} not allowed",
self.x_min(),
self.x_max()
);
}
Ok(())
}
}
impl Interpolation for LinearInterpolation {
fn value(&self, x: Real) -> QlResult<Real> {
self.check_range(x)?;
let i = self.locate(x);
Ok(self.y[i] + (x - self.x[i]) * self.slopes[i])
}
fn derivative(&self, x: Real) -> QlResult<Real> {
self.check_range(x)?;
Ok(self.slopes[self.locate(x)])
}
fn primitive(&self, x: Real) -> QlResult<Real> {
self.check_range(x)?;
let i = self.locate(x);
let dx = x - self.x[i];
Ok(self.primitive_const[i] + dx * (self.y[i] + 0.5 * dx * self.slopes[i]))
}
fn x_min(&self) -> Real {
self.x[0]
}
fn x_max(&self) -> Real {
self.x[self.x.len() - 1]
}
fn is_in_range(&self, x: Real) -> bool {
x >= self.x_min() && x <= self.x_max()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn sample() -> LinearInterpolation {
LinearInterpolation::new(vec![0.0, 1.0, 3.0], vec![0.0, 2.0, 2.0]).unwrap()
}
#[test]
fn value_at_nodes_and_between() {
let f = sample();
assert_eq!(f.value(0.0).unwrap(), 0.0);
assert_eq!(f.value(1.0).unwrap(), 2.0);
assert_eq!(f.value(3.0).unwrap(), 2.0);
assert_eq!(f.value(0.5).unwrap(), 1.0); assert_eq!(f.value(2.0).unwrap(), 2.0); }
#[test]
fn derivative_is_segment_slope() {
let f = sample();
assert_eq!(f.derivative(0.5).unwrap(), 2.0);
assert_eq!(f.derivative(2.0).unwrap(), 0.0);
}
#[test]
fn primitive_is_cumulative_area() {
let f = sample();
assert_eq!(f.primitive(0.0).unwrap(), 0.0);
assert_eq!(f.primitive(1.0).unwrap(), 1.0); assert_eq!(f.primitive(3.0).unwrap(), 5.0); assert!((f.primitive(0.5).unwrap() - 0.25).abs() < 1e-12); }
#[test]
fn domain_and_in_range() {
let f = sample();
assert_eq!(f.x_min(), 0.0);
assert_eq!(f.x_max(), 3.0);
assert!(f.is_in_range(1.5));
assert!(!f.is_in_range(-0.1));
assert!(!f.is_in_range(3.1));
}
#[test]
fn extrapolation_disabled_errors_out_of_range() {
let f = sample();
assert!(f.value(-1.0).is_err());
assert!(f.value(4.0).is_err());
}
#[test]
fn extrapolation_enabled_extends_end_segments() {
let f = sample().with_extrapolation(true);
assert!(f.allows_extrapolation());
assert_eq!(f.value(-1.0).unwrap(), -2.0); assert_eq!(f.value(4.0).unwrap(), 2.0); }
#[test]
fn nan_input_is_rejected_even_with_extrapolation() {
let f = sample().with_extrapolation(true);
assert!(f.value(Real::NAN).is_err());
assert!(f.derivative(Real::NAN).is_err());
assert!(f.primitive(Real::NAN).is_err());
}
#[test]
fn invalid_inputs_rejected() {
assert!(LinearInterpolation::new(vec![0.0, 1.0], vec![0.0]).is_err());
assert!(LinearInterpolation::new(vec![0.0], vec![0.0]).is_err());
assert!(LinearInterpolation::new(vec![0.0, 0.0], vec![1.0, 2.0]).is_err());
}
#[test]
fn factory_builds_the_interpolation() {
let f = Linear
.interpolate(&[0.0, 1.0, 3.0], &[0.0, 2.0, 2.0])
.unwrap();
assert_eq!(f.value(0.5).unwrap(), 1.0);
assert_eq!(Linear.required_points(), 2);
assert!(Linear.interpolate(&[0.0], &[0.0]).is_err());
}
#[test]
fn non_finite_nodes_rejected() {
assert!(LinearInterpolation::new(vec![0.0, Real::NAN], vec![0.0, 1.0]).is_err());
assert!(LinearInterpolation::new(vec![0.0, Real::INFINITY], vec![0.0, 1.0]).is_err());
assert!(LinearInterpolation::new(vec![0.0, 1.0], vec![0.0, Real::NAN]).is_err());
}
}