scirs2_interpolate/interpnd.rs
1//! N-dimensional interpolation methods
2//!
3//! This module provides functionality for interpolating multidimensional data.
4
5use crate::advanced::rbf::{RBFInterpolator, RBFKernel};
6use crate::error::{InterpolateError, InterpolateResult};
7use scirs2_core::ndarray::{Array, Array1, Array2, ArrayView1, ArrayView2, IxDyn};
8use scirs2_core::numeric::{Float, FromPrimitive};
9use std::fmt::{Debug, Display};
10use std::ops::{AddAssign, SubAssign};
11
12/// Available grid types for N-dimensional interpolation
13#[derive(Debug, Clone, Copy, PartialEq)]
14pub enum GridType {
15 /// Regular grid (evenly spaced points in each dimension)
16 Regular,
17 /// Rectilinear grid (unevenly spaced points along each axis)
18 Rectilinear,
19 /// Unstructured grid (arbitrary point positions)
20 Unstructured,
21}
22
23/// Extrapolation mode for N-dimensional interpolation
24#[derive(Debug, Clone, Copy, PartialEq)]
25pub enum ExtrapolateMode {
26 /// Return NaN for points outside the interpolation domain
27 Nan,
28 /// Raise an error for points outside the interpolation domain
29 Error,
30 /// Clamp out-of-range coordinates to the grid boundary before interpolating
31 Nearest,
32 /// Extrapolate beyond the grid using the boundary cell's slope
33 Extrapolate,
34}
35
36/// N-dimensional interpolation object for rectilinear grids
37///
38/// This interpolator works with data defined on a rectilinear grid,
39/// where each dimension has its own set of coordinates.
40#[derive(Debug, Clone)]
41pub struct RegularGridInterpolator<F: Float + FromPrimitive + Debug + Display> {
42 /// Grid points in each dimension
43 points: Vec<Array1<F>>,
44 /// Values at grid points
45 values: Array<F, IxDyn>,
46 /// Method to use for interpolation
47 method: InterpolationMethod,
48 /// How to handle points outside the domain
49 extrapolate: ExtrapolateMode,
50}
51
52/// Available interpolation methods for N-dimensional interpolation
53#[derive(Debug, Clone, Copy, PartialEq)]
54pub enum InterpolationMethod {
55 /// Nearest neighbor interpolation
56 Nearest,
57 /// Linear interpolation
58 Linear,
59 /// Spline interpolation
60 Spline,
61}
62
63impl<F: crate::traits::InterpolationFloat> RegularGridInterpolator<F> {
64 /// Create a new RegularGridInterpolator
65 ///
66 /// # Arguments
67 ///
68 /// * `points` - A vector of arrays, where each array contains the points in one dimension
69 /// * `values` - An N-dimensional array of values at the grid points
70 /// * `method` - Interpolation method to use
71 /// * `extrapolate` - How to handle points outside the domain
72 ///
73 /// # Returns
74 ///
75 /// A new RegularGridInterpolator object
76 ///
77 /// # Errors
78 ///
79 /// * If points dimensions don't match values dimensions
80 /// * If any dimension has less than 2 points
81 ///
82 /// # Examples
83 ///
84 /// ```rust
85 /// use scirs2_core::ndarray::{Array, Array1, Dim, IxDyn};
86 /// use scirs2_interpolate::interpnd::{
87 /// RegularGridInterpolator, InterpolationMethod, ExtrapolateMode
88 /// };
89 ///
90 /// // Create a 3D grid
91 /// let x = Array1::from_vec(vec![0.0, 1.0, 2.0]);
92 /// let y = Array1::from_vec(vec![0.0, 1.0]);
93 /// let z = Array1::from_vec(vec![0.0, 1.0, 2.0, 3.0]);
94 /// let points = vec![x, y, z];
95 ///
96 /// // Create values on the grid (3 × 2 × 4 = 24 values)
97 /// let mut values = Array::zeros(IxDyn(&[3, 2, 4]));
98 /// for i in 0..3 {
99 /// for j in 0..2 {
100 /// for k in 0..4 {
101 /// let idx = [i, j, k];
102 /// values[idx.as_slice()] = (i + j + k) as f64;
103 /// }
104 /// }
105 /// }
106 ///
107 /// let interpolator = RegularGridInterpolator::new(
108 /// points,
109 /// values,
110 /// InterpolationMethod::Linear,
111 /// ExtrapolateMode::Extrapolate,
112 /// ).expect("Operation failed");
113 /// ```
114 pub fn new(
115 points: Vec<Array1<F>>,
116 values: Array<F, IxDyn>,
117 method: InterpolationMethod,
118 extrapolate: ExtrapolateMode,
119 ) -> InterpolateResult<Self> {
120 // Check that points dimensions match values dimensions
121 if points.len() != values.ndim() {
122 return Err(InterpolateError::invalid_input(format!(
123 "Points dimensions ({}) do not match values dimensions ({})",
124 points.len(),
125 values.ndim()
126 )));
127 }
128
129 // Check that each dimension has at least 2 points
130 for (i, p) in points.iter().enumerate() {
131 if p.len() < 2 {
132 return Err(InterpolateError::invalid_input(format!(
133 "Dimension {} has less than 2 points",
134 i
135 )));
136 }
137
138 // Check that points are sorted
139 for j in 1..p.len() {
140 if p[j] <= p[j - 1] {
141 return Err(InterpolateError::invalid_input(format!(
142 "Points in dimension {} are not strictly increasing",
143 i
144 )));
145 }
146 }
147
148 // Check that values dimension matches points dimension
149 if p.len() != values.shape()[i] {
150 return Err(InterpolateError::invalid_input(format!(
151 "Values dimension {} size {} does not match points dimension size {}",
152 i,
153 values.shape()[i],
154 p.len()
155 )));
156 }
157 }
158
159 Ok(Self {
160 points,
161 values,
162 method,
163 extrapolate,
164 })
165 }
166
167 /// Interpolate at the given points
168 ///
169 /// # Arguments
170 ///
171 /// * `xi` - Array of points to interpolate at, shape (n_points, n_dims)
172 ///
173 /// # Returns
174 ///
175 /// Interpolated values at the given points, shape (n_points,)
176 ///
177 /// # Errors
178 ///
179 /// * If xi dimensions don't match grid dimensions
180 /// * If extrapolation is not allowed and points are outside the domain
181 ///
182 /// # Examples
183 ///
184 /// ```rust
185 /// use scirs2_core::ndarray::{Array, Array1, Array2, IxDyn};
186 /// use scirs2_interpolate::interpnd::{
187 /// RegularGridInterpolator, InterpolationMethod, ExtrapolateMode
188 /// };
189 ///
190 /// // Create a simple 2D grid
191 /// let x = Array1::from_vec(vec![0.0, 1.0, 2.0]);
192 /// let y = Array1::from_vec(vec![0.0, 1.0]);
193 /// let points = vec![x, y];
194 ///
195 /// let mut values = Array::zeros(IxDyn(&[3, 2]));
196 /// for i in 0..3 {
197 /// for j in 0..2 {
198 /// let idx = [i, j];
199 /// values[idx.as_slice()] = (i * i + j * j) as f64;
200 /// }
201 /// }
202 ///
203 /// let interpolator = RegularGridInterpolator::new(
204 /// points, values, InterpolationMethod::Linear, ExtrapolateMode::Extrapolate
205 /// ).expect("Operation failed");
206 ///
207 /// // Interpolate at multiple points
208 /// let xi = Array2::from_shape_vec((3, 2), vec![
209 /// 0.5, 0.5,
210 /// 1.0, 0.0,
211 /// 1.5, 0.5,
212 /// ]).expect("Operation failed");
213 ///
214 /// let results = interpolator.__call__(&xi.view()).expect("Operation failed");
215 /// assert_eq!(results.len(), 3);
216 /// ```
217 pub fn __call__(&self, xi: &ArrayView2<F>) -> InterpolateResult<Array1<F>> {
218 // Check that xi dimensions match grid dimensions
219 if xi.shape()[1] != self.points.len() {
220 return Err(InterpolateError::invalid_input(format!(
221 "Dimensions of interpolation points ({}) do not match grid dimensions ({})",
222 xi.shape()[1],
223 self.points.len()
224 )));
225 }
226
227 let n_points = xi.shape()[0];
228 let mut result = Array1::zeros(n_points);
229
230 for i in 0..n_points {
231 let point = xi.slice(scirs2_core::ndarray::s![i, ..]);
232 result[i] = self.interpolate_point(&point)?;
233 }
234
235 Ok(result)
236 }
237
238 /// Interpolate at a single point
239 ///
240 /// # Arguments
241 ///
242 /// * `point` - Coordinates of the point to interpolate at
243 ///
244 /// # Returns
245 ///
246 /// Interpolated value at the given point
247 fn interpolate_point(&self, point: &ArrayView1<F>) -> InterpolateResult<F> {
248 // Find the grid cells containing the point and calculate the normalized distances
249 let mut indices = Vec::with_capacity(self.points.len());
250 let mut weights = Vec::with_capacity(self.points.len());
251
252 for (dim, dim_points) in self.points.iter().enumerate() {
253 let mut x = point[dim];
254
255 // Check if point is outside the domain
256 if x < dim_points[0] || x > dim_points[dim_points.len() - 1] {
257 match self.extrapolate {
258 ExtrapolateMode::Error => {
259 return Err(InterpolateError::OutOfBounds(format!(
260 "Point outside domain in dimension {}: {} not in [{}, {}]",
261 dim,
262 x,
263 dim_points[0],
264 dim_points[dim_points.len() - 1]
265 )));
266 }
267 ExtrapolateMode::Nan => {
268 return Ok(F::nan());
269 }
270 ExtrapolateMode::Nearest => {
271 // Clamp to grid boundary
272 x = x.max(dim_points[0]).min(dim_points[dim_points.len() - 1]);
273 }
274 ExtrapolateMode::Extrapolate => {}
275 }
276 }
277
278 // Find index of cell containing x
279 let idx = match self.method {
280 InterpolationMethod::Nearest => {
281 // For nearest, just find the closest point
282 let mut closest_idx = 0;
283 let mut min_dist = (x - dim_points[0]).abs();
284
285 for (j, &p) in dim_points.iter().enumerate().skip(1) {
286 let dist = (x - p).abs();
287 if dist < min_dist {
288 min_dist = dist;
289 closest_idx = j;
290 }
291 }
292
293 // Return just the index of the nearest point
294 indices.push(closest_idx);
295 weights.push(F::from_f64(1.0).expect("Operation failed"));
296 continue;
297 }
298 _ => {
299 // For linear and spline, find the cell interval
300 let mut idx = dim_points.len() - 2;
301
302 // Find the cell that contains x (where x is between x[idx] and x[idx+1])
303 // Simply iterate through the points to find the right cell
304 let mut found = false;
305 for i in 0..dim_points.len() - 1 {
306 if x >= dim_points[i] && x <= dim_points[i + 1] {
307 idx = i;
308 found = true;
309 break;
310 }
311 }
312
313 // Handle extrapolation cases
314 if !found {
315 if x < dim_points[0] {
316 // Point is before the first grid point
317 if self.extrapolate == ExtrapolateMode::Extrapolate {
318 idx = 0;
319 } else if self.extrapolate == ExtrapolateMode::Error {
320 return Err(InterpolateError::out_of_domain(
321 x,
322 dim_points[0],
323 dim_points[dim_points.len() - 1],
324 "N-dimensional interpolation",
325 ));
326 } else {
327 // For Nan mode, clamp to boundary
328 idx = 0;
329 }
330 } else {
331 // Point is after the last grid point
332 idx = dim_points.len() - 2;
333 }
334 }
335
336 idx
337 }
338 };
339
340 // For linear interpolation, compute the weights
341 if self.method != InterpolationMethod::Nearest {
342 // Get the lower and upper bounds of the cell
343 let x0 = dim_points[idx];
344 let x1 = dim_points[idx + 1];
345
346 // Calculate the normalized distance for linear interpolation
347 // t is the fraction of the distance between x0 and x1
348 let t = if x1 == x0 {
349 F::from_f64(0.0).expect("Operation failed")
350 } else {
351 (x - x0) / (x1 - x0)
352 };
353
354 // Ensure t is between 0 and 1 (this handles any numerical precision issues)
355 let t = t
356 .max(F::from_f64(0.0).expect("Operation failed"))
357 .min(F::from_f64(1.0).expect("Operation failed"));
358
359 indices.push(idx);
360 weights.push(t);
361 }
362 }
363
364 // Perform the interpolation based on the method
365 match self.method {
366 InterpolationMethod::Nearest => {
367 // For nearest, we just return the value at the nearest grid point
368 let idx_array = indices.to_vec();
369 Ok(self.values[idx_array.as_slice()])
370 }
371 InterpolationMethod::Linear => {
372 // For linear, we need to compute a weighted average of the surrounding cell vertices
373 self.linear_interpolate(&indices, &weights)
374 }
375 InterpolationMethod::Spline => {
376 // For now, implement 2D spline interpolation only
377 if self.points.len() == 2 {
378 self.spline_interpolate_2d(point)
379 } else {
380 Err(InterpolateError::NotImplemented(format!(
381 "Spline interpolation only supports 2D grids, got {}D",
382 self.points.len()
383 )))
384 }
385 }
386 }
387 }
388
389 /// Perform linear interpolation
390 ///
391 /// # Arguments
392 ///
393 /// * `indices` - Indices of the cell containing the point
394 /// * `weights` - Normalized distances within the cell
395 ///
396 /// # Returns
397 ///
398 /// Interpolated value
399 fn linear_interpolate(&self, indices: &[usize], weights: &[F]) -> InterpolateResult<F> {
400 // For linear interpolation, we compute a weighted average of cell vertices
401 // Each vertex has a weight that is a product of 1D weights
402
403 // Handle the 2D case directly for better performance and correctness in test cases
404 if indices.len() == 2 {
405 // 2D case (rectangle)
406 let i0 = indices[0];
407 let i1 = indices[1];
408 let t0 = weights[0];
409 let t1 = weights[1];
410
411 // Get the values at the 4 corners
412 let idx00 = [i0, i1];
413 let idx01 = [i0, i1 + 1];
414 let idx10 = [i0 + 1, i1];
415 let idx11 = [i0 + 1, i1 + 1];
416
417 let v00 = self.values[idx00.as_slice()];
418 let v01 = self.values[idx01.as_slice()];
419 let v10 = self.values[idx10.as_slice()];
420 let v11 = self.values[idx11.as_slice()];
421
422 // Bilinear interpolation formula
423 // (1-t0)(1-t1)v00 + (1-t0)t1v01 + t0(1-t1)v10 + t0t1v11
424 let one = F::from_f64(1.0).expect("Operation failed");
425 let result = (one - t0) * (one - t1) * v00
426 + (one - t0) * t1 * v01
427 + t0 * (one - t1) * v10
428 + t0 * t1 * v11;
429
430 return Ok(result);
431 }
432
433 // General case for N dimensions
434 let n_dims = indices.len();
435 let mut result = F::from_f64(0.0).expect("Operation failed");
436
437 // We need to iterate through all 2^n_dims vertices of the hypercube
438 // Each vertex is identified by a binary pattern of lower/upper indices
439 let n_vertices = 1 << n_dims;
440
441 for vertex in 0..n_vertices {
442 // Build the index for this vertex and calculate its weight
443 let mut vertex_index = Vec::with_capacity(n_dims);
444 let mut vertex_weight = F::from_f64(1.0).expect("Operation failed");
445
446 for dim in 0..n_dims {
447 let use_upper = (vertex >> dim) & 1 == 1;
448 let idx = indices[dim] + if use_upper { 1 } else { 0 };
449 vertex_index.push(idx);
450
451 // Weight is either weight (for upper) or (1-weight) for lower
452 // For linear interpolation, weights represent normalized positions
453 // e.g., weight 0.7 means 70% toward upper point, 30% toward lower point
454 let dim_weight = if use_upper {
455 weights[dim]
456 } else {
457 F::from_f64(1.0).expect("Operation failed") - weights[dim]
458 };
459
460 vertex_weight *= dim_weight;
461 }
462
463 // Add the weighted value to the result
464 let vertex_value = self.values[vertex_index.as_slice()];
465 result += vertex_weight * vertex_value;
466 }
467
468 Ok(result)
469 }
470
471 /// Perform 2D spline interpolation
472 ///
473 /// # Arguments
474 ///
475 /// * `point` - Coordinates of the point to interpolate at
476 ///
477 /// # Returns
478 ///
479 /// Interpolated value at the point
480 fn spline_interpolate_2d(&self, point: &ArrayView1<F>) -> InterpolateResult<F> {
481 use crate::interp2d::{Interp2d, Interp2dKind};
482
483 if self.points.len() != 2 {
484 return Err(InterpolateError::invalid_input(
485 "spline_interpolate_2d requires exactly 2 dimensions",
486 ));
487 }
488
489 // Convert the N-D grid to 2D format
490 let x = &self.points[0];
491 let y = &self.points[1];
492
493 // The values should be in a 2D array format
494 let shape = self.values.shape();
495 if shape.len() != 2 {
496 return Err(InterpolateError::invalid_input(
497 "spline_interpolate_2d requires 2D value array",
498 ));
499 }
500
501 // Create 2D array from N-D array
502 let z = self
503 .values
504 .clone()
505 .into_dimensionality::<scirs2_core::ndarray::Ix2>()
506 .map_err(|_| InterpolateError::invalid_input("Failed to convert to 2D array"))?;
507
508 // Create 2D interpolator
509 let interp = Interp2d::new(&x.view(), &y.view(), &z.view(), Interp2dKind::Cubic)?;
510
511 // Evaluate at the point
512 if point.len() != 2 {
513 return Err(InterpolateError::invalid_input(
514 "Point must have 2 coordinates for 2D spline interpolation",
515 ));
516 }
517
518 interp.evaluate(point[0], point[1])
519 }
520}
521
522/// N-dimensional interpolation on unstructured data (scattered points)
523///
524/// This interpolator works with data defined on scattered points without
525/// a regular grid structure, using various methods.
526#[derive(Debug, Clone)]
527#[allow(dead_code)]
528pub struct ScatteredInterpolator<F: Float + FromPrimitive + Debug + Display> {
529 /// Points coordinates, shape (n_points, n_dims)
530 points: Array2<F>,
531 /// Values at points, shape (n_points,)
532 values: Array1<F>,
533 /// Method to use for interpolation
534 method: ScatteredInterpolationMethod,
535 /// How to handle points outside the domain
536 extrapolate: ExtrapolateMode,
537 /// Additional parameters for specific methods
538 params: ScatteredInterpolatorParams<F>,
539}
540
541/// Parameters for scattered interpolation methods
542#[derive(Debug, Clone)]
543pub enum ScatteredInterpolatorParams<F: Float + FromPrimitive + Debug + Display> {
544 /// No additional parameters
545 None,
546 /// Parameters for IDW (Inverse Distance Weighting)
547 IDW {
548 /// Power parameter for IDW (default: 2.0)
549 power: F,
550 },
551 /// Parameters for RBF (Radial Basis Function)
552 RBF {
553 /// Epsilon parameter for RBF (default: 1.0)
554 epsilon: F,
555 /// Type of radial basis function
556 rbf_type: RBFType,
557 },
558}
559
560/// Types of radial basis functions
561#[derive(Debug, Clone, Copy, PartialEq)]
562pub enum RBFType {
563 /// Gaussian: exp(-(εr)²)
564 Gaussian,
565 /// Multiquadric: sqrt(1 + (εr)²)
566 Multiquadric,
567 /// Inverse multiquadric: 1/sqrt(1 + (εr)²)
568 InverseMultiquadric,
569 /// Thin plate spline: (εr)² log(εr)
570 ThinPlateSpline,
571}
572
573/// Available interpolation methods for scattered data
574#[derive(Debug, Clone, Copy, PartialEq)]
575pub enum ScatteredInterpolationMethod {
576 /// Nearest neighbor interpolation
577 Nearest,
578 /// Inverse Distance Weighting
579 IDW,
580 /// Radial Basis Function interpolation
581 RBF,
582}
583
584impl<
585 F: Float
586 + FromPrimitive
587 + Debug
588 + Display
589 + AddAssign
590 + SubAssign
591 + std::fmt::LowerExp
592 + std::ops::MulAssign
593 + std::ops::DivAssign
594 + Send
595 + Sync
596 + 'static,
597 > ScatteredInterpolator<F>
598{
599 /// Create a new ScatteredInterpolator
600 ///
601 /// # Arguments
602 ///
603 /// * `points` - Coordinates of sample points, shape (n_points, n_dims)
604 /// * `values` - Values at sample points, shape (n_points,)
605 /// * `method` - Interpolation method to use
606 /// * `extrapolate` - How to handle points outside the domain
607 /// * `params` - Additional parameters for specific methods
608 ///
609 /// # Returns
610 ///
611 /// A new ScatteredInterpolator object
612 ///
613 /// # Errors
614 ///
615 /// * If points and values dimensions don't match
616 ///
617 /// # Examples
618 ///
619 /// ```rust
620 /// use scirs2_core::ndarray::{Array1, Array2};
621 /// use scirs2_interpolate::interpnd::{
622 /// ScatteredInterpolator, ScatteredInterpolationMethod,
623 /// ExtrapolateMode, ScatteredInterpolatorParams
624 /// };
625 ///
626 /// // Create scattered 3D data
627 /// let points = Array2::from_shape_vec((5, 3), vec![
628 /// 0.0, 0.0, 0.0,
629 /// 1.0, 0.0, 0.0,
630 /// 0.0, 1.0, 0.0,
631 /// 0.0, 0.0, 1.0,
632 /// 0.5, 0.5, 0.5,
633 /// ]).expect("Operation failed");
634 /// let values = Array1::from_vec(vec![0.0, 1.0, 2.0, 3.0, 1.5]);
635 ///
636 /// // Create IDW interpolator with custom power
637 /// let interpolator = ScatteredInterpolator::new(
638 /// points,
639 /// values,
640 /// ScatteredInterpolationMethod::IDW,
641 /// ExtrapolateMode::Extrapolate,
642 /// Some(ScatteredInterpolatorParams::IDW { power: 3.0 }),
643 /// ).expect("Operation failed");
644 /// ```
645 pub fn new(
646 points: Array2<F>,
647 values: Array1<F>,
648 method: ScatteredInterpolationMethod,
649 extrapolate: ExtrapolateMode,
650 params: Option<ScatteredInterpolatorParams<F>>,
651 ) -> InterpolateResult<Self> {
652 // Check that points and values have compatible dimensions
653 if points.shape()[0] != values.len() {
654 return Err(InterpolateError::invalid_input(format!(
655 "Number of points ({}) does not match number of values ({})",
656 points.shape()[0],
657 values.len()
658 )));
659 }
660
661 // Set default parameters based on method if not provided
662 let params = match params {
663 Some(p) => p,
664 None => match method {
665 ScatteredInterpolationMethod::Nearest => ScatteredInterpolatorParams::None,
666 ScatteredInterpolationMethod::IDW => ScatteredInterpolatorParams::IDW {
667 power: F::from_f64(2.0).expect("Operation failed"),
668 },
669 ScatteredInterpolationMethod::RBF => ScatteredInterpolatorParams::RBF {
670 epsilon: F::from_f64(1.0).expect("Operation failed"),
671 rbf_type: RBFType::Multiquadric,
672 },
673 },
674 };
675
676 Ok(Self {
677 points,
678 values,
679 method,
680 extrapolate,
681 params,
682 })
683 }
684
685 /// Interpolate at the given points
686 ///
687 /// # Arguments
688 ///
689 /// * `xi` - Array of points to interpolate at, shape (n_points, n_dims)
690 ///
691 /// # Returns
692 ///
693 /// Interpolated values at the given points, shape (n_points,)
694 ///
695 /// # Errors
696 ///
697 /// * If xi dimensions don't match input dimensions
698 pub fn __call__(&self, xi: &ArrayView2<F>) -> InterpolateResult<Array1<F>> {
699 // Check that xi dimensions match input dimensions
700 if xi.shape()[1] != self.points.shape()[1] {
701 return Err(InterpolateError::invalid_input(format!(
702 "Dimensions of interpolation points ({}) do not match input dimensions ({})",
703 xi.shape()[1],
704 self.points.shape()[1]
705 )));
706 }
707
708 let n_points = xi.shape()[0];
709 let mut result = Array1::zeros(n_points);
710
711 for i in 0..n_points {
712 let point = xi.slice(scirs2_core::ndarray::s![i, ..]);
713 result[i] = self.interpolate_point(&point)?;
714 }
715
716 Ok(result)
717 }
718
719 /// Interpolate at a single point
720 ///
721 /// # Arguments
722 ///
723 /// * `point` - Coordinates of the point to interpolate at
724 ///
725 /// # Returns
726 ///
727 /// Interpolated value at the given point
728 fn interpolate_point(&self, point: &ArrayView1<F>) -> InterpolateResult<F> {
729 match self.method {
730 ScatteredInterpolationMethod::Nearest => self.nearest_interpolate(point),
731 ScatteredInterpolationMethod::IDW => self.idw_interpolate(point),
732 ScatteredInterpolationMethod::RBF => self.rbf_interpolate(point),
733 }
734 }
735
736 /// Perform nearest neighbor interpolation
737 ///
738 /// # Arguments
739 ///
740 /// * `point` - Coordinates of the point to interpolate at
741 ///
742 /// # Returns
743 ///
744 /// Interpolated value at the given point
745 fn nearest_interpolate(&self, point: &ArrayView1<F>) -> InterpolateResult<F> {
746 let mut min_dist = F::infinity();
747 let mut nearest_idx = 0;
748
749 // Find the nearest point
750 for i in 0..self.points.shape()[0] {
751 let p = self.points.slice(scirs2_core::ndarray::s![i, ..]);
752 let dist = self.compute_distance(&p, point);
753
754 if dist < min_dist {
755 min_dist = dist;
756 nearest_idx = i;
757 }
758 }
759
760 Ok(self.values[nearest_idx])
761 }
762
763 /// Perform Inverse Distance Weighting interpolation
764 ///
765 /// # Arguments
766 ///
767 /// * `point` - Coordinates of the point to interpolate at
768 ///
769 /// # Returns
770 ///
771 /// Interpolated value at the given point
772 fn idw_interpolate(&self, point: &ArrayView1<F>) -> InterpolateResult<F> {
773 // Get the power parameter
774 let power = match self.params {
775 ScatteredInterpolatorParams::IDW { power } => power,
776 _ => F::from_f64(2.0).expect("Operation failed"), // Default to 2.0 if wrong params
777 };
778
779 let mut sum_weights = F::from_f64(0.0).expect("Operation failed");
780 let mut sum_weighted_values = F::from_f64(0.0).expect("Operation failed");
781
782 // Check for exact match with any input point
783 for i in 0..self.points.shape()[0] {
784 let p = self.points.slice(scirs2_core::ndarray::s![i, ..]);
785 let dist = self.compute_distance(&p, point);
786
787 if dist.is_zero() {
788 // Exact match found
789 return Ok(self.values[i]);
790 }
791
792 // Calculate weight as 1/distance^power
793 let weight = F::from_f64(1.0).expect("Operation failed") / dist.powf(power);
794 sum_weights += weight;
795 sum_weighted_values += weight * self.values[i];
796 }
797
798 // Calculate weighted average
799 if sum_weights.is_zero() {
800 // This should not happen with non-zero distances
801 return Err(InterpolateError::ComputationError(
802 "Sum of weights is zero in IDW interpolation".to_string(),
803 ));
804 }
805
806 Ok(sum_weighted_values / sum_weights)
807 }
808
809 /// Compute Euclidean distance between two points
810 ///
811 /// # Arguments
812 ///
813 /// * `p1` - First point
814 /// * `p2` - Second point
815 ///
816 /// # Returns
817 ///
818 /// Euclidean distance between the points
819 fn compute_distance(&self, p1: &ArrayView1<F>, p2: &ArrayView1<F>) -> F {
820 let mut sum_sq = F::from_f64(0.0).expect("Operation failed");
821 for i in 0..p1.len() {
822 let diff = p1[i] - p2[i];
823 sum_sq += diff * diff;
824 }
825 sum_sq.sqrt()
826 }
827
828 /// Perform RBF interpolation at a point
829 ///
830 /// # Arguments
831 ///
832 /// * `point` - Coordinates of the point to interpolate at
833 ///
834 /// # Returns
835 ///
836 /// Interpolated value at the point
837 fn rbf_interpolate(&self, point: &ArrayView1<F>) -> InterpolateResult<F>
838 where
839 F: Float
840 + FromPrimitive
841 + Debug
842 + Display
843 + AddAssign
844 + std::ops::SubAssign
845 + std::fmt::LowerExp
846 + std::ops::MulAssign
847 + std::ops::DivAssign
848 + Send
849 + Sync
850 + 'static,
851 {
852 // Create RBF interpolator
853 let epsilon = F::from_f64(1.0).expect("Operation failed"); // Default shape parameter
854 let rbf = RBFInterpolator::new(
855 &self.points.view(),
856 &self.values.view(),
857 RBFKernel::Gaussian,
858 epsilon,
859 )?;
860
861 // Evaluate at the query point (reshape 1D point to 2D for RBF interface)
862 let binding = point.to_owned();
863 let point_2d = binding
864 .to_shape((1, point.len()))
865 .expect("Operation failed");
866 let result = rbf.evaluate(&point_2d.view())?;
867 Ok(result[0])
868 }
869}
870
871/// Create an N-dimensional interpolator on a regular grid
872///
873/// # Arguments
874///
875/// * `points` - A vector of arrays, where each array contains the points in one dimension
876/// * `values` - An N-dimensional array of values at the grid points
877/// * `method` - Interpolation method to use
878/// * `extrapolate` - How to handle points outside the domain
879///
880/// # Returns
881///
882/// A new RegularGridInterpolator object
883///
884/// # Errors
885///
886/// * If points dimensions don't match values dimensions
887/// * If any dimension has less than 2 points
888///
889/// # Examples
890///
891/// ```
892/// use scirs2_core::ndarray::{Array, Array1, Dim, IxDyn};
893/// use scirs2_core::numeric::Float;
894/// use scirs2_interpolate::interpnd::{
895/// make_interp_nd, InterpolationMethod, ExtrapolateMode
896/// };
897///
898/// // Create a 2D grid
899/// let x = Array1::from_vec(vec![0.0, 1.0, 2.0]);
900/// let y = Array1::from_vec(vec![0.0, 1.0, 2.0, 3.0]);
901/// let points = vec![x, y];
902///
903/// // Create values on the grid (z = x^2 + y^2)
904/// let mut values = Array::zeros(IxDyn(&[3, 4]));
905/// for i in 0..3 {
906/// for j in 0..4 {
907/// let idx = [i, j];
908/// values[idx.as_slice()] = (i * i + j * j) as f64;
909/// }
910/// }
911///
912/// // Create the interpolator
913/// let interp = make_interp_nd(
914/// points,
915/// values,
916/// InterpolationMethod::Linear,
917/// ExtrapolateMode::Extrapolate,
918/// ).expect("Operation failed");
919///
920/// // Interpolate at a point
921/// use scirs2_core::ndarray::Array2;
922/// let points_to_interp = Array2::from_shape_vec((1, 2), vec![1.5, 2.5]).expect("Operation failed");
923/// let result = interp.__call__(&points_to_interp.view()).expect("Operation failed");
924/// assert!((result[0] - 9.0).abs() < 1e-10);
925/// ```
926#[allow(dead_code)]
927pub fn make_interp_nd<F: crate::traits::InterpolationFloat>(
928 points: Vec<Array1<F>>,
929 values: Array<F, IxDyn>,
930 method: InterpolationMethod,
931 extrapolate: ExtrapolateMode,
932) -> InterpolateResult<RegularGridInterpolator<F>> {
933 RegularGridInterpolator::new(points, values, method, extrapolate)
934}
935
936/// Create an N-dimensional interpolator for scattered data
937///
938/// # Arguments
939///
940/// * `points` - Coordinates of sample points, shape (n_points, n_dims)
941/// * `values` - Values at sample points, shape (n_points,)
942/// * `method` - Interpolation method to use
943/// * `extrapolate` - How to handle points outside the domain
944/// * `params` - Additional parameters for specific methods
945///
946/// # Returns
947///
948/// A new ScatteredInterpolator object
949///
950/// # Errors
951///
952/// * If points and values dimensions don't match
953#[allow(dead_code)]
954pub fn make_interp_scattered<F: crate::traits::InterpolationFloat>(
955 points: Array2<F>,
956 values: Array1<F>,
957 method: ScatteredInterpolationMethod,
958 extrapolate: ExtrapolateMode,
959 params: Option<ScatteredInterpolatorParams<F>>,
960) -> InterpolateResult<ScatteredInterpolator<F>> {
961 ScatteredInterpolator::new(points, values, method, extrapolate, params)
962}
963
964/// Map values on a rectilinear grid to a new grid
965///
966/// # Arguments
967///
968/// * `old_grid` - Vec of Arrays representing the old grid points in each dimension
969/// * `old_values` - Values at old grid points
970/// * `new_grid` - Vec of Arrays representing the new grid points in each dimension
971/// * `method` - Interpolation method to use
972///
973/// # Returns
974///
975/// Values at new grid points
976///
977/// # Errors
978///
979/// * If dimensions don't match
980/// * If any dimension has less than 2 points
981#[allow(dead_code)]
982pub fn map_coordinates<F: crate::traits::InterpolationFloat>(
983 old_grid: Vec<Array1<F>>,
984 old_values: Array<F, IxDyn>,
985 new_grid: Vec<Array1<F>>,
986 method: InterpolationMethod,
987) -> InterpolateResult<Array<F, IxDyn>> {
988 // Create the interpolator
989 let interp =
990 RegularGridInterpolator::new(old_grid, old_values, method, ExtrapolateMode::Error)?;
991
992 // Determine the shape of the output array
993 let outshape: Vec<usize> = new_grid.iter().map(|x| x.len()).collect();
994 let n_dims = outshape.len();
995
996 // Create meshgrid of coordinates
997 let mut indices = vec![Vec::<F>::new(); n_dims];
998 let mut shape = vec![1; n_dims];
999
1000 for (i, grid) in new_grid.iter().enumerate() {
1001 let mut idx = vec![F::from_f64(0.0).expect("Operation failed"); grid.len()];
1002 for (j, val) in grid.iter().enumerate() {
1003 idx[j] = *val;
1004 }
1005 indices[i] = idx;
1006 shape[i] = grid.len();
1007 }
1008
1009 // Calculate total number of points
1010 let total_points: usize = shape.iter().product();
1011
1012 // Create the output array
1013 let mut out_values = Array::zeros(IxDyn(&outshape));
1014
1015 // Create a 2D array of all points to interpolate
1016 let mut points = Array2::zeros((total_points, n_dims));
1017
1018 // Create a multi-index for traversing the _grid
1019 let mut multi_index = vec![0; n_dims];
1020
1021 for flat_idx in 0..total_points {
1022 // Convert flat index to multi-index
1023 let mut temp = flat_idx;
1024 for i in (0..n_dims).rev() {
1025 multi_index[i] = temp % shape[i];
1026 temp /= shape[i];
1027 }
1028
1029 // Set point coordinates
1030 for i in 0..n_dims {
1031 points[[flat_idx, i]] = indices[i][multi_index[i]];
1032 }
1033 }
1034
1035 // Perform interpolation for all points
1036 let values = interp.__call__(&points.view())?;
1037
1038 // Reshape the result to match the output _grid
1039 let mut out_idx_vec = Vec::with_capacity(n_dims);
1040 for flat_idx in 0..total_points {
1041 // Convert flat index to multi-index
1042 let mut temp = flat_idx;
1043 for i in (0..n_dims).rev() {
1044 multi_index[i] = temp % shape[i];
1045 temp /= shape[i];
1046 }
1047
1048 // Convert multi-index to output index vector
1049 out_idx_vec.clear();
1050 out_idx_vec.extend_from_slice(&multi_index[..n_dims]);
1051
1052 // Set the value in the output array
1053 *out_values
1054 .get_mut(out_idx_vec.as_slice())
1055 .expect("Operation failed") = values[flat_idx];
1056 }
1057
1058 Ok(out_values)
1059}
1060
1061#[cfg(test)]
1062mod tests {
1063 use super::*;
1064 use approx::assert_abs_diff_eq;
1065 use scirs2_core::ndarray::{Array2, IxDyn}; // 配列操作用
1066
1067 #[test]
1068 fn test_regular_grid_interpolator_2d() {
1069 // Create a 2D grid
1070 let x = Array1::from_vec(vec![0.0, 1.0, 2.0]);
1071 let y = Array1::from_vec(vec![0.0, 1.0, 2.0, 3.0]);
1072 let points = vec![x, y];
1073
1074 // Create values on the grid (z = x^2 + y^2)
1075 let mut values = Array::zeros(IxDyn(&[3, 4]));
1076 for i in 0..3 {
1077 for j in 0..4 {
1078 let idx = [i, j];
1079 values[idx.as_slice()] = (i * i + j * j) as f64;
1080 }
1081 }
1082
1083 // Create the interpolator
1084 let interp = RegularGridInterpolator::new(
1085 points.clone(),
1086 values.clone(),
1087 InterpolationMethod::Linear,
1088 ExtrapolateMode::Extrapolate,
1089 )
1090 .expect("Operation failed");
1091
1092 // Test interpolation at grid points
1093 let grid_point = Array2::from_shape_vec((1, 2), vec![1.0, 2.0]).expect("Operation failed");
1094 let result = interp
1095 .__call__(&grid_point.view())
1096 .expect("Operation failed");
1097 assert_abs_diff_eq!(result[0], 5.0, epsilon = 1e-10);
1098
1099 // Test interpolation at non-grid points
1100 let non_grid_point =
1101 Array2::from_shape_vec((1, 2), vec![1.5, 2.5]).expect("Operation failed");
1102 let result = interp
1103 .__call__(&non_grid_point.view())
1104 .expect("Operation failed");
1105
1106 // For point (1.5, 2.5):
1107 // We're interpolating between grid points:
1108 // (1,2) -> value = 5.0
1109 // (1,3) -> value = 10.0
1110 // (2,2) -> value = 8.0
1111 // (2,3) -> value = 13.0
1112 // With weights: x=0.5, y=0.5
1113 // Expected = (1-0.5)(1-0.5)*5.0 + (1-0.5)(0.5)*10.0 + (0.5)(1-0.5)*8.0 + (0.5)(0.5)*13.0
1114 // = 0.25*5.0 + 0.25*10.0 + 0.25*8.0 + 0.25*13.0
1115 // = 1.25 + 2.5 + 2.0 + 3.25 = 9.0
1116 assert_abs_diff_eq!(result[0], 9.0, epsilon = 1e-10);
1117
1118 // Test multiple points at once
1119 let multiple_points =
1120 Array2::from_shape_vec((2, 2), vec![1.0, 1.0, 2.0, 2.0]).expect("Operation failed");
1121 let result = interp
1122 .__call__(&multiple_points.view())
1123 .expect("Operation failed");
1124 assert_abs_diff_eq!(result[0], 2.0, epsilon = 1e-10);
1125 assert_abs_diff_eq!(result[1], 8.0, epsilon = 1e-10);
1126
1127 // Test nearest neighbor interpolation
1128 let interp_nearest = RegularGridInterpolator::new(
1129 points.clone(),
1130 values.clone(),
1131 InterpolationMethod::Nearest,
1132 ExtrapolateMode::Extrapolate,
1133 )
1134 .expect("Operation failed");
1135
1136 let point = Array2::from_shape_vec((1, 2), vec![1.6, 1.7]).expect("Operation failed");
1137 let result = interp_nearest
1138 .__call__(&point.view())
1139 .expect("Operation failed");
1140 // Point (1.6, 1.7) is closest to grid point (2,2) which has value 8.0
1141 assert_abs_diff_eq!(result[0], 8.0, epsilon = 1e-10);
1142 }
1143
1144 #[test]
1145 fn test_scattered_interpolator() {
1146 // Create scattered points in 2D
1147 let points = Array2::from_shape_vec(
1148 (5, 2),
1149 vec![0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, 1.0, 0.5, 0.5],
1150 )
1151 .expect("Operation failed");
1152
1153 // Create values at those points (z = x^2 + y^2)
1154 let values = Array1::from_vec(vec![0.0, 1.0, 1.0, 2.0, 0.5]);
1155
1156 // Create the interpolator with IDW
1157 let interp = ScatteredInterpolator::new(
1158 points.clone(),
1159 values.clone(),
1160 ScatteredInterpolationMethod::IDW,
1161 ExtrapolateMode::Extrapolate,
1162 Some(ScatteredInterpolatorParams::IDW { power: 2.0 }),
1163 )
1164 .expect("Operation failed");
1165
1166 // Test interpolation at a point
1167 let test_point = Array2::from_shape_vec((1, 2), vec![0.5, 0.0]).expect("Operation failed");
1168 let result = interp
1169 .__call__(&test_point.view())
1170 .expect("Operation failed");
1171 // Value should be between 0.0 and 1.0, closer to 0.5
1172 assert!(result[0] > 0.0 && result[0] < 1.0);
1173
1174 // Test nearest neighbor interpolator
1175 let interp_nearest = ScatteredInterpolator::new(
1176 points,
1177 values,
1178 ScatteredInterpolationMethod::Nearest,
1179 ExtrapolateMode::Extrapolate,
1180 None,
1181 )
1182 .expect("Operation failed");
1183
1184 let test_point = Array2::from_shape_vec((1, 2), vec![0.6, 0.6]).expect("Operation failed");
1185 let result = interp_nearest
1186 .__call__(&test_point.view())
1187 .expect("Operation failed");
1188 assert_abs_diff_eq!(result[0], 0.5, epsilon = 1e-10); // Should pick the center point
1189 }
1190}