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(¢ered) / (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, ¶llel_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}