1mod basic_interp;
6pub mod monotonic;
7pub mod pchip;
8
9pub use basic_interp::{cubic_interpolate, linear_interpolate, nearest_interpolate};
11pub use monotonic::{
12 hyman_interpolate, modified_akima_interpolate, monotonic_interpolate, steffen_interpolate,
13 MonotonicInterpolator, MonotonicMethod,
14};
15pub use pchip::{pchip_interpolate, PchipExtrapolateMode, PchipInterpolator};
16
17use crate::error::{InterpolateError, InterpolateResult};
18use scirs2_core::ndarray::{Array1, ArrayView1};
19use scirs2_core::numeric::{Float, FromPrimitive};
20use std::fmt::Debug;
21
22#[derive(Debug, Clone, Copy, PartialEq, Default)]
24pub enum InterpolationMethod {
25 Nearest,
27 #[default]
29 Linear,
30 Cubic,
32 Pchip,
34}
35
36#[derive(Debug, Clone, Copy, PartialEq, Default)]
38pub enum ExtrapolateMode {
39 #[default]
41 Error,
42 Extrapolate,
44 Nearest,
46}
47
48#[derive(Debug, Clone)]
53pub struct Interp1d<F: Float> {
54 x: Array1<F>,
56 y: Array1<F>,
58 method: InterpolationMethod,
60 extrapolate: ExtrapolateMode,
62 pchip_cache: Option<PchipInterpolator<F>>,
64}
65
66impl<F: Float + FromPrimitive + Debug + std::fmt::Display> Interp1d<F> {
67 pub fn new(
102 x: &ArrayView1<F>,
103 y: &ArrayView1<F>,
104 method: InterpolationMethod,
105 extrapolate: ExtrapolateMode,
106 ) -> InterpolateResult<Self> {
107 if x.len() != y.len() {
109 return Err(InterpolateError::invalid_input(
110 "x and y arrays must have the same length".to_string(),
111 ));
112 }
113
114 if x.len() < 2 {
115 return Err(InterpolateError::insufficient_points(
116 2,
117 x.len(),
118 "interpolation",
119 ));
120 }
121
122 for i in 0..x.len() {
124 if !x[i].is_finite() {
125 return Err(InterpolateError::invalid_input(format!(
126 "x values must be finite, found non-finite value at index {}",
127 i
128 )));
129 }
130 if !y[i].is_finite() {
131 return Err(InterpolateError::invalid_input(format!(
132 "y values must be finite, found non-finite value at index {}",
133 i
134 )));
135 }
136 }
137
138 for i in 1..x.len() {
140 if x[i] <= x[i - 1] {
141 return Err(InterpolateError::invalid_input(
142 "x values must be sorted in ascending order".to_string(),
143 ));
144 }
145 }
146
147 if method == InterpolationMethod::Cubic && x.len() < 4 {
149 return Err(InterpolateError::insufficient_points(
150 4,
151 x.len(),
152 "cubic interpolation",
153 ));
154 }
155
156 let pchip_cache = if method == InterpolationMethod::Pchip {
157 let pchip_extrap = extrapolate == ExtrapolateMode::Extrapolate
158 || extrapolate == ExtrapolateMode::Nearest;
159 let mut interp = PchipInterpolator::new(x, y, pchip_extrap)?;
160 if extrapolate == ExtrapolateMode::Extrapolate {
161 interp = interp.with_extrapolate_mode(PchipExtrapolateMode::Polynomial);
162 }
163 Some(interp)
164 } else {
165 None
166 };
167
168 Ok(Interp1d {
169 x: x.to_owned(),
170 y: y.to_owned(),
171 method,
172 extrapolate,
173 pchip_cache,
174 })
175 }
176
177 pub fn evaluate(&self, xnew: F) -> InterpolateResult<F> {
187 let is_extrapolating = xnew < self.x[0] || xnew > self.x[self.x.len() - 1];
189
190 if is_extrapolating {
191 match self.extrapolate {
192 ExtrapolateMode::Error => {
193 return Err(InterpolateError::out_of_domain_with_suggestion(
194 xnew,
195 self.x[0],
196 self.x[self.x.len() - 1],
197 "1D interpolation evaluation",
198 format!("Use ExtrapolateMode::Extrapolate for linear extrapolation, ExtrapolateMode::Nearest for constant extrapolation, or ensure query points are within the data range [{:?}, {:?}]",
199 self.x[0], self.x[self.x.len() - 1])
200 ));
201 }
202 ExtrapolateMode::Nearest => {
203 if xnew < self.x[0] {
204 return Ok(self.y[0]);
205 } else {
206 return Ok(self.y[self.y.len() - 1]);
207 }
208 }
209 ExtrapolateMode::Extrapolate => {
210 if let Some(ref pchip) = self.pchip_cache {
212 return pchip.evaluate(xnew);
213 }
214 if xnew < self.x[0] {
216 let x0 = self.x[0];
218 let x1 = self.x[1];
219 let y0 = self.y[0];
220 let y1 = self.y[1];
221
222 let slope = (y1 - y0) / (x1 - x0);
224 return Ok(y0 + (xnew - x0) * slope);
225 } else {
226 let n = self.x.len();
228 let x0 = self.x[n - 2];
229 let x1 = self.x[n - 1];
230 let y0 = self.y[n - 2];
231 let y1 = self.y[n - 1];
232
233 let slope = (y1 - y0) / (x1 - x0);
235 return Ok(y1 + (xnew - x1) * slope);
236 }
237 }
238 }
239 }
240
241 let idx = self.find_segment(xnew);
243
244 if xnew == self.x[self.x.len() - 1] {
246 return Ok(self.y[self.x.len() - 1]);
247 }
248
249 match self.method {
251 InterpolationMethod::Nearest => {
252 nearest_interp(&self.x.view(), &self.y.view(), idx, xnew)
253 }
254 InterpolationMethod::Linear => linear_interp(&self.x.view(), &self.y.view(), idx, xnew),
255 InterpolationMethod::Cubic => cubic_interp(&self.x.view(), &self.y.view(), idx, xnew),
256 InterpolationMethod::Pchip => {
257 match self.pchip_cache {
259 Some(ref pchip) => pchip.evaluate(xnew),
260 None => Err(InterpolateError::invalid_input(
261 "PCHIP cache missing (internal error)".to_string(),
262 )),
263 }
264 }
265 }
266 }
267
268 fn find_segment(&self, xnew: F) -> usize {
273 let n = self.x.len();
274 if n < 2 {
275 return 0;
276 }
277
278 let mut lo = 0usize;
280 let mut hi = n - 1;
281
282 if xnew <= self.x[0] {
284 return 0;
285 }
286 if xnew >= self.x[n - 1] {
287 return n - 2;
288 }
289
290 while hi - lo > 1 {
291 let mid = lo + (hi - lo) / 2;
292 if self.x[mid] <= xnew {
293 lo = mid;
294 } else {
295 hi = mid;
296 }
297 }
298
299 lo
300 }
301
302 pub fn evaluate_array(&self, xnew: &ArrayView1<F>) -> InterpolateResult<Array1<F>> {
312 let mut result = Array1::zeros(xnew.len());
313 for (i, &x) in xnew.iter().enumerate() {
314 result[i] = self.evaluate(x)?;
315 }
316 Ok(result)
317 }
318}
319
320#[allow(dead_code)]
333fn nearest_interp<F: Float>(
334 x: &ArrayView1<F>,
335 y: &ArrayView1<F>,
336 idx: usize,
337 xnew: F,
338) -> InterpolateResult<F> {
339 let dist_left = (xnew - x[idx]).abs();
341 let dist_right = (xnew - x[idx + 1]).abs();
342
343 if dist_left <= dist_right {
344 Ok(y[idx])
345 } else {
346 Ok(y[idx + 1])
347 }
348}
349
350#[allow(dead_code)]
363fn linear_interp<F: Float>(
364 x: &ArrayView1<F>,
365 y: &ArrayView1<F>,
366 idx: usize,
367 xnew: F,
368) -> InterpolateResult<F> {
369 let x0 = x[idx];
370 let x1 = x[idx + 1];
371 let y0 = y[idx];
372 let y1 = y[idx + 1];
373
374 if x0 == x1 {
376 return Ok(y0); }
378
379 Ok(y0 + (xnew - x0) * (y1 - y0) / (x1 - x0))
381}
382
383#[allow(dead_code)]
396fn cubic_interp<F: Float + FromPrimitive>(
397 x: &ArrayView1<F>,
398 y: &ArrayView1<F>,
399 idx: usize,
400 xnew: F,
401) -> InterpolateResult<F> {
402 let (i0, i1, i2, i3) = if idx == 0 {
405 (0, 0, 1, 2)
406 } else if idx == x.len() - 2 {
407 (idx - 1, idx, idx + 1, idx + 1)
408 } else {
409 (idx - 1, idx, idx + 1, idx + 2)
411 };
412
413 let _x0 = x[i0];
414 let x1 = x[i1];
415 let x2 = x[i2];
416 let _x3 = x[i3];
417
418 let y0 = y[i0];
419 let y1 = y[i1];
420 let y2 = y[i2];
421 let y3 = y[i3];
422
423 let t = if x2 != x1 {
425 (xnew - x1) / (x2 - x1)
426 } else {
427 F::zero()
428 };
429
430 let two = F::from_f64(2.0).expect("Operation failed");
437 let three = F::from_f64(3.0).expect("Operation failed");
438 let four = F::from_f64(4.0).expect("Operation failed");
439 let five = F::from_f64(5.0).expect("Operation failed");
440 let half = F::from_f64(0.5).expect("Operation failed");
441
442 let t2 = t * t;
443 let t3 = t2 * t;
444
445 let c0 = two * y1;
446 let c1 = -y0 + y2;
447 let c2 = two * y0 - five * y1 + four * y2 - y3;
448 let c3 = -y0 + three * y1 - three * y2 + y3;
449
450 let result = half * (c0 + c1 * t + c2 * t2 + c3 * t3);
451
452 Ok(result)
453}
454
455#[cfg(test)]
456mod tests {
457 use super::*;
458 use approx::assert_relative_eq;
459 use scirs2_core::ndarray::array;
460
461 #[test]
462 fn test_nearest_interpolation() {
463 let x = array![0.0, 1.0, 2.0, 3.0];
464 let y = array![0.0, 1.0, 4.0, 9.0];
465
466 let interp = Interp1d::new(
467 &x.view(),
468 &y.view(),
469 InterpolationMethod::Nearest,
470 ExtrapolateMode::Error,
471 )
472 .expect("Operation failed");
473
474 assert_relative_eq!(interp.evaluate(0.0).expect("Operation failed"), 0.0);
476 assert_relative_eq!(interp.evaluate(1.0).expect("Operation failed"), 1.0);
477 assert_relative_eq!(interp.evaluate(2.0).expect("Operation failed"), 4.0);
478 assert_relative_eq!(interp.evaluate(3.0).expect("Operation failed"), 9.0);
479
480 assert_relative_eq!(interp.evaluate(0.4).expect("Operation failed"), 0.0);
482 assert_relative_eq!(interp.evaluate(0.6).expect("Operation failed"), 1.0);
483 assert_relative_eq!(interp.evaluate(1.4).expect("Operation failed"), 1.0);
484 assert_relative_eq!(interp.evaluate(1.6).expect("Operation failed"), 4.0);
485 }
486
487 #[test]
488 fn test_linear_interpolation() {
489 let x = array![0.0, 1.0, 2.0, 3.0];
490 let y = array![0.0, 1.0, 4.0, 9.0];
491
492 let interp = Interp1d::new(
493 &x.view(),
494 &y.view(),
495 InterpolationMethod::Linear,
496 ExtrapolateMode::Error,
497 )
498 .expect("Operation failed");
499
500 assert_relative_eq!(interp.evaluate(0.0).expect("Operation failed"), 0.0);
502 assert_relative_eq!(interp.evaluate(1.0).expect("Operation failed"), 1.0);
503 assert_relative_eq!(interp.evaluate(2.0).expect("Operation failed"), 4.0);
504 assert_relative_eq!(interp.evaluate(3.0).expect("Operation failed"), 9.0);
505
506 assert_relative_eq!(interp.evaluate(0.5).expect("Operation failed"), 0.5);
508 assert_relative_eq!(interp.evaluate(1.5).expect("Operation failed"), 2.5);
509 assert_relative_eq!(interp.evaluate(2.5).expect("Operation failed"), 6.5);
510 }
511
512 #[test]
513 fn test_cubic_interpolation() {
514 let x = array![0.0, 1.0, 2.0, 3.0];
515 let y = array![0.0, 1.0, 4.0, 9.0];
516
517 let interp = Interp1d::new(
518 &x.view(),
519 &y.view(),
520 InterpolationMethod::Cubic,
521 ExtrapolateMode::Error,
522 )
523 .expect("Operation failed");
524
525 assert_relative_eq!(interp.evaluate(0.0).expect("Operation failed"), 0.0);
527 assert_relative_eq!(interp.evaluate(1.0).expect("Operation failed"), 1.0);
528 assert_relative_eq!(interp.evaluate(2.0).expect("Operation failed"), 4.0);
529 assert_relative_eq!(interp.evaluate(3.0).expect("Operation failed"), 9.0);
530
531 assert_relative_eq!(
535 interp.evaluate(0.5).expect("Operation failed"),
536 0.25,
537 epsilon = 0.1
538 );
539 assert_relative_eq!(
540 interp.evaluate(1.5).expect("Operation failed"),
541 2.25,
542 epsilon = 0.1
543 );
544 assert_relative_eq!(
545 interp.evaluate(2.5).expect("Operation failed"),
546 6.25,
547 epsilon = 1.0
548 );
549 }
550
551 #[test]
552 fn test_pchip_interpolation() {
553 let x = array![0.0, 1.0, 2.0, 3.0];
554 let y = array![0.0, 1.0, 4.0, 9.0];
555
556 let interp = Interp1d::new(
557 &x.view(),
558 &y.view(),
559 InterpolationMethod::Pchip,
560 ExtrapolateMode::Error,
561 )
562 .expect("Operation failed");
563
564 assert_relative_eq!(interp.evaluate(0.0).expect("Operation failed"), 0.0);
566 assert_relative_eq!(interp.evaluate(1.0).expect("Operation failed"), 1.0);
567 assert_relative_eq!(interp.evaluate(2.0).expect("Operation failed"), 4.0);
568 assert_relative_eq!(interp.evaluate(3.0).expect("Operation failed"), 9.0);
569
570 let y_05 = interp.evaluate(0.5).expect("Operation failed");
573 let y_15 = interp.evaluate(1.5).expect("Operation failed");
574 let y_25 = interp.evaluate(2.5).expect("Operation failed");
575
576 assert!(y_05 > 0.0 && y_05 < 1.0);
577 assert!(y_15 > 1.0 && y_15 < 4.0);
578 assert!(y_25 > 4.0 && y_25 < 9.0);
579 }
580
581 #[test]
582 fn test_extrapolation_modes() {
583 let x = array![0.0, 1.0, 2.0, 3.0];
584 let y = array![0.0, 1.0, 4.0, 9.0];
585
586 let interp_error = Interp1d::new(
588 &x.view(),
589 &y.view(),
590 InterpolationMethod::Linear,
591 ExtrapolateMode::Error,
592 )
593 .expect("Operation failed");
594
595 assert!(interp_error.evaluate(-1.0).is_err());
596 assert!(interp_error.evaluate(4.0).is_err());
597
598 let interp_nearest = Interp1d::new(
600 &x.view(),
601 &y.view(),
602 InterpolationMethod::Linear,
603 ExtrapolateMode::Nearest,
604 )
605 .expect("Operation failed");
606
607 assert_relative_eq!(
608 interp_nearest.evaluate(-1.0).expect("Operation failed"),
609 0.0
610 );
611 assert_relative_eq!(interp_nearest.evaluate(4.0).expect("Operation failed"), 9.0);
612
613 let interp_extrapolate = Interp1d::new(
615 &x.view(),
616 &y.view(),
617 InterpolationMethod::Linear,
618 ExtrapolateMode::Extrapolate,
619 )
620 .expect("Operation failed");
621
622 assert_relative_eq!(
625 interp_extrapolate.evaluate(-1.0).expect("Operation failed"),
626 -1.0
627 );
628
629 assert_relative_eq!(
632 interp_extrapolate.evaluate(4.0).expect("Operation failed"),
633 14.0
634 );
635 }
636
637 #[test]
638 fn test_convenience_functions() {
639 let x = array![0.0, 1.0, 2.0, 3.0];
640 let y = array![0.0, 1.0, 4.0, 9.0];
641 let xnew = array![0.5, 1.5, 2.5];
642
643 let y_nearest =
645 nearest_interpolate(&x.view(), &y.view(), &xnew.view()).expect("Operation failed");
646 assert_relative_eq!(y_nearest[0], 0.0);
648 assert_relative_eq!(y_nearest[1], 1.0);
650 assert_relative_eq!(y_nearest[2], 4.0);
652
653 let y_linear =
655 linear_interpolate(&x.view(), &y.view(), &xnew.view()).expect("Operation failed");
656 assert_relative_eq!(y_linear[0], 0.5);
657 assert_relative_eq!(y_linear[1], 2.5);
658 assert_relative_eq!(y_linear[2], 6.5);
659
660 let y_cubic =
662 cubic_interpolate(&x.view(), &y.view(), &xnew.view()).expect("Operation failed");
663 assert!((y_cubic[0] - 0.25).abs() < 0.15);
665 assert!((y_cubic[1] - 2.25).abs() < 0.15);
666 assert!((y_cubic[2] - 6.25).abs() < 1.0);
668
669 let y_pchip =
671 pchip_interpolate(&x.view(), &y.view(), &xnew.view(), false).expect("Operation failed");
672 assert!(y_pchip[0] > 0.0 && y_pchip[0] < 1.0);
674 assert!(y_pchip[1] > 1.0 && y_pchip[1] < 4.0);
675 assert!(y_pchip[2] > 4.0 && y_pchip[2] < 9.0);
676 }
677
678 #[test]
679 fn test_error_conditions() {
680 let x = array![0.0, 1.0, 2.0, 3.0];
681 let y = array![0.0, 1.0, 4.0];
682
683 let result = Interp1d::new(
685 &x.view(),
686 &y.view(),
687 InterpolationMethod::Linear,
688 ExtrapolateMode::Error,
689 );
690 assert!(result.is_err());
691
692 let x_unsorted = array![0.0, 2.0, 1.0, 3.0];
694 let y_valid = array![0.0, 1.0, 4.0, 9.0];
695
696 let result = Interp1d::new(
697 &x_unsorted.view(),
698 &y_valid.view(),
699 InterpolationMethod::Linear,
700 ExtrapolateMode::Error,
701 );
702 assert!(result.is_err());
703
704 let x_short = array![0.0, 1.0];
706 let y_short = array![0.0, 1.0];
707
708 let result = Interp1d::new(
709 &x_short.view(),
710 &y_short.view(),
711 InterpolationMethod::Cubic,
712 ExtrapolateMode::Error,
713 );
714 assert!(result.is_err());
715 }
716}