Skip to main content

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}