use crate::errors::QlResult;
use crate::fail;
use crate::math::interpolations::{Interpolation, Interpolator};
use crate::types::{Real, Size};
#[derive(Clone, Copy, Default)]
pub struct BackwardFlat;
impl Interpolator for BackwardFlat {
type Output = BackwardFlatInterpolation;
fn interpolate(&self, x: &[Real], y: &[Real]) -> QlResult<BackwardFlatInterpolation> {
BackwardFlatInterpolation::new(x.to_vec(), y.to_vec())
}
fn required_points(&self) -> Size {
1
}
}
fn validate(x: &[Real], y: &[Real], min_points: usize) -> QlResult<()> {
if x.len() != y.len() {
fail!(
"x and y must have equal length ({} vs {})",
x.len(),
y.len()
);
}
if x.len() < min_points {
fail!(
"flat interpolation needs at least {min_points} point(s), 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");
}
}
Ok(())
}
fn locate(x: &[Real], at: Real) -> Size {
let n = x.len();
if at < x[0] {
0
} else if at > x[n - 1] {
n - 2
} else {
x[..n - 1].partition_point(|&xi| xi <= at) - 1
}
}
fn check_range(x: &[Real], allow_extrapolation: bool, at: Real) -> QlResult<()> {
if at.is_nan() {
fail!("interpolation cannot be evaluated at NaN");
}
let (lo, hi) = (x[0], x[x.len() - 1]);
if !allow_extrapolation && !(lo..=hi).contains(&at) {
fail!("interpolation range is [{lo}, {hi}]: extrapolation at {at} not allowed");
}
Ok(())
}
pub struct BackwardFlatInterpolation {
x: Vec<Real>,
y: Vec<Real>,
primitive: Vec<Real>,
allow_extrapolation: bool,
}
impl BackwardFlatInterpolation {
pub fn new(x: Vec<Real>, y: Vec<Real>) -> QlResult<Self> {
validate(&x, &y, 1)?;
let n = x.len();
let mut primitive = vec![0.0; n];
for i in 1..n {
primitive[i] = primitive[i - 1] + (x[i] - x[i - 1]) * y[i];
}
Ok(BackwardFlatInterpolation {
x,
y,
primitive,
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
}
}
impl Interpolation for BackwardFlatInterpolation {
fn value(&self, at: Real) -> QlResult<Real> {
check_range(&self.x, self.allow_extrapolation, at)?;
let j = self.x.partition_point(|&xi| xi < at).min(self.x.len() - 1);
Ok(self.y[j])
}
fn derivative(&self, at: Real) -> QlResult<Real> {
check_range(&self.x, self.allow_extrapolation, at)?;
Ok(0.0)
}
fn primitive(&self, at: Real) -> QlResult<Real> {
check_range(&self.x, self.allow_extrapolation, at)?;
if self.x.len() == 1 {
return Ok((at - self.x[0]) * self.y[0]);
}
let i = locate(&self.x, at);
Ok(self.primitive[i] + (at - self.x[i]) * self.y[i + 1])
}
fn x_min(&self) -> Real {
self.x[0]
}
fn x_max(&self) -> Real {
self.x[self.x.len() - 1]
}
fn is_in_range(&self, at: Real) -> bool {
(self.x_min()..=self.x_max()).contains(&at)
}
}
pub struct ForwardFlatInterpolation {
x: Vec<Real>,
y: Vec<Real>,
primitive: Vec<Real>,
allow_extrapolation: bool,
}
impl ForwardFlatInterpolation {
pub fn new(x: Vec<Real>, y: Vec<Real>) -> QlResult<Self> {
validate(&x, &y, 2)?;
let n = x.len();
let mut primitive = vec![0.0; n];
for i in 1..n {
primitive[i] = primitive[i - 1] + (x[i] - x[i - 1]) * y[i - 1];
}
Ok(ForwardFlatInterpolation {
x,
y,
primitive,
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
}
}
impl Interpolation for ForwardFlatInterpolation {
fn value(&self, at: Real) -> QlResult<Real> {
check_range(&self.x, self.allow_extrapolation, at)?;
let j = self.x.partition_point(|&xi| xi <= at).saturating_sub(1);
Ok(self.y[j])
}
fn derivative(&self, at: Real) -> QlResult<Real> {
check_range(&self.x, self.allow_extrapolation, at)?;
Ok(0.0)
}
fn primitive(&self, at: Real) -> QlResult<Real> {
check_range(&self.x, self.allow_extrapolation, at)?;
if self.x.len() == 1 {
return Ok((at - self.x[0]) * self.y[0]);
}
let i = locate(&self.x, at);
Ok(self.primitive[i] + (at - self.x[i]) * self.y[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, at: Real) -> bool {
(self.x_min()..=self.x_max()).contains(&at)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn backward_flat_matches_oracle() {
let x = vec![0.0, 1.0, 2.0, 3.0, 4.0];
let y = vec![5.0, 4.0, 3.0, 2.0, 1.0];
let f = BackwardFlatInterpolation::new(x.clone(), y.clone()).unwrap();
for i in 0..x.len() {
assert_eq!(f.value(x[i]).unwrap(), y[i]);
}
for i in 0..x.len() - 1 {
assert_eq!(f.value(0.5 * (x[i] + x[i + 1])).unwrap(), y[i + 1]);
}
let f = f.with_extrapolation(true);
assert_eq!(f.value(x[0] - 0.5).unwrap(), y[0]);
assert_eq!(f.value(x[4] + 0.5).unwrap(), y[4]);
}
#[test]
fn forward_flat_matches_oracle() {
let x = vec![0.0, 1.0, 2.0, 3.0, 4.0];
let y = vec![5.0, 4.0, 3.0, 2.0, 1.0];
let f = ForwardFlatInterpolation::new(x.clone(), y.clone()).unwrap();
for i in 0..x.len() {
assert_eq!(f.value(x[i]).unwrap(), y[i]);
}
for i in 0..x.len() - 1 {
assert_eq!(f.value(0.5 * (x[i] + x[i + 1])).unwrap(), y[i]);
}
let f = f.with_extrapolation(true);
assert_eq!(f.value(x[0] - 0.5).unwrap(), y[0]);
assert_eq!(f.value(x[4] + 0.5).unwrap(), y[4]);
}
#[test]
fn derivative_is_zero_and_primitive_is_cumulative() {
let x = vec![0.0, 1.0, 2.0, 3.0, 4.0];
let y = vec![5.0, 4.0, 3.0, 2.0, 1.0];
let b = BackwardFlatInterpolation::new(x.clone(), y.clone()).unwrap();
let fwd = ForwardFlatInterpolation::new(x.clone(), y.clone()).unwrap();
assert_eq!(b.derivative(2.5).unwrap(), 0.0);
assert_eq!(fwd.derivative(2.5).unwrap(), 0.0);
assert_eq!(b.primitive(0.0).unwrap(), 0.0);
assert_eq!(b.primitive(1.0).unwrap(), 4.0);
assert_eq!(b.primitive(1.5).unwrap(), 4.0 + 0.5 * 3.0);
assert_eq!(fwd.primitive(1.0).unwrap(), 5.0);
assert_eq!(fwd.primitive(1.5).unwrap(), 5.0 + 0.5 * 4.0);
}
#[test]
fn backward_flat_single_point_is_piecewise_constant() {
let b = BackwardFlatInterpolation::new(vec![1.0], vec![2.5])
.unwrap()
.with_extrapolation(true);
for at in [-1.0, 1.0, 2.0, 3.0] {
assert_eq!(b.value(at).unwrap(), 2.5);
assert_eq!(b.primitive(at).unwrap(), 2.5 * (at - 1.0));
}
assert!(ForwardFlatInterpolation::new(vec![1.0], vec![2.5]).is_err());
}
#[test]
fn factory_builds_the_interpolation_and_allows_a_single_node() {
let f = BackwardFlat
.interpolate(&[0.0, 1.0, 3.0], &[5.0, 4.0, 2.0])
.unwrap();
assert_eq!(f.value(0.5).unwrap(), 4.0);
assert_eq!(BackwardFlat.required_points(), 1);
assert!(BackwardFlat.interpolate(&[1.0], &[2.5]).is_ok());
}
#[test]
fn nan_input_is_rejected() {
let f = BackwardFlatInterpolation::new(vec![0.0, 1.0], vec![1.0, 2.0])
.unwrap()
.with_extrapolation(true);
assert!(f.value(Real::NAN).is_err());
assert!(f.primitive(Real::NAN).is_err());
}
#[test]
fn out_of_range_errors_without_extrapolation() {
let f = ForwardFlatInterpolation::new(vec![0.0, 1.0], vec![1.0, 2.0]).unwrap();
assert!(f.value(-0.5).is_err());
assert!(f.value(1.5).is_err());
}
#[test]
fn invalid_inputs_rejected() {
assert!(BackwardFlatInterpolation::new(vec![], vec![]).is_err());
assert!(BackwardFlatInterpolation::new(vec![0.0, 1.0], vec![0.0]).is_err());
assert!(BackwardFlatInterpolation::new(vec![1.0, 0.0], vec![0.0, 1.0]).is_err());
assert!(ForwardFlatInterpolation::new(vec![0.0, Real::NAN], vec![0.0, 1.0]).is_err());
}
}