scirs2_interpolate/interp1d/
pchip.rs1use crate::error::{InterpolateError, InterpolateResult};
7use scirs2_core::ndarray::{Array1, ArrayView1};
8use scirs2_core::numeric::{Float, FromPrimitive};
9use std::fmt::Debug;
10
11#[derive(Debug, Clone, Copy, PartialEq, Default)]
13pub enum PchipExtrapolateMode {
14 #[default]
17 Linear,
18 Polynomial,
22}
23
24#[derive(Debug, Clone)]
39pub struct PchipInterpolator<F: Float> {
40 x: Array1<F>,
42 y: Array1<F>,
44 derivatives: Array1<F>,
46 extrapolate: bool,
48 extrapolate_mode: PchipExtrapolateMode,
50}
51
52impl<F: Float + FromPrimitive + Debug> PchipInterpolator<F> {
53 pub fn new(x: &ArrayView1<F>, y: &ArrayView1<F>, extrapolate: bool) -> InterpolateResult<Self> {
89 if x.len() != y.len() {
91 return Err(InterpolateError::invalid_input(
92 "x and y arrays must have the same length".to_string(),
93 ));
94 }
95
96 if x.len() < 2 {
97 return Err(InterpolateError::insufficient_points(
98 2,
99 x.len(),
100 "PCHIP interpolation",
101 ));
102 }
103
104 for i in 1..x.len() {
106 if x[i] <= x[i - 1] {
107 return Err(InterpolateError::invalid_input(
108 "x values must be sorted in ascending order".to_string(),
109 ));
110 }
111 }
112
113 let x_arr = x.to_owned();
115 let y_arr = y.to_owned();
116
117 let derivatives = Self::find_derivatives(&x_arr, &y_arr)?;
119
120 Ok(PchipInterpolator {
121 x: x_arr,
122 y: y_arr,
123 derivatives,
124 extrapolate,
125 extrapolate_mode: PchipExtrapolateMode::Linear,
126 })
127 }
128
129 pub fn with_extrapolate_mode(mut self, mode: PchipExtrapolateMode) -> Self {
135 self.extrapolate_mode = mode;
136 self
137 }
138
139 pub fn evaluate(&self, xnew: F) -> InterpolateResult<F> {
154 let n = self.x.len();
155
156 let is_extrapolating = xnew < self.x[0] || xnew > self.x[n - 1];
158 if is_extrapolating && !self.extrapolate {
159 return Err(InterpolateError::OutOfBounds(
160 "xnew is outside the interpolation range".to_string(),
161 ));
162 }
163
164 if is_extrapolating && self.extrapolate_mode == PchipExtrapolateMode::Linear {
166 if xnew < self.x[0] {
167 let dx = xnew - self.x[0];
168 return Ok(self.y[0] + self.derivatives[0] * dx);
169 } else {
170 let dx = xnew - self.x[n - 1];
171 return Ok(self.y[n - 1] + self.derivatives[n - 1] * dx);
172 }
173 }
174
175 if !is_extrapolating && xnew == self.x[n - 1] {
177 return Ok(self.y[n - 1]);
178 }
179
180 let idx = if is_extrapolating {
182 if xnew < self.x[0] {
184 0
185 } else {
186 n - 2
187 }
188 } else {
189 let mut seg = 0;
191 for i in 0..n - 1 {
192 if xnew >= self.x[i] && xnew <= self.x[i + 1] {
193 seg = i;
194 break;
195 }
196 }
197 seg
198 };
199
200 let x1 = self.x[idx];
202 let x2 = self.x[idx + 1];
203 let y1 = self.y[idx];
204 let y2 = self.y[idx + 1];
205 let d1 = self.derivatives[idx];
206 let d2 = self.derivatives[idx + 1];
207
208 let h = x2 - x1;
211 let t = (xnew - x1) / h;
212
213 let h00 = Self::h00(t);
215 let h10 = Self::h10(t);
216 let h01 = Self::h01(t);
217 let h11 = Self::h11(t);
218
219 let result = h00 * y1 + h10 * h * d1 + h01 * y2 + h11 * h * d2;
221
222 Ok(result)
223 }
224
225 pub fn evaluate_array(&self, xnew: &ArrayView1<F>) -> InterpolateResult<Array1<F>> {
240 let mut result = Array1::zeros(xnew.len());
241 for (i, &x) in xnew.iter().enumerate() {
242 result[i] = self.evaluate(x)?;
243 }
244 Ok(result)
245 }
246
247 fn h00(t: F) -> F {
249 let two = F::from_f64(2.0).expect("Operation failed");
250 let three = F::from_f64(3.0).expect("Operation failed");
251 (two * t * t * t) - (three * t * t) + F::one()
252 }
253
254 fn h10(t: F) -> F {
256 let two = F::from_f64(2.0).expect("Operation failed");
257 (t * t * t) - (two * t * t) + t
259 }
260
261 fn h01(t: F) -> F {
263 let two = F::from_f64(2.0).expect("Operation failed");
264 let three = F::from_f64(3.0).expect("Operation failed");
265 -(two * t * t * t) + (three * t * t)
266 }
267
268 fn h11(t: F) -> F {
270 (t * t * t) - (t * t)
272 }
273
274 fn edge_case(h0: F, h1: F, m0: F, m1: F) -> F {
287 let two = F::from_f64(2.0).expect("Operation failed");
289 let three = F::from_f64(3.0).expect("Operation failed");
290
291 let d = ((two * h0 + h1) * m0 - h0 * m1) / (h0 + h1);
292
293 let sign_d = if d >= F::zero() { F::one() } else { -F::one() };
295 let sign_m0 = if m0 >= F::zero() { F::one() } else { -F::one() };
296 let sign_m1 = if m1 >= F::zero() { F::one() } else { -F::one() };
297
298 if sign_d != sign_m0 {
300 F::zero()
301 } else if (sign_m0 != sign_m1) && (d.abs() > three * m0.abs()) {
302 three * m0
303 } else {
304 d
305 }
306 }
307
308 fn find_derivatives(x: &Array1<F>, y: &Array1<F>) -> InterpolateResult<Array1<F>> {
319 let n = x.len();
320 let mut derivatives = Array1::zeros(n);
321
322 if n == 2 {
324 let slope = (y[1] - y[0]) / (x[1] - x[0]);
325 derivatives[0] = slope;
326 derivatives[1] = slope;
327 return Ok(derivatives);
328 }
329
330 let mut slopes = Array1::zeros(n - 1);
332 for i in 0..n - 1 {
333 slopes[i] = (y[i + 1] - y[i]) / (x[i + 1] - x[i]);
334 }
335
336 let mut h = Array1::zeros(n - 1);
338 for i in 0..n - 1 {
339 h[i] = x[i + 1] - x[i];
340 }
341
342 let two = F::from_f64(2.0).expect("Operation failed");
344
345 for i in 1..n - 1 {
346 let prev_slope = slopes[i - 1];
348 let curr_slope = slopes[i];
349
350 let sign_prev = if prev_slope > F::zero() {
351 F::one()
352 } else if prev_slope < F::zero() {
353 -F::one()
354 } else {
355 F::zero()
356 };
357
358 let sign_curr = if curr_slope > F::zero() {
359 F::one()
360 } else if curr_slope < F::zero() {
361 -F::one()
362 } else {
363 F::zero()
364 };
365
366 if sign_prev * sign_curr <= F::zero() {
368 derivatives[i] = F::zero();
369 } else {
370 let w1 = two * h[i] + h[i - 1];
372 let w2 = h[i] + two * h[i - 1];
373
374 if prev_slope.abs() < F::epsilon() || curr_slope.abs() < F::epsilon() {
376 derivatives[i] = F::zero();
377 } else {
378 let whmean_inv = (w1 / prev_slope + w2 / curr_slope) / (w1 + w2);
379 derivatives[i] = F::one() / whmean_inv;
380 }
381 }
382 }
383
384 derivatives[0] = Self::edge_case(h[0], h[1], slopes[0], slopes[1]);
387
388 derivatives[n - 1] = Self::edge_case(h[n - 2], h[n - 3], slopes[n - 2], slopes[n - 3]);
390
391 Ok(derivatives)
392 }
393}
394
395#[allow(dead_code)]
421pub fn pchip_interpolate<F: crate::traits::InterpolationFloat>(
422 x: &ArrayView1<F>,
423 y: &ArrayView1<F>,
424 xnew: &ArrayView1<F>,
425 extrapolate: bool,
426) -> InterpolateResult<Array1<F>> {
427 let interp = PchipInterpolator::new(x, y, extrapolate)?;
428 interp.evaluate_array(xnew)
429}
430
431#[cfg(test)]
432mod tests {
433 use super::*;
434 use approx::assert_relative_eq;
435 use scirs2_core::ndarray::array;
436
437 #[test]
438 fn test_pchip_interpolation_basic() {
439 let x = array![0.0, 1.0, 2.0, 3.0];
440 let y = array![0.0, 1.0, 4.0, 9.0];
441
442 let interp = PchipInterpolator::new(&x.view(), &y.view(), false).expect("Operation failed");
443
444 assert_relative_eq!(interp.evaluate(0.0).expect("Operation failed"), 0.0);
446 assert_relative_eq!(interp.evaluate(1.0).expect("Operation failed"), 1.0);
447 assert_relative_eq!(interp.evaluate(2.0).expect("Operation failed"), 4.0);
448 assert_relative_eq!(interp.evaluate(3.0).expect("Operation failed"), 9.0);
449
450 let y_interp_0_5 = interp.evaluate(0.5).expect("Operation failed");
452 let y_interp_1_5 = interp.evaluate(1.5).expect("Operation failed");
453 let y_interp_2_5 = interp.evaluate(2.5).expect("Operation failed");
454
455 assert!(y_interp_0_5 > 0.0 && y_interp_0_5 < 1.0);
457 assert!(y_interp_1_5 > 1.0 && y_interp_1_5 < 4.0);
458 assert!(y_interp_2_5 > 4.0 && y_interp_2_5 < 9.0);
459 }
460
461 #[test]
462 fn test_pchip_monotonicity_preservation() {
463 let x = array![0.0, 1.0, 2.0, 3.0, 4.0, 5.0];
465 let y = array![0.0, 1.0, 0.5, 0.0, 0.5, 2.0];
466
467 let interp = PchipInterpolator::new(&x.view(), &y.view(), false).expect("Operation failed");
468
469 let y_0_25 = interp.evaluate(0.25).expect("Operation failed");
471 let y_0_50 = interp.evaluate(0.50).expect("Operation failed");
472 let y_0_75 = interp.evaluate(0.75).expect("Operation failed");
473 assert!(y_0_25 <= y_0_50 && y_0_50 <= y_0_75);
474
475 let y_1_25 = interp.evaluate(1.25).expect("Operation failed");
477 let y_1_50 = interp.evaluate(1.50).expect("Operation failed");
478 let y_1_75 = interp.evaluate(1.75).expect("Operation failed");
479 assert!(y_1_25 >= y_1_50 && y_1_50 >= y_1_75);
480
481 let y_4_25 = interp.evaluate(4.25).expect("Operation failed");
483 let y_4_50 = interp.evaluate(4.50).expect("Operation failed");
484 let y_4_75 = interp.evaluate(4.75).expect("Operation failed");
485 assert!(y_4_25 <= y_4_50 && y_4_50 <= y_4_75);
486 }
487
488 #[test]
489 fn test_pchip_extrapolation() {
490 let x = array![0.0, 1.0, 2.0, 3.0];
491 let y = array![0.0, 1.0, 4.0, 9.0];
492
493 let interp_extrap =
495 PchipInterpolator::new(&x.view(), &y.view(), true).expect("Operation failed");
496 let y_minus_1 = interp_extrap.evaluate(-1.0).expect("Operation failed");
497 let y_plus_4 = interp_extrap.evaluate(4.0).expect("Operation failed");
498
499 assert!(
503 y_plus_4 > 9.0,
504 "Extrapolation above should be greater than last point"
505 );
506
507 assert!(
509 y_minus_1.is_finite(),
510 "Extrapolation should produce finite values"
511 );
512 assert!(
513 y_plus_4.is_finite(),
514 "Extrapolation should produce finite values"
515 );
516
517 let interp_no_extrap =
519 PchipInterpolator::new(&x.view(), &y.view(), false).expect("Operation failed");
520 assert!(interp_no_extrap.evaluate(-1.0).is_err());
521 assert!(interp_no_extrap.evaluate(4.0).is_err());
522 }
523
524 #[test]
525 fn test_pchip_extrapolation_far_beyond_range() {
526 let x = array![0.0, 1.0, 2.0, 3.0];
529 let y = array![0.0, 1.0, 4.0, 9.0];
530
531 let interp = PchipInterpolator::new(&x.view(), &y.view(), true).expect("Operation failed");
532
533 let y_50 = interp.evaluate(50.0).expect("Operation failed");
535
536 assert!(
542 y_50 > 9.0,
543 "Extrapolation at x=50 should be > 9.0, got {}",
544 y_50
545 );
546 assert!(
547 y_50 < 1000.0,
548 "Extrapolation should be reasonable (linear), got {}",
549 y_50
550 );
551 assert!(
552 y_50.is_finite(),
553 "Extrapolation should produce finite values"
554 );
555
556 let y_minus_50 = interp.evaluate(-50.0).expect("Operation failed");
559 assert!(
560 y_minus_50.is_finite(),
561 "Extrapolation should produce finite values"
562 );
563 assert!(
564 y_minus_50.abs() < 1000.0,
565 "Extrapolation should be reasonable (linear), got {}",
566 y_minus_50
567 );
568 }
569
570 #[test]
571 fn test_pchip_extrapolation_linear_behavior() {
572 let x = array![0.0, 1.0, 2.0, 3.0];
574 let y = array![0.0, 1.0, 4.0, 9.0];
575
576 let interp = PchipInterpolator::new(&x.view(), &y.view(), true).expect("Operation failed");
577
578 let y_4 = interp.evaluate(4.0).expect("Operation failed");
580 let y_5 = interp.evaluate(5.0).expect("Operation failed");
581
582 let extrap_slope = y_5 - y_4;
585
586 let last_derivative = interp.derivatives[interp.derivatives.len() - 1];
588
589 assert_relative_eq!(extrap_slope, last_derivative, epsilon = 1e-10);
591 }
592
593 #[test]
594 fn test_pchip_interpolate_function() {
595 let x = array![0.0, 1.0, 2.0, 3.0];
596 let y = array![0.0, 1.0, 4.0, 9.0];
597 let xnew = array![0.5, 1.5, 2.5];
598
599 let y_interp =
600 pchip_interpolate(&x.view(), &y.view(), &xnew.view(), false).expect("Operation failed");
601
602 assert_eq!(y_interp.len(), 3);
604
605 assert!(y_interp[0] > 0.0 && y_interp[0] < 1.0);
607 assert!(y_interp[1] > 1.0 && y_interp[1] < 4.0);
608 assert!(y_interp[2] > 4.0 && y_interp[2] < 9.0);
609 }
610
611 #[test]
612 fn test_pchip_error_conditions() {
613 let x = array![0.0, 1.0, 2.0, 3.0];
615 let y = array![0.0, 1.0, 4.0];
616 assert!(PchipInterpolator::new(&x.view(), &y.view(), false).is_err());
617
618 let x_unsorted = array![0.0, 2.0, 1.0, 3.0];
620 let y_valid = array![0.0, 1.0, 4.0, 9.0];
621 assert!(PchipInterpolator::new(&x_unsorted.view(), &y_valid.view(), false).is_err());
622
623 let x_short = array![0.0];
625 let y_short = array![0.0];
626 assert!(PchipInterpolator::new(&x_short.view(), &y_short.view(), false).is_err());
627 }
628}