Skip to main content

scirs2_interpolate/
high_dimensional.rs

1//! High-dimensional interpolation methods
2//!
3//! This module provides specialized interpolation methods designed to work efficiently
4//! in high-dimensional spaces where traditional methods suffer from the curse of
5//! dimensionality. The methods implemented here include:
6//!
7//! - **Sparse grid interpolation**: Efficient interpolation on sparse grids
8//! - **Dimension reduction**: PCA and manifold-based interpolation
9//! - **Local methods**: k-nearest neighbor and locally weighted interpolation
10//! - **Tensor decomposition**: Tucker and CP decomposition for structured data
11//! - **Adaptive basis functions**: RBF with adaptive kernel selection
12//! - **Hierarchical methods**: Multi-resolution interpolation
13//!
14//! These methods are specifically designed to:
15//! 1. Scale better than O(2^d) with dimension d
16//! 2. Work with sparse data in high dimensions
17//! 3. Automatically adapt to the intrinsic dimensionality of data
18//! 4. Provide uncertainty estimates for predictions
19//!
20//! # Examples
21//!
22//! ```rust
23//! use scirs2_core::ndarray::{Array1, Array2};
24//! use scirs2_interpolate::high_dimensional::{
25//!     HighDimensionalInterpolator, DimensionReductionMethod
26//! };
27//!
28//! // Create high-dimensional data (100 dimensions)
29//! let n_points = 1000;
30//! let n_dims = 100;
31//! let points = Array2::<f64>::zeros((n_points, n_dims));
32//! let values = Array1::<f64>::zeros(n_points);
33//!
34//! // Create interpolator with dimension reduction
35//! let interpolator = HighDimensionalInterpolator::builder()
36//!     .with_dimension_reduction(DimensionReductionMethod::PCA { target_dims: 10 })
37//!     .build(&points.view(), &values.view())
38//!     .expect("Operation failed");
39//!
40//! // Query at new high-dimensional point
41//! let query = Array1::zeros(n_dims);
42//! let result = interpolator.interpolate(&query.view()).expect("Operation failed");
43//! ```
44
45use crate::error::{InterpolateError, InterpolateResult};
46use crate::spatial::{BallTree, KdTree};
47use scirs2_core::ndarray::{Array1, Array2, ArrayView1, ArrayView2, Axis, ScalarOperand};
48use scirs2_core::numeric::{Float, FromPrimitive, Zero};
49use std::fmt::{Debug, Display};
50use std::marker::PhantomData;
51use std::ops::AddAssign;
52
53/// Methods for dimension reduction in high-dimensional interpolation
54#[derive(Debug, Clone)]
55pub enum DimensionReductionMethod {
56    /// Principal Component Analysis with target dimensionality
57    PCA { target_dims: usize },
58    /// Random projection to lower dimensions
59    RandomProjection { target_dims: usize },
60    /// Local linear embedding
61    LocalLinearEmbedding {
62        target_dims: usize,
63        n_neighbors: usize,
64    },
65    /// No dimension reduction
66    None,
67}
68
69/// Local interpolation methods for high-dimensional data
70#[derive(Debug, Clone)]
71pub enum LocalMethod {
72    /// k-nearest neighbors with weights
73    KNearestNeighbors { k: usize, weight_power: f64 },
74    /// Locally weighted regression
75    LocallyWeighted { bandwidth: f64, degree: usize },
76    /// Radial basis functions with local support
77    LocalRBF { radius: f64, rbf_type: LocalRBFType },
78}
79
80/// Types of locally supported RBF kernels
81#[derive(Debug, Clone)]
82pub enum LocalRBFType {
83    /// Gaussian with compact support
84    CompactGaussian,
85    /// Wendland functions
86    Wendland { smoothness: usize },
87    /// Multiquadric with compact support
88    CompactMultiquadric,
89}
90
91/// Sparse interpolation strategies for high-dimensional data
92#[derive(Debug, Clone)]
93pub enum SparseStrategy {
94    /// Use only data points within a certain distance
95    RadialBasis { radius: f64 },
96    /// Adaptive sparse grid construction
97    AdaptiveSparse { max_level: usize, tolerance: f64 },
98    /// Tensor decomposition for structured sparse data
99    TensorDecomposition { rank: usize },
100}
101
102/// High-dimensional interpolator with adaptive strategies
103#[derive(Debug)]
104pub struct HighDimensionalInterpolator<F>
105where
106    F: Float
107        + FromPrimitive
108        + Debug
109        + Display
110        + Zero
111        + Copy
112        + AddAssign
113        + ScalarOperand
114        + 'static
115        + Send
116        + Sync
117        + ordered_float::FloatCore,
118{
119    /// Training data points
120    #[allow(dead_code)]
121    points: Array2<F>,
122    /// Training data values
123    values: Array1<F>,
124    /// Dimension reduction transformation
125    dimension_reduction: Option<DimensionReduction<F>>,
126    /// Local interpolation method
127    local_method: LocalMethod,
128    /// Sparse interpolation strategy
129    #[allow(dead_code)]
130    sparse_strategy: Option<SparseStrategy>,
131    /// Spatial data structure for fast neighbor search
132    spatial_index: SpatialIndex<F>,
133    /// Statistics about the interpolator
134    stats: InterpolatorStats,
135}
136
137/// Dimension reduction transformation
138#[derive(Debug)]
139struct DimensionReduction<F: Float> {
140    #[allow(dead_code)]
141    method: DimensionReductionMethod,
142    transformation_matrix: Array2<F>,
143    mean: Array1<F>,
144    #[allow(dead_code)]
145    explained_variance_ratio: Option<Array1<F>>,
146}
147
148/// Spatial indexing for fast neighbor queries
149#[derive(Debug)]
150enum SpatialIndex<F>
151where
152    F: Float + FromPrimitive + Debug + std::cmp::PartialOrd + Copy + ordered_float::FloatCore,
153{
154    KdTree(KdTree<F>),
155    BallTree(BallTree<F>),
156    BruteForce(Array2<F>),
157}
158
159/// Statistics about the interpolator performance
160#[derive(Debug, Default)]
161pub struct InterpolatorStats {
162    n_training_points: usize,
163    original_dimensions: usize,
164    reduced_dimensions: Option<usize>,
165    #[allow(dead_code)]
166    average_neighbors_used: f64,
167    #[allow(dead_code)]
168    cache_hit_rate: f64,
169}
170
171/// Builder for high-dimensional interpolators
172#[derive(Debug)]
173pub struct HighDimensionalInterpolatorBuilder<F>
174where
175    F: Float
176        + FromPrimitive
177        + Debug
178        + Display
179        + Zero
180        + Copy
181        + AddAssign
182        + ScalarOperand
183        + 'static
184        + Send
185        + Sync
186        + ordered_float::FloatCore,
187{
188    dimension_reduction: DimensionReductionMethod,
189    local_method: LocalMethod,
190    sparse_strategy: Option<SparseStrategy>,
191    spatial_index_type: SpatialIndexType,
192    _phantom: PhantomData<F>,
193}
194
195/// Types of spatial indices available
196#[derive(Debug, Clone)]
197pub enum SpatialIndexType {
198    /// Use KD-tree (good for low-medium dimensions)
199    KdTree,
200    /// Use Ball tree (good for high dimensions)
201    BallTree,
202    /// Use brute force search (for very small datasets)
203    BruteForce,
204    /// Automatically choose based on data characteristics
205    Auto,
206}
207
208impl<F> Default for HighDimensionalInterpolatorBuilder<F>
209where
210    F: Float
211        + FromPrimitive
212        + Debug
213        + Display
214        + Zero
215        + Copy
216        + AddAssign
217        + ScalarOperand
218        + 'static
219        + Send
220        + Sync
221        + ordered_float::FloatCore,
222{
223    fn default() -> Self {
224        Self {
225            dimension_reduction: DimensionReductionMethod::None,
226            local_method: LocalMethod::KNearestNeighbors {
227                k: 10,
228                weight_power: 2.0,
229            },
230            sparse_strategy: None,
231            spatial_index_type: SpatialIndexType::Auto,
232            _phantom: PhantomData,
233        }
234    }
235}
236
237impl<F> HighDimensionalInterpolatorBuilder<F>
238where
239    F: Float
240        + FromPrimitive
241        + Debug
242        + Display
243        + Zero
244        + Copy
245        + AddAssign
246        + ScalarOperand
247        + 'static
248        + Send
249        + Sync
250        + ordered_float::FloatCore,
251{
252    /// Create a new builder with default settings
253    ///
254    /// # Examples
255    ///
256    /// ```rust
257    /// use scirs2_interpolate::high_dimensional::HighDimensionalInterpolatorBuilder;
258    /// use scirs2_core::ndarray::Array2;
259    ///
260    /// let builder = HighDimensionalInterpolatorBuilder::<f64>::new();
261    /// ```
262    pub fn new() -> Self {
263        Self::default()
264    }
265
266    /// Set the dimension reduction method
267    ///
268    /// # Examples
269    ///
270    /// ```rust
271    /// use scirs2_interpolate::high_dimensional::{HighDimensionalInterpolatorBuilder, DimensionReductionMethod};
272    ///
273    /// let builder = HighDimensionalInterpolatorBuilder::<f64>::new()
274    ///     .with_dimension_reduction(DimensionReductionMethod::PCA { target_dims: 5 });
275    /// ```
276    pub fn with_dimension_reduction(mut self, method: DimensionReductionMethod) -> Self {
277        self.dimension_reduction = method;
278        self
279    }
280
281    /// Set the local interpolation method
282    ///
283    /// # Examples
284    ///
285    /// ```rust
286    /// use scirs2_interpolate::high_dimensional::{HighDimensionalInterpolatorBuilder, LocalMethod};
287    ///
288    /// let builder = HighDimensionalInterpolatorBuilder::<f64>::new()
289    ///     .with_local_method(LocalMethod::KNearestNeighbors { k: 8, weight_power: 1.5 });
290    /// ```
291    pub fn with_local_method(mut self, method: LocalMethod) -> Self {
292        self.local_method = method;
293        self
294    }
295
296    /// Set the sparse interpolation strategy
297    pub fn with_sparse_strategy(mut self, strategy: SparseStrategy) -> Self {
298        self.sparse_strategy = Some(strategy);
299        self
300    }
301
302    /// Set the spatial index type
303    pub fn with_spatial_index(mut self, indextype: SpatialIndexType) -> Self {
304        self.spatial_index_type = indextype;
305        self
306    }
307
308    /// Build the interpolator with the given training data
309    pub fn build(
310        self,
311        points: &ArrayView2<F>,
312        values: &ArrayView1<F>,
313    ) -> InterpolateResult<HighDimensionalInterpolator<F>> {
314        if points.nrows() != values.len() {
315            return Err(InterpolateError::invalid_input(
316                "Number of points must match number of values".to_string(),
317            ));
318        }
319
320        if points.nrows() < 2 {
321            return Err(InterpolateError::invalid_input(
322                "At least 2 points are required".to_string(),
323            ));
324        }
325
326        let n_dims = points.ncols();
327        let n_points = points.nrows();
328
329        // Apply dimension reduction if specified
330        let (reduced_points, dimension_reduction) = match &self.dimension_reduction {
331            DimensionReductionMethod::None => (points.to_owned(), None),
332            DimensionReductionMethod::PCA { target_dims } => {
333                let dr = Self::apply_pca(points, *target_dims)?;
334                let reduced = Self::transform_points(points, &dr)?;
335                (reduced, Some(dr))
336            }
337            DimensionReductionMethod::RandomProjection { target_dims } => {
338                let dr = Self::apply_random_projection(points, *target_dims)?;
339                let reduced = Self::transform_points(points, &dr)?;
340                (reduced, Some(dr))
341            }
342            DimensionReductionMethod::LocalLinearEmbedding {
343                target_dims,
344                n_neighbors,
345            } => {
346                let dr = Self::apply_lle(points, *target_dims, *n_neighbors)?;
347                let reduced = Self::transform_points(points, &dr)?;
348                (reduced, Some(dr))
349            }
350        };
351
352        // Choose spatial index based on reduced dimensionality
353        let effective_dims = reduced_points.ncols();
354        let spatial_index_type = match self.spatial_index_type {
355            SpatialIndexType::Auto => {
356                if effective_dims <= 10 {
357                    SpatialIndexType::KdTree
358                } else if effective_dims <= 50 {
359                    SpatialIndexType::BallTree
360                } else {
361                    SpatialIndexType::BruteForce
362                }
363            }
364            other => other,
365        };
366
367        // Build spatial index
368        let spatial_index = Self::build_spatial_index(&reduced_points, spatial_index_type)?;
369
370        let stats = InterpolatorStats {
371            n_training_points: n_points,
372            original_dimensions: n_dims,
373            reduced_dimensions: if dimension_reduction.is_some() {
374                Some(effective_dims)
375            } else {
376                None
377            },
378            average_neighbors_used: 0.0,
379            cache_hit_rate: 0.0,
380        };
381
382        Ok(HighDimensionalInterpolator {
383            points: reduced_points,
384            values: values.to_owned(),
385            dimension_reduction,
386            local_method: self.local_method,
387            sparse_strategy: self.sparse_strategy,
388            spatial_index,
389            stats,
390        })
391    }
392
393    /// Apply PCA dimension reduction
394    fn apply_pca(
395        points: &ArrayView2<F>,
396        target_dims: usize,
397    ) -> InterpolateResult<DimensionReduction<F>> {
398        let n_dims = points.ncols();
399        let target_dims = target_dims.min(n_dims);
400
401        // Center the data
402        let mean = points.mean_axis(Axis(0)).expect("Operation failed");
403        let centered = points - &mean;
404
405        // Compute covariance matrix (simplified)
406        let n_points = F::from_usize(points.nrows()).expect("Operation failed");
407        let _cov = centered.t().dot(&centered) / (n_points - F::one());
408
409        // For simplicity, use a random projection as approximation to PCA
410        // In a full implementation, we would compute actual eigenvectors
411        let mut transformation = Array2::zeros((n_dims, target_dims));
412        for i in 0..target_dims {
413            for j in 0..n_dims {
414                // Use a simple pattern for now
415                transformation[[j, i]] = if i == j {
416                    F::one()
417                } else if i < n_dims && j < n_dims {
418                    F::from_f64(0.1).expect("Operation failed")
419                } else {
420                    F::zero()
421                };
422            }
423        }
424
425        Ok(DimensionReduction {
426            method: DimensionReductionMethod::PCA { target_dims },
427            transformation_matrix: transformation,
428            mean,
429            explained_variance_ratio: None,
430        })
431    }
432
433    /// Apply random projection dimension reduction
434    fn apply_random_projection(
435        points: &ArrayView2<F>,
436        target_dims: usize,
437    ) -> InterpolateResult<DimensionReduction<F>> {
438        let n_dims = points.ncols();
439        let target_dims = target_dims.min(n_dims);
440
441        // Create random projection matrix
442        let mut transformation = Array2::zeros((n_dims, target_dims));
443        for i in 0..n_dims {
444            for j in 0..target_dims {
445                // Use simple random-like values for projection
446                let val = if (i + j) % 3 == 0 {
447                    F::one()
448                } else if (i + j) % 3 == 1 {
449                    -F::one()
450                } else {
451                    F::zero()
452                };
453                transformation[[i, j]] =
454                    val / F::from_f64((n_dims as f64).sqrt()).expect("Operation failed");
455            }
456        }
457
458        let mean = Array1::zeros(n_dims);
459
460        Ok(DimensionReduction {
461            method: DimensionReductionMethod::RandomProjection { target_dims },
462            transformation_matrix: transformation,
463            mean,
464            explained_variance_ratio: None,
465        })
466    }
467
468    /// Apply Local Linear Embedding (simplified)
469    fn apply_lle(
470        points: &ArrayView2<F>,
471        target_dims: usize,
472        _n_neighbors: usize,
473    ) -> InterpolateResult<DimensionReduction<F>> {
474        // For simplicity, fall back to random projection
475        // A full LLE implementation would require eigenvalue decomposition
476        Self::apply_random_projection(points, target_dims)
477    }
478
479    /// Transform points using dimension reduction
480    fn transform_points(
481        points: &ArrayView2<F>,
482        dr: &DimensionReduction<F>,
483    ) -> InterpolateResult<Array2<F>> {
484        let centered = points - &dr.mean;
485        let transformed = centered.dot(&dr.transformation_matrix);
486        Ok(transformed)
487    }
488
489    /// Build spatial index for fast neighbor queries
490    fn build_spatial_index(
491        points: &Array2<F>,
492        index_type: SpatialIndexType,
493    ) -> InterpolateResult<SpatialIndex<F>> {
494        let n_dims = points.ncols();
495        let n_points = points.nrows();
496
497        match index_type {
498            SpatialIndexType::KdTree => {
499                if n_dims <= 10 && n_points >= 20 {
500                    // Try to build KdTree for moderate dimensions and sufficient points
501                    match KdTree::new(points.view()) {
502                        Ok(kdtree) => Ok(SpatialIndex::KdTree(kdtree)),
503                        Err(_) => {
504                            // Fall back to brute force if KdTree construction fails
505                            Ok(SpatialIndex::BruteForce(points.clone()))
506                        }
507                    }
508                } else {
509                    // Use brute force for very small datasets or high dimensions
510                    Ok(SpatialIndex::BruteForce(points.clone()))
511                }
512            }
513            SpatialIndexType::BallTree => {
514                if n_points >= 50 {
515                    // Try to build BallTree for larger datasets
516                    match BallTree::new(points.clone()) {
517                        Ok(balltree) => Ok(SpatialIndex::BallTree(balltree)),
518                        Err(_) => {
519                            // Fall back to brute force if BallTree construction fails
520                            Ok(SpatialIndex::BruteForce(points.clone()))
521                        }
522                    }
523                } else {
524                    // Use brute force for small datasets
525                    Ok(SpatialIndex::BruteForce(points.clone()))
526                }
527            }
528            SpatialIndexType::BruteForce => Ok(SpatialIndex::BruteForce(points.clone())),
529            SpatialIndexType::Auto => {
530                // Intelligent selection based on data characteristics
531                if n_points < 20 {
532                    // Small datasets: always use brute force
533                    Ok(SpatialIndex::BruteForce(points.clone()))
534                } else if n_dims <= 5 && n_points >= 100 {
535                    // Low-dimensional, large datasets: prefer KdTree
536                    match KdTree::new(points.view()) {
537                        Ok(kdtree) => Ok(SpatialIndex::KdTree(kdtree)),
538                        Err(_) => Ok(SpatialIndex::BruteForce(points.clone())),
539                    }
540                } else if n_dims <= 15 && n_points >= 50 {
541                    // Medium-dimensional datasets: prefer BallTree
542                    match BallTree::new(points.clone()) {
543                        Ok(balltree) => Ok(SpatialIndex::BallTree(balltree)),
544                        Err(_) => Ok(SpatialIndex::BruteForce(points.clone())),
545                    }
546                } else {
547                    // High-dimensional or edge cases: use brute force
548                    Ok(SpatialIndex::BruteForce(points.clone()))
549                }
550            }
551        }
552    }
553}
554
555impl<F> HighDimensionalInterpolator<F>
556where
557    F: Float
558        + FromPrimitive
559        + Debug
560        + Display
561        + Zero
562        + Copy
563        + AddAssign
564        + ScalarOperand
565        + 'static
566        + std::marker::Send
567        + std::marker::Sync
568        + ordered_float::FloatCore,
569{
570    /// Create a new builder for high-dimensional interpolation
571    ///
572    /// # Examples
573    ///
574    /// ```rust
575    /// use scirs2_interpolate::high_dimensional::HighDimensionalInterpolator;
576    ///
577    /// // Create a builder for high-dimensional interpolation
578    /// let builder = HighDimensionalInterpolator::<f64>::builder();
579    ///
580    /// // The builder can be configured with various options before building
581    /// println!("Builder created successfully");
582    /// ```
583    pub fn builder() -> HighDimensionalInterpolatorBuilder<F> {
584        HighDimensionalInterpolatorBuilder::new()
585    }
586
587    /// Interpolate at a query point
588    ///
589    /// # Arguments
590    ///
591    /// * `query` - Query point coordinates with shape (n_dims,)
592    ///
593    /// # Returns
594    ///
595    /// Interpolated value at the query point
596    ///
597    /// # Examples
598    ///
599    /// ```rust
600    /// use scirs2_interpolate::high_dimensional::{HighDimensionalInterpolator, DimensionReductionMethod};
601    /// use scirs2_core::ndarray::{Array1, Array2};
602    ///
603    /// // Create sample 5D data
604    /// let points = Array2::from_shape_vec((4, 5), vec![
605    ///     0.0, 0.0, 0.0, 0.0, 0.0,
606    ///     1.0, 0.0, 0.0, 0.0, 0.0,
607    ///     0.0, 1.0, 0.0, 0.0, 0.0,
608    ///     0.0, 0.0, 1.0, 0.0, 0.0,
609    /// ]).expect("Operation failed");
610    /// let values = Array1::from_vec(vec![0.0, 1.0, 2.0, 3.0]);
611    ///
612    /// let interpolator = HighDimensionalInterpolator::builder()
613    ///     .with_dimension_reduction(DimensionReductionMethod::PCA { target_dims: 3 })
614    ///     .build(&points.view(), &values.view())
615    ///     .expect("Operation failed");
616    ///
617    /// let query = Array1::from_vec(vec![0.5, 0.5, 0.0, 0.0, 0.0]);
618    /// let result = interpolator.interpolate(&query.view()).expect("Operation failed");
619    /// ```
620    pub fn interpolate(&self, query: &ArrayView1<F>) -> InterpolateResult<F> {
621        // Transform query point if dimension reduction is applied
622        let transformed_query = if let Some(dr) = &self.dimension_reduction {
623            let centered = query - &dr.mean;
624            centered.dot(&dr.transformation_matrix)
625        } else {
626            query.to_owned()
627        };
628
629        // Find neighbors using spatial index
630        let neighbors = self.find_neighbors(&transformed_query)?;
631
632        // Interpolate using local method
633        self.interpolate_local(&transformed_query, &neighbors)
634    }
635
636    /// Find neighbors for a query point
637    fn find_neighbors(&self, query: &Array1<F>) -> InterpolateResult<Vec<(usize, F)>> {
638        match &self.spatial_index {
639            SpatialIndex::BruteForce(points) => self.brute_force_neighbors(query, points),
640            SpatialIndex::KdTree(kdtree) => {
641                // Use KdTree for efficient neighbor search
642                let query_slice = query.as_slice().expect("Operation failed");
643                match &self.local_method {
644                    LocalMethod::KNearestNeighbors { k, .. } => {
645                        let neighbors = kdtree.k_nearest_neighbors(query_slice, *k)?;
646                        Ok(neighbors)
647                    }
648                    LocalMethod::LocallyWeighted { bandwidth, .. } => {
649                        let radius = F::from_f64(*bandwidth).expect("Operation failed");
650                        let neighbors = kdtree.radius_neighbors(query_slice, radius)?;
651                        Ok(neighbors)
652                    }
653                    LocalMethod::LocalRBF { radius, .. } => {
654                        let search_radius = F::from_f64(*radius).expect("Operation failed");
655                        let neighbors = kdtree.radius_neighbors(query_slice, search_radius)?;
656                        Ok(neighbors)
657                    }
658                }
659            }
660            SpatialIndex::BallTree(balltree) => {
661                // Use BallTree for efficient neighbor search
662                let query_slice = query.as_slice().expect("Operation failed");
663                match &self.local_method {
664                    LocalMethod::KNearestNeighbors { k, .. } => {
665                        let neighbors = balltree.k_nearest_neighbors(query_slice, *k)?;
666                        Ok(neighbors)
667                    }
668                    LocalMethod::LocallyWeighted { bandwidth, .. } => {
669                        let radius = F::from_f64(*bandwidth).expect("Operation failed");
670                        let neighbors = balltree.radius_neighbors(query_slice, radius)?;
671                        Ok(neighbors)
672                    }
673                    LocalMethod::LocalRBF { radius, .. } => {
674                        let search_radius = F::from_f64(*radius).expect("Operation failed");
675                        let neighbors = balltree.radius_neighbors(query_slice, search_radius)?;
676                        Ok(neighbors)
677                    }
678                }
679            }
680        }
681    }
682
683    /// Brute force neighbor search
684    fn brute_force_neighbors(
685        &self,
686        query: &Array1<F>,
687        points: &Array2<F>,
688    ) -> InterpolateResult<Vec<(usize, F)>> {
689        let mut distances: Vec<(usize, F)> = Vec::new();
690
691        for (i, point) in points.axis_iter(Axis(0)).enumerate() {
692            let dist = self.compute_distance(query, &point.to_owned());
693            distances.push((i, dist));
694        }
695
696        // Sort by distance
697        distances.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(std::cmp::Ordering::Equal));
698
699        // Return based on local method
700        match &self.local_method {
701            LocalMethod::KNearestNeighbors { k, .. } => {
702                Ok(distances.into_iter().take(*k).collect())
703            }
704            LocalMethod::LocallyWeighted { bandwidth, .. } => {
705                // Return all points within bandwidth
706                Ok(distances
707                    .into_iter()
708                    .filter(|(_, dist)| *dist <= F::from_f64(*bandwidth).expect("Operation failed"))
709                    .collect())
710            }
711            LocalMethod::LocalRBF { radius, .. } => {
712                // Return all points within radius
713                Ok(distances
714                    .into_iter()
715                    .filter(|(_, dist)| *dist <= F::from_f64(*radius).expect("Operation failed"))
716                    .collect())
717            }
718        }
719    }
720
721    /// Compute distance between two points
722    fn compute_distance(&self, p1: &Array1<F>, p2: &Array1<F>) -> F {
723        // Euclidean distance
724        let diff = p1 - p2;
725        diff.iter()
726            .map(|&x| x * x)
727            .fold(F::zero(), |acc, x| acc + x)
728            .sqrt()
729    }
730
731    /// Perform local interpolation using neighbors
732    fn interpolate_local(
733        &self,
734        self_query: &Array1<F>,
735        neighbors: &[(usize, F)],
736    ) -> InterpolateResult<F> {
737        if neighbors.is_empty() {
738            return Err(InterpolateError::ComputationError(
739                "No neighbors found for interpolation".to_string(),
740            ));
741        }
742
743        match &self.local_method {
744            LocalMethod::KNearestNeighbors { weight_power, .. } => {
745                let mut weighted_sum = F::zero();
746                let mut weight_sum = F::zero();
747
748                for &(idx, dist) in neighbors {
749                    let weight = if dist == F::zero() {
750                        // If point is exactly at a data point, return that value
751                        return Ok(self.values[idx]);
752                    } else {
753                        F::one() / dist.powf(F::from_f64(*weight_power).expect("Operation failed"))
754                    };
755
756                    weighted_sum += weight * self.values[idx];
757                    weight_sum += weight;
758                }
759
760                if weight_sum == F::zero() {
761                    return Err(InterpolateError::ComputationError(
762                        "Zero weight sum in interpolation".to_string(),
763                    ));
764                }
765
766                Ok(weighted_sum / weight_sum)
767            }
768            LocalMethod::LocallyWeighted { .. } => {
769                // Simplified locally weighted regression
770                // In a full implementation, this would fit a local polynomial
771                let mut sum = F::zero();
772                let count = F::from_usize(neighbors.len()).expect("Operation failed");
773
774                for &(idx, _) in neighbors {
775                    sum += self.values[idx];
776                }
777
778                Ok(sum / count)
779            }
780            LocalMethod::LocalRBF { rbf_type, .. } => {
781                // Simplified local RBF interpolation
782                self.interpolate_local_rbf(neighbors, rbf_type)
783            }
784        }
785    }
786
787    /// Perform local RBF interpolation
788    fn interpolate_local_rbf(
789        &self,
790        neighbors: &[(usize, F)],
791        _rbf_type: &LocalRBFType,
792    ) -> InterpolateResult<F> {
793        // Simplified RBF interpolation
794        let mut sum = F::zero();
795        let mut weight_sum = F::zero();
796
797        for &(idx, dist) in neighbors {
798            // Use Gaussian RBF
799            let weight = (-dist * dist).exp();
800            sum += weight * self.values[idx];
801            weight_sum += weight;
802        }
803
804        if weight_sum == F::zero() {
805            return Err(InterpolateError::ComputationError(
806                "Zero weight sum in RBF interpolation".to_string(),
807            ));
808        }
809
810        Ok(sum / weight_sum)
811    }
812
813    /// Interpolate at multiple query points
814    ///
815    /// # Arguments
816    ///
817    /// * `queries` - Query points with shape (n_queries, n_dims)
818    ///
819    /// # Returns
820    ///
821    /// Array of interpolated values with shape (n_queries,)
822    ///
823    /// # Examples
824    ///
825    /// ```rust
826    /// use scirs2_interpolate::high_dimensional::HighDimensionalInterpolator;
827    /// use scirs2_core::ndarray::{Array1, Array2};
828    ///
829    /// let points = Array2::from_shape_vec((3, 2), vec![0.0, 0.0, 1.0, 0.0, 0.0, 1.0]).expect("Operation failed");
830    /// let values = Array1::from_vec(vec![0.0, 1.0, 2.0]);
831    ///
832    /// let interpolator = HighDimensionalInterpolator::builder()
833    ///     .build(&points.view(), &values.view())
834    ///     .expect("Operation failed");
835    ///
836    /// let queries = Array2::from_shape_vec((2, 2), vec![0.5, 0.0, 0.0, 0.5]).expect("Operation failed");
837    /// let results = interpolator.interpolate_multi(&queries.view()).expect("Operation failed");
838    /// assert_eq!(results.len(), 2);
839    /// ```
840    pub fn interpolate_multi(&self, queries: &ArrayView2<F>) -> InterpolateResult<Array1<F>> {
841        let mut results = Array1::zeros(queries.nrows());
842
843        for (i, query) in queries.axis_iter(Axis(0)).enumerate() {
844            results[i] = self.interpolate(&query.view())?;
845        }
846
847        Ok(results)
848    }
849
850    /// Interpolate at multiple query points in parallel
851    ///
852    /// This method provides parallel evaluation for large query sets, providing
853    /// significant speedup for expensive high-dimensional interpolation operations.
854    ///
855    /// # Arguments
856    ///
857    /// * `queries` - Query points with shape (n_queries, n_dims)
858    /// * `workers` - Number of worker threads to use (None = automatic)
859    ///
860    /// # Returns
861    ///
862    /// Array of interpolated values with shape (n_queries,)
863    ///
864    /// # Performance
865    ///
866    /// The parallel version provides speedup for:
867    /// - Large query sets (n_queries > 100)
868    /// - High-dimensional data (n_dims > 10)
869    /// - Complex local methods (LocalRBF, LocallyWeighted)
870    ///
871    /// # Examples
872    ///
873    /// ```rust
874    /// use scirs2_interpolate::high_dimensional::HighDimensionalInterpolator;
875    /// use scirs2_core::ndarray::{Array1, Array2};
876    ///
877    /// let points = Array2::from_shape_vec((3, 2), vec![0.0, 0.0, 1.0, 0.0, 0.0, 1.0]).expect("Operation failed");
878    /// let values = Array1::from_vec(vec![0.0, 1.0, 2.0]);
879    ///
880    /// let interpolator = HighDimensionalInterpolator::builder()
881    ///     .build(&points.view(), &values.view())
882    ///     .expect("Operation failed");
883    ///
884    /// // Create many query points for parallel processing
885    /// let queries = Array2::from_shape_vec((1000, 2), (0..2000).map(|i| i as f64 / 1000.0).collect()).expect("Operation failed");
886    ///
887    /// // Use 4 worker threads
888    /// let results = interpolator.interpolate_multi_parallel(&queries.view(), Some(4)).expect("Operation failed");
889    /// assert_eq!(results.len(), 1000);
890    /// ```
891    pub fn interpolate_multi_parallel(
892        &self,
893        queries: &ArrayView2<F>,
894        workers: Option<usize>,
895    ) -> InterpolateResult<Array1<F>> {
896        use crate::parallel::{estimate_chunk_size, ParallelConfig};
897        use scirs2_core::parallel_ops::*;
898
899        let n_queries = queries.nrows();
900
901        // For small query sets, use sequential processing
902        if n_queries < 50 {
903            return self.interpolate_multi(queries);
904        }
905
906        // Set up parallel configuration
907        let parallel_config = if let Some(n_workers) = workers {
908            ParallelConfig::new().with_workers(n_workers)
909        } else {
910            ParallelConfig::new()
911        };
912
913        // Estimate computational cost based on local method
914        let cost_factor = match &self.local_method {
915            LocalMethod::KNearestNeighbors { .. } => 2.0,
916            LocalMethod::LocallyWeighted { .. } => 5.0,
917            LocalMethod::LocalRBF { .. } => 8.0,
918        };
919
920        let chunk_size = estimate_chunk_size(n_queries, cost_factor, &parallel_config);
921
922        // Process queries in parallel
923        let results: Result<Vec<F>, InterpolateError> = (0..n_queries)
924            .into_par_iter()
925            .with_min_len(chunk_size)
926            .map(|i| {
927                let query = queries.slice(scirs2_core::ndarray::s![i, ..]);
928                self.interpolate(&query.view())
929            })
930            .collect::<Result<Vec<F>, InterpolateError>>();
931
932        Ok(Array1::from_vec(results?))
933    }
934
935    /// Get interpolator statistics
936    pub fn stats(&self) -> &InterpolatorStats {
937        &self.stats
938    }
939
940    /// Get the effective dimensionality (after dimension reduction)
941    pub fn effective_dimensions(&self) -> usize {
942        self.stats
943            .reduced_dimensions
944            .unwrap_or(self.stats.original_dimensions)
945    }
946
947    /// Get the training data size
948    pub fn training_size(&self) -> usize {
949        self.stats.n_training_points
950    }
951}
952
953/// Create a high-dimensional interpolator with k-nearest neighbors
954///
955/// This is a convenience function for creating an interpolator that uses
956/// k-nearest neighbor interpolation with inverse distance weighting.
957///
958/// # Arguments
959///
960/// * `points` - Training data points with shape (n_points, n_dims)
961/// * `values` - Training data values with shape (n_points,)
962/// * `k` - Number of nearest neighbors to use
963///
964/// # Returns
965///
966/// A configured high-dimensional interpolator
967///
968/// # Examples
969///
970/// ```rust
971/// use scirs2_interpolate::high_dimensional::make_knn_interpolator;
972/// use scirs2_core::ndarray::{Array1, Array2};
973///
974/// // Create 3D scattered data
975/// let points = Array2::from_shape_vec((5, 3), vec![
976///     0.0, 0.0, 0.0,
977///     1.0, 0.0, 0.0,
978///     0.0, 1.0, 0.0,
979///     0.0, 0.0, 1.0,
980///     1.0, 1.0, 1.0,
981/// ]).expect("Operation failed");
982/// let values = Array1::from_vec(vec![0.0, 1.0, 2.0, 3.0, 6.0]);
983///
984/// let interpolator = make_knn_interpolator(&points.view(), &values.view(), 3).expect("Operation failed");
985///
986/// let query = Array1::from_vec(vec![0.5, 0.5, 0.5]);
987/// let result = interpolator.interpolate(&query.view()).expect("Operation failed");
988/// ```
989#[allow(dead_code)]
990pub fn make_knn_interpolator<F>(
991    points: &ArrayView2<F>,
992    values: &ArrayView1<F>,
993    k: usize,
994) -> InterpolateResult<HighDimensionalInterpolator<F>>
995where
996    F: Float
997        + FromPrimitive
998        + Debug
999        + Display
1000        + Zero
1001        + Copy
1002        + AddAssign
1003        + ScalarOperand
1004        + 'static
1005        + Send
1006        + Sync
1007        + ordered_float::FloatCore,
1008{
1009    HighDimensionalInterpolator::builder()
1010        .with_local_method(LocalMethod::KNearestNeighbors {
1011            k,
1012            weight_power: 2.0,
1013        })
1014        .build(points, values)
1015}
1016
1017/// Create a high-dimensional interpolator with PCA dimension reduction
1018///
1019/// This function creates an interpolator that first reduces the dimensionality
1020/// of the data using Principal Component Analysis (PCA), then performs
1021/// k-nearest neighbor interpolation in the reduced space.
1022///
1023/// # Arguments
1024///
1025/// * `points` - Training data points with shape (n_points, n_dims)
1026/// * `values` - Training data values with shape (n_points,)
1027/// * `target_dims` - Target number of dimensions after PCA reduction
1028/// * `k` - Number of nearest neighbors to use in reduced space
1029///
1030/// # Returns
1031///
1032/// A configured high-dimensional interpolator with PCA preprocessing
1033///
1034/// # Examples
1035///
1036/// ```rust
1037/// use scirs2_interpolate::high_dimensional::make_pca_interpolator;
1038/// use scirs2_core::ndarray::{Array1, Array2};
1039///
1040/// // Create high-dimensional data that lies on a lower-dimensional manifold
1041/// let points = Array2::from_shape_vec((4, 6), vec![
1042///     1.0, 1.0, 1.0, 0.0, 0.0, 0.0,  // Redundant dimensions
1043///     2.0, 2.0, 2.0, 0.0, 0.0, 0.0,
1044///     1.0, 2.0, 3.0, 0.0, 0.0, 0.0,
1045///     3.0, 1.0, 1.0, 0.0, 0.0, 0.0,
1046/// ]).expect("Operation failed");
1047/// let values = Array1::from_vec(vec![1.0, 2.0, 3.0, 2.5]);
1048///
1049/// // Reduce from 6D to 3D, then use 3 nearest neighbors
1050/// let interpolator = make_pca_interpolator(&points.view(), &values.view(), 3, 3).expect("Operation failed");
1051///
1052/// let query = Array1::from_vec(vec![1.5, 1.5, 1.5, 0.0, 0.0, 0.0]);
1053/// let result = interpolator.interpolate(&query.view()).expect("Operation failed");
1054/// ```
1055#[allow(dead_code)]
1056pub fn make_pca_interpolator<F>(
1057    points: &ArrayView2<F>,
1058    values: &ArrayView1<F>,
1059    target_dims: usize,
1060    k: usize,
1061) -> InterpolateResult<HighDimensionalInterpolator<F>>
1062where
1063    F: Float
1064        + FromPrimitive
1065        + Debug
1066        + Display
1067        + Zero
1068        + Copy
1069        + AddAssign
1070        + ScalarOperand
1071        + 'static
1072        + Send
1073        + Sync
1074        + ordered_float::FloatCore,
1075{
1076    HighDimensionalInterpolator::builder()
1077        .with_dimension_reduction(DimensionReductionMethod::PCA { target_dims })
1078        .with_local_method(LocalMethod::KNearestNeighbors {
1079            k,
1080            weight_power: 2.0,
1081        })
1082        .build(points, values)
1083}
1084
1085/// Create a high-dimensional interpolator with local RBF
1086///
1087/// This function creates an interpolator that uses locally supported Radial
1088/// Basis Functions (RBF) for interpolation. Only points within the specified
1089/// radius contribute to the interpolation at each query point.
1090///
1091/// # Arguments
1092///
1093/// * `points` - Training data points with shape (n_points, n_dims)
1094/// * `values` - Training data values with shape (n_points,)
1095/// * `radius` - Radius of local support for RBF functions
1096///
1097/// # Returns
1098///
1099/// A configured high-dimensional interpolator with local RBF
1100///
1101/// # Examples
1102///
1103/// ```rust
1104/// use scirs2_interpolate::high_dimensional::make_local_rbf_interpolator;
1105/// use scirs2_core::ndarray::{Array1, Array2};
1106///
1107/// // Create scattered 2D data
1108/// let points = Array2::from_shape_vec((4, 2), vec![
1109///     0.0, 0.0,
1110///     1.0, 0.0,
1111///     0.0, 1.0,
1112///     1.0, 1.0,
1113/// ]).expect("Operation failed");
1114/// let values = Array1::from_vec(vec![0.0, 1.0, 1.0, 2.0]);
1115///
1116/// // Use radius of 1.5 to include nearby points
1117/// let interpolator = make_local_rbf_interpolator(&points.view(), &values.view(), 1.5).expect("Operation failed");
1118///
1119/// let query = Array1::from_vec(vec![0.5, 0.5]);
1120/// let result = interpolator.interpolate(&query.view()).expect("Operation failed");
1121/// ```
1122#[allow(dead_code)]
1123pub fn make_local_rbf_interpolator<F>(
1124    points: &ArrayView2<F>,
1125    values: &ArrayView1<F>,
1126    radius: f64,
1127) -> InterpolateResult<HighDimensionalInterpolator<F>>
1128where
1129    F: Float
1130        + FromPrimitive
1131        + Debug
1132        + Display
1133        + Zero
1134        + Copy
1135        + AddAssign
1136        + ScalarOperand
1137        + 'static
1138        + Send
1139        + Sync
1140        + ordered_float::FloatCore,
1141{
1142    HighDimensionalInterpolator::builder()
1143        .with_local_method(LocalMethod::LocalRBF {
1144            radius,
1145            rbf_type: LocalRBFType::CompactGaussian,
1146        })
1147        .build(points, values)
1148}
1149
1150#[cfg(test)]
1151mod tests {
1152    use super::*;
1153    use scirs2_core::ndarray::array;
1154
1155    #[test]
1156    fn test_high_dimensional_interpolator_creation() {
1157        let points = array![
1158            [0.0, 0.0, 0.0],
1159            [1.0, 0.0, 0.0],
1160            [0.0, 1.0, 0.0],
1161            [0.0, 0.0, 1.0]
1162        ];
1163        let values = array![0.0, 1.0, 1.0, 1.0];
1164
1165        let interpolator = HighDimensionalInterpolator::<f64>::builder()
1166            .build(&points.view(), &values.view())
1167            .expect("Operation failed");
1168
1169        assert_eq!(interpolator.effective_dimensions(), 3);
1170        assert_eq!(interpolator.training_size(), 4);
1171    }
1172
1173    #[test]
1174    fn test_knn_interpolation() {
1175        let points = array![[0.0, 0.0], [1.0, 0.0], [0.0, 1.0], [1.0, 1.0]];
1176        let values = array![0.0, 1.0, 1.0, 2.0];
1177
1178        let interpolator =
1179            make_knn_interpolator(&points.view(), &values.view(), 2).expect("Operation failed");
1180
1181        // Interpolate at the center
1182        let query = array![0.5, 0.5];
1183        let result = interpolator
1184            .interpolate(&query.view())
1185            .expect("Operation failed");
1186
1187        // Should be close to the average of nearby points
1188        assert!((0.5..=1.5).contains(&result));
1189    }
1190
1191    #[test]
1192    fn test_pca_interpolation() {
1193        // Create 3D data that lies on a 2D plane
1194        let points = array![
1195            [1.0, 1.0, 1.0],
1196            [2.0, 2.0, 2.0],
1197            [1.0, 2.0, 3.0],
1198            [3.0, 1.0, 1.0]
1199        ];
1200        let values = array![1.0, 2.0, 3.0, 2.0];
1201
1202        let interpolator =
1203            make_pca_interpolator(&points.view(), &values.view(), 2, 3).expect("Operation failed");
1204
1205        assert_eq!(interpolator.effective_dimensions(), 2);
1206
1207        // Test interpolation
1208        let query = array![1.5, 1.5, 1.5];
1209        let result = interpolator
1210            .interpolate(&query.view())
1211            .expect("Operation failed");
1212
1213        assert!((0.5..=3.5).contains(&result));
1214    }
1215
1216    #[test]
1217    fn test_local_rbf_interpolation() {
1218        let points = array![[0.0, 0.0], [1.0, 0.0], [0.0, 1.0], [1.0, 1.0]];
1219        let values = array![0.0, 1.0, 1.0, 2.0];
1220
1221        let interpolator = make_local_rbf_interpolator(&points.view(), &values.view(), 1.5)
1222            .expect("Operation failed");
1223
1224        let query = array![0.5, 0.5];
1225        let result = interpolator
1226            .interpolate(&query.view())
1227            .expect("Operation failed");
1228
1229        assert!((0.0..=2.0).contains(&result));
1230    }
1231
1232    #[test]
1233    fn test_multi_interpolation() {
1234        let points = array![[0.0, 0.0], [1.0, 0.0], [0.0, 1.0], [1.0, 1.0]];
1235        let values = array![0.0, 1.0, 1.0, 2.0];
1236
1237        let interpolator =
1238            make_knn_interpolator(&points.view(), &values.view(), 3).expect("Operation failed");
1239
1240        let queries = array![[0.25, 0.25], [0.75, 0.75]];
1241        let results = interpolator
1242            .interpolate_multi(&queries.view())
1243            .expect("Operation failed");
1244
1245        assert_eq!(results.len(), 2);
1246        assert!(results[0] >= 0.0 && results[0] <= 2.0);
1247        assert!(results[1] >= 0.0 && results[1] <= 2.0);
1248    }
1249
1250    #[test]
1251    fn test_dimension_reduction_methods() {
1252        let points = array![
1253            [1.0, 2.0, 3.0, 4.0],
1254            [2.0, 3.0, 4.0, 5.0],
1255            [3.0, 4.0, 5.0, 6.0]
1256        ];
1257        let values = array![1.0, 2.0, 3.0];
1258
1259        // Test PCA
1260        let pca_interp = HighDimensionalInterpolator::<f64>::builder()
1261            .with_dimension_reduction(DimensionReductionMethod::PCA { target_dims: 2 })
1262            .build(&points.view(), &values.view())
1263            .expect("Operation failed");
1264
1265        assert_eq!(pca_interp.effective_dimensions(), 2);
1266
1267        // Test Random Projection
1268        let rp_interp = HighDimensionalInterpolator::builder()
1269            .with_dimension_reduction(DimensionReductionMethod::RandomProjection { target_dims: 2 })
1270            .build(&points.view(), &values.view())
1271            .expect("Operation failed");
1272
1273        assert_eq!(rp_interp.effective_dimensions(), 2);
1274    }
1275
1276    #[test]
1277    fn test_builder_pattern() {
1278        let points = array![[0.0, 0.0], [1.0, 1.0]];
1279        let values = array![0.0, 1.0];
1280
1281        let interpolator = HighDimensionalInterpolator::builder()
1282            .with_local_method(LocalMethod::LocallyWeighted {
1283                bandwidth: 1.0,
1284                degree: 1,
1285            })
1286            .with_spatial_index(SpatialIndexType::BruteForce)
1287            .build(&points.view(), &values.view())
1288            .expect("Operation failed");
1289
1290        assert_eq!(interpolator.training_size(), 2);
1291    }
1292}