use crate::error::{InterpolateError, InterpolateResult};
use crate::spline::CubicSpline;
use scirs2_core::ndarray::{Array1, ArrayView1};
use scirs2_core::numeric::{Float, FromPrimitive};
use std::fmt::Debug;
#[inline(always)]
fn const_f64<F: Float + FromPrimitive>(value: f64) -> F {
F::from(value).expect("Failed to convert constant to target float type")
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum MonotonicMethod {
Pchip,
Hyman,
Steffen,
ModifiedAkima,
}
#[derive(Debug, Clone)]
pub struct MonotonicInterpolator<F: Float> {
x: Array1<F>,
y: Array1<F>,
derivatives: Array1<F>,
method: MonotonicMethod,
extrapolate: bool,
}
impl<F: Float> MonotonicInterpolator<F> {
pub fn method(&self) -> MonotonicMethod {
self.method
}
}
impl<F: Float + FromPrimitive + Debug + crate::traits::InterpolationFloat>
MonotonicInterpolator<F>
{
pub fn new(
x: &ArrayView1<F>,
y: &ArrayView1<F>,
method: MonotonicMethod,
extrapolate: bool,
) -> InterpolateResult<Self> {
if x.len() != y.len() {
return Err(InterpolateError::invalid_input(
"x and y arrays must have the same length".to_string(),
));
}
if x.len() < 2 {
return Err(InterpolateError::insufficient_points(
2,
x.len(),
"monotonic interpolation",
));
}
for i in 1..x.len() {
if x[i] <= x[i - 1] {
return Err(InterpolateError::invalid_input(
"x values must be sorted in ascending order".to_string(),
));
}
}
let x_arr = x.to_owned();
let y_arr = y.to_owned();
let derivatives = match method {
MonotonicMethod::Pchip => Self::find_pchip_derivatives(&x_arr, &y_arr)?,
MonotonicMethod::Hyman => Self::find_hyman_derivatives(&x_arr, &y_arr)?,
MonotonicMethod::Steffen => Self::find_steffen_derivatives(&x_arr, &y_arr)?,
MonotonicMethod::ModifiedAkima => {
Self::find_modified_akima_derivatives(&x_arr, &y_arr)?
}
};
Ok(MonotonicInterpolator {
x: x_arr,
y: y_arr,
derivatives,
method,
extrapolate,
})
}
pub fn evaluate(&self, xnew: F) -> InterpolateResult<F> {
let is_extrapolating = xnew < self.x[0] || xnew > self.x[self.x.len() - 1];
if is_extrapolating && !self.extrapolate {
return Err(InterpolateError::OutOfBounds(
"xnew is outside the interpolation range".to_string(),
));
}
let mut idx = 0;
for i in 0..self.x.len() - 1 {
if xnew >= self.x[i] && xnew <= self.x[i + 1] {
idx = i;
break;
}
}
if is_extrapolating {
if xnew < self.x[0] {
idx = 0;
} else {
idx = self.x.len() - 2;
}
}
for i in 0..self.x.len() {
if xnew == self.x[i] {
return Ok(self.y[i]);
}
}
let x1 = self.x[idx];
let x2 = self.x[idx + 1];
let y1 = self.y[idx];
let y2 = self.y[idx + 1];
let d1 = self.derivatives[idx];
let d2 = self.derivatives[idx + 1];
let h = x2 - x1;
let t = (xnew - x1) / h;
let h00 = Self::h00(t);
let h10 = Self::h10(t);
let h01 = Self::h01(t);
let h11 = Self::h11(t);
let result = h00 * y1 + h10 * h * d1 + h01 * y2 + h11 * h * d2;
Ok(result)
}
pub fn evaluate_array(&self, xnew: &ArrayView1<F>) -> InterpolateResult<Array1<F>> {
let mut result = Array1::zeros(xnew.len());
for (i, &x) in xnew.iter().enumerate() {
result[i] = self.evaluate(x)?;
}
Ok(result)
}
fn h00(t: F) -> F {
let two = F::from_f64(2.0).unwrap_or_else(|| F::from(2).unwrap_or(F::zero()));
let three = F::from_f64(3.0).unwrap_or_else(|| F::from(3).unwrap_or(F::zero()));
(two * t * t * t) - (three * t * t) + F::one()
}
fn h10(t: F) -> F {
let two = F::from_f64(2.0).unwrap_or_else(|| F::from(2).unwrap_or(F::zero()));
(t * t * t) - (two * t * t) + t
}
fn h01(t: F) -> F {
let two = F::from_f64(2.0).unwrap_or_else(|| F::from(2).unwrap_or(F::zero()));
let three = F::from_f64(3.0).unwrap_or_else(|| F::from(3).unwrap_or(F::zero()));
-(two * t * t * t) + (three * t * t)
}
fn h11(t: F) -> F {
(t * t * t) - (t * t)
}
fn find_pchip_derivatives(x: &Array1<F>, y: &Array1<F>) -> InterpolateResult<Array1<F>> {
let n = x.len();
let mut derivatives = Array1::zeros(n);
if n == 2 {
let slope = (y[1] - y[0]) / (x[1] - x[0]);
derivatives[0] = slope;
derivatives[1] = slope;
return Ok(derivatives);
}
let mut slopes = Array1::zeros(n - 1);
for i in 0..n - 1 {
slopes[i] = (y[i + 1] - y[i]) / (x[i + 1] - x[i]);
}
let mut h = Array1::zeros(n - 1);
for i in 0..n - 1 {
h[i] = x[i + 1] - x[i];
}
let two = F::from_f64(2.0).ok_or_else(|| {
InterpolateError::ComputationError(
"Failed to convert constant 2.0 to float type".to_string(),
)
})?;
let three = F::from_f64(3.0).ok_or_else(|| {
InterpolateError::ComputationError(
"Failed to convert constant 3.0 to float type".to_string(),
)
})?;
for i in 1..n - 1 {
let prev_slope = slopes[i - 1];
let curr_slope = slopes[i];
let sign_prev = if prev_slope > F::zero() {
F::one()
} else if prev_slope < F::zero() {
-F::one()
} else {
F::zero()
};
let sign_curr = if curr_slope > F::zero() {
F::one()
} else if curr_slope < F::zero() {
-F::one()
} else {
F::zero()
};
if sign_prev * sign_curr <= F::zero() {
derivatives[i] = F::zero();
} else {
let w1 = two * h[i] + h[i - 1];
let w2 = h[i] + two * h[i - 1];
if prev_slope.abs() < F::epsilon() || curr_slope.abs() < F::epsilon() {
derivatives[i] = F::zero();
} else {
let whmean_inv = (w1 / prev_slope + w2 / curr_slope) / (w1 + w2);
derivatives[i] = F::one() / whmean_inv;
}
}
}
let h0 = h[0];
let h1 = if n > 2 { h[1] } else { h[0] };
let m0 = slopes[0];
let m1 = if n > 2 { slopes[1] } else { slopes[0] };
let mut d = ((two * h0 + h1) * m0 - h0 * m1) / (h0 + h1);
let sign_d = if d >= F::zero() { F::one() } else { -F::one() };
let sign_m0 = if m0 >= F::zero() { F::one() } else { -F::one() };
let sign_m1 = if m1 >= F::zero() { F::one() } else { -F::one() };
if sign_d != sign_m0 {
d = F::zero();
} else if (sign_m0 != sign_m1) && (d.abs() > three * m0.abs()) {
d = three * m0;
}
derivatives[0] = d;
let h0 = h[n - 2];
let h1 = if n > 2 { h[n - 3] } else { h[n - 2] };
let m0 = slopes[n - 2];
let m1 = if n > 2 { slopes[n - 3] } else { slopes[n - 2] };
let mut d = ((two * h0 + h1) * m0 - h0 * m1) / (h0 + h1);
let sign_d = if d >= F::zero() { F::one() } else { -F::one() };
let sign_m0 = if m0 >= F::zero() { F::one() } else { -F::one() };
let sign_m1 = if m1 >= F::zero() { F::one() } else { -F::one() };
if sign_d != sign_m0 {
d = F::zero();
} else if (sign_m0 != sign_m1) && (d.abs() > three * m0.abs()) {
d = three * m0;
}
derivatives[n - 1] = d;
Ok(derivatives)
}
fn find_hyman_derivatives(x: &Array1<F>, y: &Array1<F>) -> InterpolateResult<Array1<F>> {
let n = x.len();
let mut derivatives = Array1::zeros(n);
if let Ok(spline) = CubicSpline::new(&x.view(), &y.view()) {
for i in 0..n {
derivatives[i] = spline.derivative(x[i]).unwrap_or(F::zero());
}
} else {
return Self::find_pchip_derivatives(x, y);
}
let mut slopes = Array1::zeros(n - 1);
for i in 0..n - 1 {
slopes[i] = (y[i + 1] - y[i]) / (x[i + 1] - x[i]);
}
let three = F::from_f64(3.0).ok_or_else(|| {
InterpolateError::ComputationError(
"Failed to convert constant 3.0 to float type".to_string(),
)
})?;
for i in 0..n {
if i > 0 && i < n - 1 {
let left_slope = slopes[i - 1];
let right_slope = slopes[i];
if left_slope * right_slope <= F::zero() {
derivatives[i] = F::zero();
} else {
let max_slope = three * F::min(left_slope.abs(), right_slope.abs());
if derivatives[i].abs() > max_slope {
derivatives[i] = max_slope * derivatives[i].signum();
}
}
} else if i == 0 {
let slope = slopes[0];
if slope * derivatives[0] < F::zero() {
derivatives[0] = F::zero();
} else if derivatives[0].abs() > three * slope.abs() {
derivatives[0] = three * slope;
}
} else {
let slope = slopes[n - 2];
if slope * derivatives[n - 1] < F::zero() {
derivatives[n - 1] = F::zero();
} else if derivatives[n - 1].abs() > three * slope.abs() {
derivatives[n - 1] = three * slope;
}
}
}
Ok(derivatives)
}
fn find_steffen_derivatives(x: &Array1<F>, y: &Array1<F>) -> InterpolateResult<Array1<F>> {
let n = x.len();
let mut derivatives = Array1::zeros(n);
if n == 2 {
let slope = (y[1] - y[0]) / (x[1] - x[0]);
derivatives[0] = slope;
derivatives[1] = slope;
return Ok(derivatives);
}
let mut slopes = Array1::zeros(n - 1);
for i in 0..n - 1 {
slopes[i] = (y[i + 1] - y[i]) / (x[i + 1] - x[i]);
}
for i in 1..n - 1 {
let p1 = slopes[i - 1]; let p2 = slopes[i];
let h1 = x[i] - x[i - 1];
let h2 = x[i + 1] - x[i];
let a = (h2 * p1 + h1 * p2) / (h1 + h2);
let min_slope = F::min(p1.abs(), p2.abs());
let min_slope_signed = if p1 * p2 > F::zero() {
min_slope * p1.signum()
} else {
F::zero()
};
derivatives[i] = min_slope_signed;
if p1 * p2 > F::zero() {
let two = F::from_f64(2.0).ok_or_else(|| {
InterpolateError::ComputationError(
"Failed to convert constant 2.0 to float type".to_string(),
)
})?;
derivatives[i] = F::min(a.abs(), min_slope * two) * a.signum();
}
}
let p1 = slopes[0];
derivatives[0] = if p1.abs() <= F::epsilon() {
F::zero()
} else {
p1
};
let p2 = slopes[n - 2];
derivatives[n - 1] = if p2.abs() <= F::epsilon() {
F::zero()
} else {
p2
};
Ok(derivatives)
}
fn find_modified_akima_derivatives(
x: &Array1<F>,
y: &Array1<F>,
) -> InterpolateResult<Array1<F>> {
let n = x.len();
let mut derivatives = Array1::zeros(n);
if n == 2 {
let slope = (y[1] - y[0]) / (x[1] - x[0]);
derivatives[0] = slope;
derivatives[1] = slope;
return Ok(derivatives);
} else if n == 3 {
let slope1 = (y[1] - y[0]) / (x[1] - x[0]);
let slope2 = (y[2] - y[1]) / (x[2] - x[1]);
derivatives[0] = slope1;
derivatives[1] = (slope1 + slope2) / F::from_f64(2.0).expect("Test/example failed");
derivatives[2] = slope2;
if slope1 * slope2 <= F::zero() {
derivatives[1] = F::zero();
}
return Ok(derivatives);
}
let mut slopes = Array1::zeros(n - 1);
for i in 0..n - 1 {
slopes[i] = (y[i + 1] - y[i]) / (x[i + 1] - x[i]);
}
let epsilon = F::from_f64(1e-10).expect("Test/example failed");
for i in 1..n - 1 {
let s1 = if i > 1 {
slopes[i - 2]
} else {
F::from_f64(2.0).expect("Failed to convert 2.0 to target float type") * slopes[0]
- slopes[1]
};
let s2 = slopes[i - 1];
let s3 = slopes[i];
let s4 = if i < n - 2 {
slopes[i + 1]
} else {
F::from_f64(2.0).expect("Failed to convert 2.0 to target float type")
* slopes[n - 2]
- slopes[n - 3]
};
let w1 = (s3 - s2).abs();
let w2 = (s1 - s4).abs();
if w1.abs() < epsilon && w2.abs() < epsilon {
derivatives[i] = (s2 + s3) / F::from_f64(2.0).expect("Test/example failed");
} else {
derivatives[i] = (w1 * s3 + w2 * s2) / (w1 + w2);
}
if s2 * s3 <= F::zero() {
derivatives[i] = F::zero();
} else {
let three = F::from_f64(3.0).expect("Test/example failed");
let max_slope = three * F::min(s2.abs(), s3.abs());
if derivatives[i].abs() > max_slope {
derivatives[i] = max_slope * derivatives[i].signum();
}
}
}
let s1 = slopes[0];
let s2 = if n > 2 { slopes[1] } else { s1 };
if s1 * s2 <= F::zero() {
derivatives[0] = F::zero(); } else {
derivatives[0] =
(F::from_f64(2.0).expect("Failed to convert 2.0 to target float type") * s1 * s2)
/ (s1 + s2);
let three = F::from_f64(3.0).expect("Test/example failed");
let max_slope = three * s1.abs();
if derivatives[0].abs() > max_slope {
derivatives[0] = max_slope * derivatives[0].signum();
}
}
let s1 = slopes[n - 2];
let s2 = if n > 2 { slopes[n - 3] } else { s1 };
if s1 * s2 <= F::zero() {
derivatives[n - 1] = F::zero(); } else {
derivatives[n - 1] =
(F::from_f64(2.0).expect("Failed to convert 2.0 to target float type") * s1 * s2)
/ (s1 + s2);
let three = F::from_f64(3.0).expect("Test/example failed");
let max_slope = three * s1.abs();
if derivatives[n - 1].abs() > max_slope {
derivatives[n - 1] = max_slope * derivatives[n - 1].signum();
}
}
Ok(derivatives)
}
}
#[allow(dead_code)]
pub fn monotonic_interpolate<
F: Float + FromPrimitive + Debug + crate::traits::InterpolationFloat,
>(
x: &ArrayView1<F>,
y: &ArrayView1<F>,
xnew: &ArrayView1<F>,
method: MonotonicMethod,
extrapolate: bool,
) -> InterpolateResult<Array1<F>> {
let interp = MonotonicInterpolator::new(x, y, method, extrapolate)?;
interp.evaluate_array(xnew)
}
#[allow(dead_code)]
pub fn hyman_interpolate<F: Float + FromPrimitive + Debug + crate::traits::InterpolationFloat>(
x: &ArrayView1<F>,
y: &ArrayView1<F>,
xnew: &ArrayView1<F>,
extrapolate: bool,
) -> InterpolateResult<Array1<F>> {
monotonic_interpolate(x, y, xnew, MonotonicMethod::Hyman, extrapolate)
}
#[allow(dead_code)]
pub fn steffen_interpolate<F: Float + FromPrimitive + Debug + crate::traits::InterpolationFloat>(
x: &ArrayView1<F>,
y: &ArrayView1<F>,
xnew: &ArrayView1<F>,
extrapolate: bool,
) -> InterpolateResult<Array1<F>> {
monotonic_interpolate(x, y, xnew, MonotonicMethod::Steffen, extrapolate)
}
#[allow(dead_code)]
pub fn modified_akima_interpolate<
F: Float + FromPrimitive + Debug + crate::traits::InterpolationFloat,
>(
x: &ArrayView1<F>,
y: &ArrayView1<F>,
xnew: &ArrayView1<F>,
extrapolate: bool,
) -> InterpolateResult<Array1<F>> {
monotonic_interpolate(x, y, xnew, MonotonicMethod::ModifiedAkima, extrapolate)
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_relative_eq;
use scirs2_core::ndarray::array;
#[test]
fn test_monotonic_methods_at_data_points() {
let x = array![0.0, 1.0, 2.0, 3.0];
let y = array![0.0, 1.0, 4.0, 9.0];
for method in &[
MonotonicMethod::Pchip,
MonotonicMethod::Hyman,
MonotonicMethod::Steffen,
MonotonicMethod::ModifiedAkima,
] {
let interp = MonotonicInterpolator::new(&x.view(), &y.view(), *method, false)
.expect("Test/example failed");
assert_relative_eq!(
interp
.evaluate(0.0)
.expect("Interpolation evaluation failed"),
0.0
);
assert_relative_eq!(
interp
.evaluate(1.0)
.expect("Interpolation evaluation failed"),
1.0
);
assert_relative_eq!(
interp
.evaluate(2.0)
.expect("Interpolation evaluation failed"),
4.0
);
assert_relative_eq!(
interp
.evaluate(3.0)
.expect("Interpolation evaluation failed"),
9.0
);
}
}
#[test]
fn test_monotonicity_preservation() {
let x = array![0.0, 1.0, 2.0, 3.0, 4.0, 5.0];
let y = array![0.0, 1.0, 0.5, 0.0, 0.5, 2.0];
for method in &[
MonotonicMethod::Pchip,
MonotonicMethod::Hyman,
MonotonicMethod::Steffen,
MonotonicMethod::ModifiedAkima,
] {
let interp = MonotonicInterpolator::new(&x.view(), &y.view(), *method, false)
.expect("Test/example failed");
let y_0_25 = interp.evaluate(0.25).expect("Test/example failed");
let y_0_50 = interp.evaluate(0.50).expect("Test/example failed");
let y_0_75 = interp.evaluate(0.75).expect("Test/example failed");
assert!(y_0_25 <= y_0_50 && y_0_50 <= y_0_75);
let y_1_25 = interp.evaluate(1.25).expect("Test/example failed");
let y_1_50 = interp.evaluate(1.50).expect("Test/example failed");
let y_1_75 = interp.evaluate(1.75).expect("Test/example failed");
assert!(y_1_25 >= y_1_50 && y_1_50 >= y_1_75);
let y_2_25 = interp.evaluate(2.25).expect("Test/example failed");
let y_2_50 = interp.evaluate(2.50).expect("Test/example failed");
let y_2_75 = interp.evaluate(2.75).expect("Test/example failed");
assert!(y_2_25 >= y_2_50 && y_2_50 >= y_2_75);
let y_3_25 = interp.evaluate(3.25).expect("Test/example failed");
let y_3_50 = interp.evaluate(3.50).expect("Test/example failed");
let y_3_75 = interp.evaluate(3.75).expect("Test/example failed");
assert!(y_3_25 <= y_3_50 && y_3_50 <= y_3_75);
let y_4_25 = interp.evaluate(4.25).expect("Test/example failed");
let y_4_50 = interp.evaluate(4.50).expect("Test/example failed");
let y_4_75 = interp.evaluate(4.75).expect("Test/example failed");
assert!(y_4_25 <= y_4_50 && y_4_50 <= y_4_75);
}
}
#[test]
fn test_avoid_overshooting() {
let x = array![0.0, 1.0, 2.0, 3.0, 4.0];
let y = array![0.0, 0.0, 0.0, 1.0, 1.0];
for method in &[
MonotonicMethod::Pchip,
MonotonicMethod::Hyman,
MonotonicMethod::Steffen,
MonotonicMethod::ModifiedAkima,
] {
let interp = MonotonicInterpolator::new(&x.view(), &y.view(), *method, false)
.expect("Test/example failed");
let y_0_5 = interp.evaluate(0.5).expect("Test/example failed");
assert_relative_eq!(y_0_5, 0.0, max_relative = 1e-8);
let y_1_5 = interp.evaluate(1.5).expect("Test/example failed");
assert_relative_eq!(y_1_5, 0.0, max_relative = 1e-8);
let y_2_25 = interp.evaluate(2.25).expect("Test/example failed");
let y_2_50 = interp.evaluate(2.50).expect("Test/example failed");
let y_2_75 = interp.evaluate(2.75).expect("Test/example failed");
assert!(y_2_25 <= y_2_50 && y_2_50 <= y_2_75);
assert!(y_2_25 >= 0.0);
assert!(y_2_50 >= 0.0);
assert!(y_2_75 >= 0.0);
assert!(y_2_25 <= 1.0);
assert!(y_2_50 <= 1.0);
assert!(y_2_75 <= 1.0);
let y_3_5 = interp.evaluate(3.5).expect("Test/example failed");
assert_relative_eq!(y_3_5, 1.0, max_relative = 1e-8);
}
}
#[test]
fn test_special_cases() {
let x = array![0.0, 1.0];
let y = array![0.0, 1.0];
for method in &[
MonotonicMethod::Pchip,
MonotonicMethod::Hyman,
MonotonicMethod::Steffen,
MonotonicMethod::ModifiedAkima,
] {
let interp = MonotonicInterpolator::new(&x.view(), &y.view(), *method, false)
.expect("Test/example failed");
assert_relative_eq!(
interp
.evaluate(0.0)
.expect("Interpolation evaluation failed"),
0.0
);
assert_relative_eq!(
interp
.evaluate(0.5)
.expect("Interpolation evaluation failed"),
0.5
);
assert_relative_eq!(
interp
.evaluate(1.0)
.expect("Interpolation evaluation failed"),
1.0
);
}
let x = array![0.0, 1.0, 2.0];
let y = array![0.0, 1.0, 0.0];
for method in &[
MonotonicMethod::Pchip,
MonotonicMethod::Hyman,
MonotonicMethod::Steffen,
MonotonicMethod::ModifiedAkima,
] {
let interp = MonotonicInterpolator::new(&x.view(), &y.view(), *method, false)
.expect("Test/example failed");
let y_0_25 = interp.evaluate(0.25).expect("Test/example failed");
let y_0_50 = interp.evaluate(0.50).expect("Test/example failed");
let y_0_75 = interp.evaluate(0.75).expect("Test/example failed");
assert!(y_0_25 <= y_0_50 && y_0_50 <= y_0_75);
let y_1_25 = interp.evaluate(1.25).expect("Test/example failed");
let y_1_50 = interp.evaluate(1.50).expect("Test/example failed");
let y_1_75 = interp.evaluate(1.75).expect("Test/example failed");
assert!(y_1_25 >= y_1_50 && y_1_50 >= y_1_75);
}
}
#[test]
fn test_extrapolation() {
let x = array![0.0, 1.0, 2.0, 3.0];
let y = array![0.0, 1.0, 4.0, 9.0];
for method in &[
MonotonicMethod::Pchip,
MonotonicMethod::Hyman,
MonotonicMethod::Steffen,
MonotonicMethod::ModifiedAkima,
] {
let interp_extrap = MonotonicInterpolator::new(&x.view(), &y.view(), *method, true)
.expect("Test/example failed");
let _y_minus_1 = interp_extrap.evaluate(-1.0).expect("Test/example failed");
let _y_plus_4 = interp_extrap.evaluate(4.0).expect("Test/example failed");
let interp_no_extrap = MonotonicInterpolator::new(&x.view(), &y.view(), *method, false)
.expect("Test/example failed");
assert!(interp_no_extrap.evaluate(-1.0).is_err());
assert!(interp_no_extrap.evaluate(4.0).is_err());
}
}
#[test]
fn test_convenience_functions() {
let x = array![0.0, 1.0, 2.0, 3.0];
let y = array![0.0, 1.0, 4.0, 9.0];
let xnew = array![0.5, 1.5, 2.5];
let y_hyman = hyman_interpolate(&x.view(), &y.view(), &xnew.view(), false)
.expect("Test/example failed");
let y_steffen = steffen_interpolate(&x.view(), &y.view(), &xnew.view(), false)
.expect("Test/example failed");
let y_akima = modified_akima_interpolate(&x.view(), &y.view(), &xnew.view(), false)
.expect("Test/example failed");
let y_generic = monotonic_interpolate(
&x.view(),
&y.view(),
&xnew.view(),
MonotonicMethod::Pchip,
false,
)
.expect("Test/example failed");
assert_eq!(y_hyman.len(), 3);
assert_eq!(y_steffen.len(), 3);
assert_eq!(y_akima.len(), 3);
assert_eq!(y_generic.len(), 3);
for result in [&y_hyman, &y_steffen, &y_akima, &y_generic] {
assert!(result[0] > 0.0 && result[0] < 1.0);
assert!(result[1] > 1.0 && result[1] < 4.0);
assert!(result[2] > 4.0 && result[2] < 9.0);
}
}
#[test]
fn test_error_conditions() {
let x = array![0.0, 1.0, 2.0, 3.0];
let y = array![0.0, 1.0, 4.0];
for method in &[
MonotonicMethod::Pchip,
MonotonicMethod::Hyman,
MonotonicMethod::Steffen,
MonotonicMethod::ModifiedAkima,
] {
assert!(MonotonicInterpolator::new(&x.view(), &y.view(), *method, false).is_err());
}
let x_unsorted = array![0.0, 2.0, 1.0, 3.0];
let y_valid = array![0.0, 1.0, 4.0, 9.0];
for method in &[
MonotonicMethod::Pchip,
MonotonicMethod::Hyman,
MonotonicMethod::Steffen,
MonotonicMethod::ModifiedAkima,
] {
assert!(MonotonicInterpolator::new(
&x_unsorted.view(),
&y_valid.view(),
*method,
false
)
.is_err());
}
let x_short = array![0.0];
let y_short = array![0.0];
for method in &[
MonotonicMethod::Pchip,
MonotonicMethod::Hyman,
MonotonicMethod::Steffen,
MonotonicMethod::ModifiedAkima,
] {
assert!(
MonotonicInterpolator::new(&x_short.view(), &y_short.view(), *method, false)
.is_err()
);
}
}
}