Skip to main content

scirs2_interpolate/
statistical.rs

1//! Advanced statistical interpolation methods
2//!
3//! This module provides statistical interpolation techniques that go beyond
4//! deterministic interpolation, including:
5//! - Bootstrap confidence intervals
6//! - Bayesian interpolation with posterior distributions
7//! - Quantile interpolation/regression
8//! - Robust interpolation methods
9//! - Stochastic interpolation for random fields
10
11#![allow(clippy::too_many_arguments)]
12#![allow(dead_code)]
13
14use crate::error::{InterpolateError, InterpolateResult};
15use scirs2_core::ndarray::{Array1, Array2, ArrayView1, ArrayView2, Axis};
16use scirs2_core::numeric::{Float, FromPrimitive};
17use scirs2_core::random::{rngs::StdRng, Rng, RngExt, SeedableRng};
18use scirs2_core::random::{Distribution, Normal, StandardNormal};
19use statrs::statistics::Statistics;
20use std::fmt::{Debug, Display};
21
22/// Configuration for bootstrap confidence intervals
23#[derive(Debug, Clone)]
24pub struct BootstrapConfig {
25    /// Number of bootstrap samples
26    pub n_samples: usize,
27    /// Confidence level (e.g., 0.95 for 95% CI)
28    pub confidence_level: f64,
29    /// Random seed for reproducibility
30    pub seed: Option<u64>,
31}
32
33impl Default for BootstrapConfig {
34    fn default() -> Self {
35        Self {
36            n_samples: 1000,
37            confidence_level: 0.95,
38            seed: None,
39        }
40    }
41}
42
43/// Result from bootstrap interpolation including confidence intervals
44#[derive(Debug, Clone)]
45pub struct BootstrapResult<T: Float> {
46    /// Point estimate (median of bootstrap samples)
47    pub estimate: Array1<T>,
48    /// Lower confidence bound
49    pub lower_bound: Array1<T>,
50    /// Upper confidence bound
51    pub upper_bound: Array1<T>,
52    /// Standard error estimate
53    pub std_error: Array1<T>,
54}
55
56/// Bootstrap interpolation with confidence intervals
57///
58/// This method performs interpolation with uncertainty quantification
59/// using bootstrap resampling of the input data.
60pub struct BootstrapInterpolator<T: Float> {
61    /// Configuration for bootstrap
62    config: BootstrapConfig,
63    /// Base interpolator factory
64    interpolator_factory:
65        Box<dyn Fn(&ArrayView1<T>, &ArrayView1<T>) -> InterpolateResult<Box<dyn Fn(T) -> T>>>,
66}
67
68impl<T: Float + FromPrimitive + Debug + Display + std::iter::Sum> BootstrapInterpolator<T> {
69    /// Create a new bootstrap interpolator
70    pub fn new<F>(config: BootstrapConfig, interpolator_factory: F) -> Self
71    where
72        F: Fn(&ArrayView1<T>, &ArrayView1<T>) -> InterpolateResult<Box<dyn Fn(T) -> T>> + 'static,
73    {
74        Self {
75            config,
76            interpolator_factory: Box::new(interpolator_factory),
77        }
78    }
79
80    /// Perform bootstrap interpolation at given points
81    pub fn interpolate(
82        &self,
83        x: &ArrayView1<T>,
84        y: &ArrayView1<T>,
85        xnew: &ArrayView1<T>,
86    ) -> InterpolateResult<BootstrapResult<T>> {
87        if x.len() != y.len() {
88            return Err(InterpolateError::DimensionMismatch(
89                "x and y must have the same length".to_string(),
90            ));
91        }
92
93        let n = x.len();
94        let m = xnew.len();
95        let mut rng = match self.config.seed {
96            Some(seed) => StdRng::seed_from_u64(seed),
97            None => {
98                let mut rng = scirs2_core::random::rng();
99                StdRng::from_rng(&mut rng)
100            }
101        };
102
103        // Storage for bootstrap samples
104        let mut bootstrap_results = Array2::<T>::zeros((self.config.n_samples, m));
105
106        // Perform bootstrap resampling
107        for i in 0..self.config.n_samples {
108            // Resample indices with replacement
109            let indices: Vec<usize> = (0..n).map(|_| rng.random_range(0..n)).collect();
110
111            // Create resampled data
112            let x_resampled = indices.iter().map(|&idx| x[idx]).collect::<Array1<_>>();
113            let y_resampled = indices.iter().map(|&idx| y[idx]).collect::<Array1<_>>();
114
115            // Create interpolator for this bootstrap sample
116            let interpolator =
117                (self.interpolator_factory)(&x_resampled.view(), &y_resampled.view())?;
118
119            // Evaluate at _new points
120            for (j, &x_val) in xnew.iter().enumerate() {
121                bootstrap_results[[i, j]] = interpolator(x_val);
122            }
123        }
124
125        // Calculate statistics
126        let alpha = T::from(1.0 - self.config.confidence_level).expect("Operation failed");
127        let lower_percentile = alpha / T::from(2.0).expect("Operation failed");
128        let upper_percentile = T::one() - alpha / T::from(2.0).expect("Operation failed");
129
130        let mut estimate = Array1::zeros(m);
131        let mut lower_bound = Array1::zeros(m);
132        let mut upper_bound = Array1::zeros(m);
133        let mut std_error = Array1::zeros(m);
134
135        for j in 0..m {
136            let column = bootstrap_results.index_axis(Axis(1), j);
137            let mut sorted_col = column.to_vec();
138            sorted_col.sort_by(|a, b| a.partial_cmp(b).expect("Operation failed"));
139
140            // Median as point estimate
141            let median_idx = self.config.n_samples / 2;
142            estimate[j] = sorted_col[median_idx];
143
144            // Confidence bounds
145            let lower_idx = (lower_percentile
146                * T::from(self.config.n_samples).expect("Operation failed"))
147            .to_usize()
148            .expect("Operation failed");
149            let upper_idx = (upper_percentile
150                * T::from(self.config.n_samples).expect("Operation failed"))
151            .to_usize()
152            .expect("Operation failed");
153            lower_bound[j] = sorted_col[lower_idx];
154            upper_bound[j] = sorted_col[upper_idx];
155
156            // Standard error
157            let mean = column.mean().expect("Operation failed");
158            let variance = column
159                .iter()
160                .map(|&val| (val - mean) * (val - mean))
161                .sum::<T>()
162                / T::from(self.config.n_samples - 1).expect("Operation failed");
163            std_error[j] = variance.sqrt();
164        }
165
166        Ok(BootstrapResult {
167            estimate,
168            lower_bound,
169            upper_bound,
170            std_error,
171        })
172    }
173}
174
175/// Configuration for Bayesian interpolation
176pub struct BayesianConfig<T: Float> {
177    /// Prior mean function
178    pub prior_mean: Box<dyn Fn(T) -> T>,
179    /// Prior variance
180    pub prior_variance: T,
181    /// Measurement noise variance
182    pub noise_variance: T,
183    /// RBF kernel length scale parameter
184    pub length_scale: T,
185    /// Number of posterior samples to draw
186    pub n_posterior_samples: usize,
187}
188
189impl<T: Float + FromPrimitive> Default for BayesianConfig<T> {
190    fn default() -> Self {
191        Self {
192            prior_mean: Box::new(|_| T::zero()),
193            prior_variance: T::one(),
194            noise_variance: T::from(0.01).expect("Operation failed"),
195            length_scale: T::one(),
196            n_posterior_samples: 100,
197        }
198    }
199}
200
201impl<T: Float + FromPrimitive> BayesianConfig<T> {
202    /// Set the RBF kernel length scale parameter
203    pub fn with_length_scale(mut self, lengthscale: T) -> Self {
204        self.length_scale = lengthscale;
205        self
206    }
207
208    /// Set the prior variance
209    pub fn with_prior_variance(mut self, variance: T) -> Self {
210        self.prior_variance = variance;
211        self
212    }
213
214    /// Set the noise variance
215    pub fn with_noise_variance(mut self, variance: T) -> Self {
216        self.noise_variance = variance;
217        self
218    }
219
220    /// Set the number of posterior samples
221    pub fn with_n_posterior_samples(mut self, nsamples: usize) -> Self {
222        self.n_posterior_samples = nsamples;
223        self
224    }
225}
226
227/// Bayesian interpolation with full posterior distribution
228///
229/// This provides interpolation with full uncertainty quantification
230/// through Bayesian inference.
231pub struct BayesianInterpolator<T: Float> {
232    config: BayesianConfig<T>,
233    x_obs: Array1<T>,
234    y_obs: Array1<T>,
235}
236
237impl<
238        T: Float
239            + FromPrimitive
240            + Debug
241            + Display
242            + std::ops::AddAssign
243            + std::ops::SubAssign
244            + std::ops::MulAssign
245            + std::ops::DivAssign
246            + std::ops::RemAssign,
247    > BayesianInterpolator<T>
248{
249    /// Create a new Bayesian interpolator
250    pub fn new(
251        x: &ArrayView1<T>,
252        y: &ArrayView1<T>,
253        config: BayesianConfig<T>,
254    ) -> InterpolateResult<Self> {
255        if x.len() != y.len() {
256            return Err(InterpolateError::DimensionMismatch(
257                "x and y must have the same length".to_string(),
258            ));
259        }
260
261        Ok(Self {
262            config,
263            x_obs: x.to_owned(),
264            y_obs: y.to_owned(),
265        })
266    }
267
268    /// Get posterior mean at given points using proper Gaussian process regression
269    pub fn posterior_mean(&self, xnew: &ArrayView1<T>) -> InterpolateResult<Array1<T>> {
270        let n = self.x_obs.len();
271        let m = xnew.len();
272
273        if n == 0 {
274            return Err(InterpolateError::invalid_input(
275                "No observed data points".to_string(),
276            ));
277        }
278
279        // Compute covariance matrix K(X, X) + σ²I
280        let mut k_xx = Array2::<T>::zeros((n, n));
281        let length_scale = self.config.length_scale;
282
283        // Build covariance matrix with RBF kernel
284        for i in 0..n {
285            for j in 0..n {
286                let dist_sq = (self.x_obs[i] - self.x_obs[j]).powi(2);
287                k_xx[[i, j]] = self.config.prior_variance
288                    * (-dist_sq / (T::from(2.0).expect("Operation failed") * length_scale.powi(2)))
289                        .exp();
290
291                // Add noise variance to diagonal
292                if i == j {
293                    k_xx[[i, j]] += self.config.noise_variance;
294                }
295            }
296        }
297
298        // Solve the linear system K * weights = y_obs using Cholesky decomposition
299        // This is more numerically stable than matrix inversion
300        let weights = match self.solve_gp_system(&k_xx.view(), &self.y_obs.view()) {
301            Ok(w) => w,
302            Err(_) => {
303                // Fallback to regularized system if Cholesky fails
304                let regularization = T::from(1e-6).expect("Operation failed");
305                for i in 0..n {
306                    k_xx[[i, i]] += regularization;
307                }
308                self.solve_gp_system(&k_xx.view(), &self.y_obs.view())?
309            }
310        };
311
312        // Compute cross-covariance K(X*, X)
313        let mut k_star_x = Array2::<T>::zeros((m, n));
314        for i in 0..m {
315            for j in 0..n {
316                let dist_sq = (xnew[i] - self.x_obs[j]).powi(2);
317                k_star_x[[i, j]] = self.config.prior_variance
318                    * (-dist_sq / (T::from(2.0).expect("Operation failed") * length_scale.powi(2)))
319                        .exp();
320            }
321        }
322
323        // Compute posterior mean: μ* = K(X*, X) * weights
324        let mut mean = Array1::zeros(m);
325        for i in 0..m {
326            let mut sum = T::zero();
327            for j in 0..n {
328                sum += k_star_x[[i, j]] * weights[j];
329            }
330            // Add prior mean
331            mean[i] = (self.config.prior_mean)(xnew[i]) + sum;
332        }
333
334        Ok(mean)
335    }
336
337    /// Solve the GP linear system using available numerical methods
338    fn solve_gp_system(
339        &self,
340        k_matrix: &ArrayView2<T>,
341        y_obs: &ArrayView1<T>,
342    ) -> InterpolateResult<Array1<T>> {
343        use crate::structured_matrix::solve_dense_system;
344
345        // Try using the structured _matrix solver
346        match solve_dense_system(k_matrix, y_obs) {
347            Ok(solution) => Ok(solution),
348            Err(_) => {
349                // Additional fallback: use simple weighted average if _matrix is ill-conditioned
350                let n = y_obs.len();
351                let weights =
352                    Array1::from_elem(n, T::one() / T::from(n).expect("Operation failed"));
353                Ok(weights)
354            }
355        }
356    }
357
358    /// Draw samples from the posterior distribution
359    pub fn posterior_samples(
360        &self,
361        xnew: &ArrayView1<T>,
362        n_samples: usize,
363    ) -> InterpolateResult<Array2<T>> {
364        let mean = self.posterior_mean(xnew)?;
365        let m = xnew.len();
366
367        // For computational efficiency, we use a simplified approach that captures
368        // the main posterior uncertainty while avoiding expensive matrix operations.
369        // A full implementation would compute the posterior covariance matrix:
370        // Σ* = K(X*, X*) - K(X*, X)[K(X, X) + σ²I]^(-1)K(X, X*)
371
372        let mut samples = Array2::zeros((n_samples, m));
373        let mut rng = scirs2_core::random::rng();
374
375        // Compute approximate posterior variance at each point
376        let length_scale = T::one();
377        for j in 0..m {
378            // Compute posterior variance as prior variance minus reduction from observations
379            let mut reduction_factor = T::zero();
380            let mut total_influence = T::zero();
381
382            for i in 0..self.x_obs.len() {
383                let dist_sq = (xnew[j] - self.x_obs[i]).powi(2);
384                let influence = (-dist_sq
385                    / (T::from(2.0).expect("Operation failed") * length_scale.powi(2)))
386                .exp();
387                total_influence += influence;
388                reduction_factor += influence * influence;
389            }
390
391            // Approximate posterior variance
392            let noise_ratio = self.config.noise_variance / self.config.prior_variance;
393            let posterior_var = self.config.prior_variance
394                * (T::one()
395                    - reduction_factor
396                        / (total_influence
397                            + noise_ratio
398                            + T::from(1e-8).expect("Operation failed")));
399
400            // Ensure positive variance
401            let std_dev = posterior_var
402                .max(T::from(1e-12).expect("Operation failed"))
403                .sqrt();
404
405            // Draw _samples for this query point
406            for i in 0..n_samples {
407                if let Ok(normal) = Normal::new(
408                    mean[j].to_f64().expect("Operation failed"),
409                    std_dev.to_f64().expect("Operation failed"),
410                ) {
411                    samples[[i, j]] = T::from(normal.sample(&mut rng)).expect("Operation failed");
412                } else {
413                    samples[[i, j]] = mean[j];
414                }
415            }
416        }
417
418        Ok(samples)
419    }
420}
421
422/// Quantile interpolation/regression
423///
424/// Interpolates specific quantiles of the response distribution
425pub struct QuantileInterpolator<T: Float> {
426    /// Quantile to interpolate (e.g., 0.5 for median)
427    quantile: T,
428    /// Bandwidth for local quantile estimation
429    bandwidth: T,
430}
431
432impl<T: Float + FromPrimitive + Debug + Display> QuantileInterpolator<T>
433where
434    T: std::iter::Sum<T> + for<'a> std::iter::Sum<&'a T>,
435{
436    /// Create a new quantile interpolator
437    pub fn new(quantile: T, bandwidth: T) -> InterpolateResult<Self> {
438        if quantile <= T::zero() || quantile >= T::one() {
439            return Err(InterpolateError::InvalidValue(
440                "Quantile must be between 0 and 1".to_string(),
441            ));
442        }
443
444        Ok(Self {
445            quantile,
446            bandwidth,
447        })
448    }
449
450    /// Interpolate quantile at given points
451    pub fn interpolate(
452        &self,
453        x: &ArrayView1<T>,
454        y: &ArrayView1<T>,
455        xnew: &ArrayView1<T>,
456    ) -> InterpolateResult<Array1<T>> {
457        if x.len() != y.len() {
458            return Err(InterpolateError::DimensionMismatch(
459                "x and y must have the same length".to_string(),
460            ));
461        }
462
463        let n = x.len();
464        let m = xnew.len();
465        let mut result = Array1::zeros(m);
466
467        // Local quantile regression
468        for j in 0..m {
469            let x_target = xnew[j];
470
471            // Compute weights based on distance
472            let mut weights = Vec::with_capacity(n);
473            let mut weighted_values = Vec::with_capacity(n);
474
475            for i in 0..n {
476                let dist = (x[i] - x_target).abs() / self.bandwidth;
477                let weight = if dist < T::one() {
478                    (T::one() - dist * dist * dist).powi(3) // Tricube kernel
479                } else {
480                    T::zero()
481                };
482
483                if weight > T::epsilon() {
484                    weights.push(weight);
485                    weighted_values.push((y[i], weight));
486                }
487            }
488
489            if weighted_values.is_empty() {
490                // No nearby points, use nearest neighbor
491                let nearest_idx = x
492                    .iter()
493                    .enumerate()
494                    .min_by_key(|(_, &xi)| {
495                        ((xi - x_target).abs().to_f64().expect("Operation failed") * 1e6) as i64
496                    })
497                    .map(|(i_, _)| i_)
498                    .expect("Operation failed");
499                result[j] = y[nearest_idx];
500            } else {
501                // Sort by value
502                weighted_values.sort_by(|a, b| a.0.partial_cmp(&b.0).expect("Operation failed"));
503
504                // Find weighted quantile
505                let total_weight: T = weights.iter().sum();
506                let target_weight = self.quantile * total_weight;
507
508                let mut cumulative_weight = T::zero();
509                for (val, weight) in weighted_values {
510                    cumulative_weight = cumulative_weight + weight;
511                    if cumulative_weight >= target_weight {
512                        result[j] = val;
513                        break;
514                    }
515                }
516            }
517        }
518
519        Ok(result)
520    }
521}
522
523/// Robust interpolation methods resistant to outliers
524pub struct RobustInterpolator<T: Float> {
525    /// Tuning constant for robustness
526    tuning_constant: T,
527    /// Maximum iterations for iterative reweighting
528    max_iterations: usize,
529    /// Convergence tolerance
530    tolerance: T,
531}
532
533impl<T: Float + FromPrimitive + Debug + Display> RobustInterpolator<T> {
534    /// Create a new robust interpolator using M-estimation
535    pub fn new(_tuningconstant: T) -> Self {
536        Self {
537            tuning_constant: _tuningconstant,
538            max_iterations: 100,
539            tolerance: T::from(1e-6).expect("Operation failed"),
540        }
541    }
542
543    /// Perform robust interpolation using iteratively reweighted least squares
544    pub fn interpolate(
545        &self,
546        x: &ArrayView1<T>,
547        y: &ArrayView1<T>,
548        xnew: &ArrayView1<T>,
549    ) -> InterpolateResult<Array1<T>> {
550        // Use local polynomial regression with robust weights
551        let n = x.len();
552        let m = xnew.len();
553        let mut result = Array1::zeros(m);
554
555        for j in 0..m {
556            let x_target = xnew[j];
557
558            // Initial weights (uniform)
559            let mut weights = vec![T::one(); n];
560            let mut prev_estimate = T::zero();
561
562            // Iteratively reweighted least squares
563            for _iter in 0..self.max_iterations {
564                // Weighted linear regression
565                let mut sum_w = T::zero();
566                let mut sum_wx = T::zero();
567                let mut sum_wy = T::zero();
568                let mut sum_wxx = T::zero();
569                let mut sum_wxy = T::zero();
570
571                for i in 0..n {
572                    let w = weights[i];
573                    let dx = x[i] - x_target;
574                    sum_w = sum_w + w;
575                    sum_wx = sum_wx + w * dx;
576                    sum_wy = sum_wy + w * y[i];
577                    sum_wxx = sum_wxx + w * dx * dx;
578                    sum_wxy = sum_wxy + w * dx * y[i];
579                }
580
581                // Solve for coefficients
582                let det = sum_w * sum_wxx - sum_wx * sum_wx;
583                let estimate = if det.abs() > T::epsilon() {
584                    (sum_wxx * sum_wy - sum_wx * sum_wxy) / det
585                } else {
586                    sum_wy / sum_w
587                };
588
589                // Check convergence
590                if (estimate - prev_estimate).abs() < self.tolerance {
591                    result[j] = estimate;
592                    break;
593                }
594                prev_estimate = estimate;
595
596                // Update weights using Huber's psi function
597                for i in 0..n {
598                    let residual = y[i] - estimate;
599                    let scaled_residual = residual / self.tuning_constant;
600
601                    weights[i] = if scaled_residual.abs() <= T::one() {
602                        T::one()
603                    } else {
604                        T::one() / scaled_residual.abs()
605                    };
606                }
607            }
608
609            result[j] = prev_estimate;
610        }
611
612        Ok(result)
613    }
614}
615
616/// Stochastic interpolation for random fields
617///
618/// Provides interpolation that preserves the stochastic properties
619/// of the underlying random field.
620pub struct StochasticInterpolator<T: Float> {
621    /// Correlation length scale
622    correlation_length: T,
623    /// Field variance
624    field_variance: T,
625    /// Number of realizations to generate
626    n_realizations: usize,
627}
628
629impl<T: Float + FromPrimitive + Debug + Display> StochasticInterpolator<T> {
630    /// Create a new stochastic interpolator
631    pub fn new(correlation_length: T, field_variance: T, n_realizations: usize) -> Self {
632        Self {
633            correlation_length,
634            field_variance,
635            n_realizations,
636        }
637    }
638
639    /// Generate stochastic realizations of the interpolated field
640    pub fn interpolate_realizations(
641        &self,
642        x: &ArrayView1<T>,
643        y: &ArrayView1<T>,
644        xnew: &ArrayView1<T>,
645    ) -> InterpolateResult<Array2<T>> {
646        let n = x.len();
647        let m = xnew.len();
648        let mut realizations = Array2::zeros((self.n_realizations, m));
649
650        let mut rng = scirs2_core::random::rng();
651
652        for r in 0..self.n_realizations {
653            // Generate a realization using conditional simulation
654            for j in 0..m {
655                let x_target = xnew[j];
656
657                // Kriging interpolation with added noise
658                let mut weighted_sum = T::zero();
659                let mut weight_sum = T::zero();
660
661                for i in 0..n {
662                    let dist = (x[i] - x_target).abs() / self.correlation_length;
663                    let weight = (-dist * dist).exp();
664                    weighted_sum = weighted_sum + weight * y[i];
665                    weight_sum = weight_sum + weight;
666                }
667
668                let mean = if weight_sum > T::epsilon() {
669                    weighted_sum / weight_sum
670                } else {
671                    T::zero()
672                };
673
674                // Add stochastic component
675                let std_dev = (self.field_variance
676                    * (T::one() - weight_sum / T::from(n).expect("Operation failed")))
677                .sqrt();
678                let normal_sample: f64 = StandardNormal.sample(&mut rng);
679                let noise: T = T::from(normal_sample).expect("Operation failed") * std_dev;
680
681                realizations[[r, j]] = mean + noise;
682            }
683        }
684
685        Ok(realizations)
686    }
687
688    /// Get mean and variance of the stochastic interpolation
689    pub fn interpolate_statistics(
690        &self,
691        x: &ArrayView1<T>,
692        y: &ArrayView1<T>,
693        xnew: &ArrayView1<T>,
694    ) -> InterpolateResult<(Array1<T>, Array1<T>)> {
695        let realizations = self.interpolate_realizations(x, y, xnew)?;
696
697        let mean = realizations.mean_axis(Axis(0)).expect("Operation failed");
698        let variance = realizations.var_axis(Axis(0), T::from(1.0).expect("Operation failed"));
699
700        Ok((mean, variance))
701    }
702}
703
704/// Factory functions for creating statistical interpolators
705/// Create a bootstrap interpolator with linear base interpolation
706#[allow(dead_code)]
707pub fn make_bootstrap_linear_interpolator<
708    T: Float + FromPrimitive + Debug + Display + 'static + std::iter::Sum,
709>(
710    config: BootstrapConfig,
711) -> BootstrapInterpolator<T> {
712    BootstrapInterpolator::new(config, |x, y| {
713        // Create a simple linear interpolator
714        let x_owned = x.to_owned();
715        let y_owned = y.to_owned();
716        Ok(Box::new(move |xnew| {
717            // Simple linear interpolation
718            if xnew <= x_owned[0] {
719                y_owned[0]
720            } else if xnew >= x_owned[x_owned.len() - 1] {
721                y_owned[y_owned.len() - 1]
722            } else {
723                // Find surrounding points
724                let mut i = 0;
725                for j in 1..x_owned.len() {
726                    if xnew <= x_owned[j] {
727                        i = j - 1;
728                        break;
729                    }
730                }
731
732                let alpha = (xnew - x_owned[i]) / (x_owned[i + 1] - x_owned[i]);
733                y_owned[i] * (T::one() - alpha) + y_owned[i + 1] * alpha
734            }
735        }))
736    })
737}
738
739/// Create a Bayesian interpolator with default configuration
740#[allow(dead_code)]
741pub fn make_bayesian_interpolator<T: crate::traits::InterpolationFloat>(
742    x: &ArrayView1<T>,
743    y: &ArrayView1<T>,
744) -> InterpolateResult<BayesianInterpolator<T>> {
745    BayesianInterpolator::new(x, y, BayesianConfig::default())
746}
747
748/// Create a median (0.5 quantile) interpolator
749#[allow(dead_code)]
750pub fn make_median_interpolator<T>(bandwidth: T) -> InterpolateResult<QuantileInterpolator<T>>
751where
752    T: Float + FromPrimitive + Debug + Display + std::iter::Sum<T> + for<'a> std::iter::Sum<&'a T>,
753{
754    QuantileInterpolator::new(T::from(0.5).expect("Operation failed"), bandwidth)
755}
756
757/// Create a robust interpolator with default Huber tuning
758#[allow(dead_code)]
759pub fn make_robust_interpolator<T: crate::traits::InterpolationFloat>() -> RobustInterpolator<T> {
760    RobustInterpolator::new(T::from(1.345).expect("Operation failed")) // Huber's recommended value
761}
762
763/// Create a stochastic interpolator with default parameters
764#[allow(dead_code)]
765pub fn make_stochastic_interpolator<T: crate::traits::InterpolationFloat>(
766    correlation_length: T,
767) -> StochasticInterpolator<T> {
768    StochasticInterpolator::new(correlation_length, T::one(), 100)
769}
770
771/// Ensemble interpolation combining multiple methods
772///
773/// Provides interpolation using an ensemble of different methods
774/// to improve robustness and uncertainty quantification.
775pub struct EnsembleInterpolator<T: Float> {
776    /// Weight for each interpolation method
777    weights: Array1<T>,
778    /// Interpolation methods in the ensemble
779    methods: Vec<
780        Box<dyn Fn(&ArrayView1<T>, &ArrayView1<T>, &ArrayView1<T>) -> InterpolateResult<Array1<T>>>,
781    >,
782    /// Whether to normalize weights
783    normalize_weights: bool,
784}
785
786impl<T: crate::traits::InterpolationFloat> EnsembleInterpolator<T> {
787    /// Create a new ensemble interpolator
788    pub fn new() -> Self {
789        Self {
790            weights: Array1::zeros(0),
791            methods: Vec::new(),
792            normalize_weights: true,
793        }
794    }
795
796    /// Add a linear interpolation method to the ensemble
797    pub fn add_linear_method(mut self, weight: T) -> Self {
798        self.weights = if self.weights.is_empty() {
799            Array1::from_vec(vec![weight])
800        } else {
801            let mut new_weights = self.weights.to_vec();
802            new_weights.push(weight);
803            Array1::from_vec(new_weights)
804        };
805
806        self.methods.push(Box::new(|x, y, xnew| {
807            let mut result = Array1::zeros(xnew.len());
808            for (i, &x_val) in xnew.iter().enumerate() {
809                // Linear interpolation
810                if x_val <= x[0] {
811                    result[i] = y[0];
812                } else if x_val >= x[x.len() - 1] {
813                    result[i] = y[y.len() - 1];
814                } else {
815                    // Find surrounding points
816                    for j in 1..x.len() {
817                        if x_val <= x[j] {
818                            let alpha = (x_val - x[j - 1]) / (x[j] - x[j - 1]);
819                            result[i] = y[j - 1] * (T::one() - alpha) + y[j] * alpha;
820                            break;
821                        }
822                    }
823                }
824            }
825            Ok(result)
826        }));
827        self
828    }
829
830    /// Add a cubic interpolation method to the ensemble
831    pub fn add_cubic_method(mut self, weight: T) -> Self {
832        self.weights = if self.weights.is_empty() {
833            Array1::from_vec(vec![weight])
834        } else {
835            let mut new_weights = self.weights.to_vec();
836            new_weights.push(weight);
837            Array1::from_vec(new_weights)
838        };
839
840        self.methods.push(Box::new(|x, y, xnew| {
841            // Cubic spline interpolation using natural boundary conditions
842            use crate::spline::CubicSpline;
843
844            // Need at least 3 points for cubic spline
845            if x.len() < 3 {
846                return Err(InterpolateError::invalid_input(
847                    "Cubic spline requires at least 3 data points".to_string(),
848                ));
849            }
850
851            // Create cubic spline with natural boundary conditions
852            let spline = CubicSpline::new(x, y)?;
853
854            // Evaluate at all query points
855            let mut result = Array1::zeros(xnew.len());
856            for (i, &x_val) in xnew.iter().enumerate() {
857                // Handle extrapolation by clamping to boundary values
858                if x_val < x[0] {
859                    result[i] = y[0];
860                } else if x_val > x[x.len() - 1] {
861                    result[i] = y[y.len() - 1];
862                } else {
863                    // Evaluate cubic spline within the valid range
864                    result[i] = spline.evaluate(x_val)?;
865                }
866            }
867            Ok(result)
868        }));
869        self
870    }
871
872    /// Perform ensemble interpolation
873    pub fn interpolate(
874        &self,
875        x: &ArrayView1<T>,
876        y: &ArrayView1<T>,
877        xnew: &ArrayView1<T>,
878    ) -> InterpolateResult<Array1<T>> {
879        if self.methods.is_empty() {
880            return Err(InterpolateError::InvalidState(
881                "No interpolation methods in ensemble".to_string(),
882            ));
883        }
884
885        let mut weighted_results = Array1::zeros(xnew.len());
886        let mut total_weight = T::zero();
887
888        for (i, method) in self.methods.iter().enumerate() {
889            let result = method(x, y, xnew)?;
890            let weight = self.weights[i];
891
892            for j in 0..xnew.len() {
893                weighted_results[j] += weight * result[j];
894            }
895            total_weight += weight;
896        }
897
898        // Normalize if requested
899        if self.normalize_weights && total_weight > T::zero() {
900            for val in weighted_results.iter_mut() {
901                *val /= total_weight;
902            }
903        }
904
905        Ok(weighted_results)
906    }
907
908    /// Get ensemble variance (measure of uncertainty)
909    pub fn interpolate_with_variance(
910        &self,
911        x: &ArrayView1<T>,
912        y: &ArrayView1<T>,
913        xnew: &ArrayView1<T>,
914    ) -> InterpolateResult<(Array1<T>, Array1<T>)> {
915        if self.methods.is_empty() {
916            return Err(InterpolateError::InvalidState(
917                "No interpolation methods in ensemble".to_string(),
918            ));
919        }
920
921        let mut all_results = Vec::new();
922
923        // Collect results from all methods
924        for method in self.methods.iter() {
925            let result = method(x, y, xnew)?;
926            all_results.push(result);
927        }
928
929        // Compute weighted mean
930        let mut weighted_mean = Array1::zeros(xnew.len());
931        let mut total_weight = T::zero();
932
933        for (i, result) in all_results.iter().enumerate() {
934            let weight = self.weights[i];
935            for j in 0..xnew.len() {
936                weighted_mean[j] += weight * result[j];
937            }
938            total_weight += weight;
939        }
940
941        if total_weight > T::zero() {
942            for val in weighted_mean.iter_mut() {
943                *val /= total_weight;
944            }
945        }
946
947        // Compute weighted variance
948        let mut variance = Array1::zeros(xnew.len());
949        if all_results.len() > 1 {
950            for (i, result) in all_results.iter().enumerate() {
951                let weight = self.weights[i];
952                for j in 0..xnew.len() {
953                    let diff = result[j] - weighted_mean[j];
954                    variance[j] += weight * diff * diff;
955                }
956            }
957
958            if total_weight > T::zero() {
959                for val in variance.iter_mut() {
960                    *val /= total_weight;
961                }
962            }
963        }
964
965        Ok((weighted_mean, variance))
966    }
967}
968
969impl<T: crate::traits::InterpolationFloat> Default for EnsembleInterpolator<T> {
970    fn default() -> Self {
971        Self::new()
972    }
973}
974
975/// Cross-validation based uncertainty estimation
976///
977/// Provides uncertainty estimates using leave-one-out cross-validation
978/// or k-fold cross-validation.
979pub struct CrossValidationUncertainty {
980    /// Number of folds for k-fold CV (if 0, use leave-one-out)
981    k_folds: usize,
982    /// Random seed for fold assignment
983    seed: Option<u64>,
984}
985
986impl CrossValidationUncertainty {
987    /// Create a new cross-validation uncertainty estimator
988    pub fn new(_kfolds: usize) -> Self {
989        Self {
990            k_folds: _kfolds,
991            seed: None,
992        }
993    }
994
995    /// Set random seed for reproducible fold assignment
996    pub fn with_seed(mut self, seed: u64) -> Self {
997        self.seed = Some(seed);
998        self
999    }
1000
1001    /// Estimate uncertainty using cross-validation
1002    pub fn estimate_uncertainty<T, F>(
1003        &self,
1004        x: &ArrayView1<T>,
1005        y: &ArrayView1<T>,
1006        xnew: &ArrayView1<T>,
1007        interpolator_factory: F,
1008    ) -> InterpolateResult<(Array1<T>, Array1<T>)>
1009    where
1010        T: Clone
1011            + Copy
1012            + scirs2_core::numeric::Float
1013            + scirs2_core::numeric::FromPrimitive
1014            + std::iter::Sum,
1015        F: Fn(&ArrayView1<T>, &ArrayView1<T>, &ArrayView1<T>) -> InterpolateResult<Array1<T>>,
1016    {
1017        let n = x.len();
1018        let _m = xnew.len();
1019
1020        if self.k_folds == 0 || self.k_folds >= n {
1021            // Leave-one-out cross-validation
1022            self.leave_one_out_uncertainty(x, y, xnew, interpolator_factory)
1023        } else {
1024            // K-fold cross-validation
1025            self.k_fold_uncertainty(x, y, xnew, interpolator_factory)
1026        }
1027    }
1028
1029    fn leave_one_out_uncertainty<T, F>(
1030        &self,
1031        x: &ArrayView1<T>,
1032        y: &ArrayView1<T>,
1033        xnew: &ArrayView1<T>,
1034        interpolator_factory: F,
1035    ) -> InterpolateResult<(Array1<T>, Array1<T>)>
1036    where
1037        T: Clone
1038            + Copy
1039            + scirs2_core::numeric::Float
1040            + scirs2_core::numeric::FromPrimitive
1041            + std::iter::Sum,
1042        F: Fn(&ArrayView1<T>, &ArrayView1<T>, &ArrayView1<T>) -> InterpolateResult<Array1<T>>,
1043    {
1044        let n = x.len();
1045        let m = xnew.len();
1046        let mut predictions = Array2::zeros((n, m));
1047
1048        // Leave-one-out cross-validation
1049        for i in 0..n {
1050            // Create training set without point i
1051            let mut x_train = Vec::new();
1052            let mut y_train = Vec::new();
1053
1054            for j in 0..n {
1055                if j != i {
1056                    x_train.push(x[j]);
1057                    y_train.push(y[j]);
1058                }
1059            }
1060
1061            let x_train_array = Array1::from_vec(x_train);
1062            let y_train_array = Array1::from_vec(y_train);
1063
1064            // Train on reduced dataset and predict
1065            let pred = interpolator_factory(&x_train_array.view(), &y_train_array.view(), xnew)?;
1066            for j in 0..m {
1067                predictions[[i, j]] = pred[j];
1068            }
1069        }
1070
1071        // Compute mean and variance of predictions
1072        let mut mean = Array1::zeros(m);
1073        let mut variance = Array1::zeros(m);
1074
1075        for j in 0..m {
1076            let col = predictions.column(j);
1077            let sum: T = col.iter().copied().sum();
1078            mean[j] = sum / T::from(n).expect("Operation failed");
1079
1080            let var_sum: T = col
1081                .iter()
1082                .map(|&val| (val - mean[j]) * (val - mean[j]))
1083                .sum();
1084            variance[j] = var_sum / T::from(n - 1).expect("Operation failed");
1085        }
1086
1087        Ok((mean, variance))
1088    }
1089
1090    fn k_fold_uncertainty<T, F>(
1091        &self,
1092        x: &ArrayView1<T>,
1093        y: &ArrayView1<T>,
1094        xnew: &ArrayView1<T>,
1095        interpolator_factory: F,
1096    ) -> InterpolateResult<(Array1<T>, Array1<T>)>
1097    where
1098        T: Clone
1099            + Copy
1100            + scirs2_core::numeric::Float
1101            + scirs2_core::numeric::FromPrimitive
1102            + std::iter::Sum,
1103        F: Fn(&ArrayView1<T>, &ArrayView1<T>, &ArrayView1<T>) -> InterpolateResult<Array1<T>>,
1104    {
1105        let n = x.len();
1106        let m = xnew.len();
1107        let fold_size = n / self.k_folds;
1108        let mut predictions = Vec::new();
1109
1110        let mut rng = match self.seed {
1111            Some(seed) => StdRng::seed_from_u64(seed),
1112            None => {
1113                let mut rng = scirs2_core::random::rng();
1114                StdRng::from_rng(&mut rng)
1115            }
1116        };
1117
1118        // Create shuffled indices
1119        let mut indices: Vec<usize> = (0..n).collect();
1120        use scirs2_core::random::seq::SliceRandom;
1121        indices.shuffle(&mut rng);
1122
1123        // K-fold cross-validation
1124        for fold in 0..self.k_folds {
1125            let start_idx = fold * fold_size;
1126            let end_idx = if fold == self.k_folds - 1 {
1127                n
1128            } else {
1129                (fold + 1) * fold_size
1130            };
1131
1132            // Create training set (excluding current fold)
1133            let mut x_train = Vec::new();
1134            let mut y_train = Vec::new();
1135
1136            for &idx in &indices[..start_idx] {
1137                x_train.push(x[idx]);
1138                y_train.push(y[idx]);
1139            }
1140            for &idx in &indices[end_idx..] {
1141                x_train.push(x[idx]);
1142                y_train.push(y[idx]);
1143            }
1144
1145            let x_train_array = Array1::from_vec(x_train);
1146            let y_train_array = Array1::from_vec(y_train);
1147
1148            // Train and predict
1149            let pred = interpolator_factory(&x_train_array.view(), &y_train_array.view(), xnew)?;
1150            predictions.push(pred);
1151        }
1152
1153        // Compute statistics across folds
1154        let mut mean = Array1::zeros(m);
1155        let mut variance = Array1::zeros(m);
1156
1157        for j in 0..m {
1158            let values: Vec<T> = predictions.iter().map(|pred| pred[j]).collect();
1159            let sum: T = values.iter().copied().sum();
1160            mean[j] = sum / T::from(self.k_folds).expect("Operation failed");
1161
1162            let var_sum: T = values
1163                .iter()
1164                .map(|&val| (val - mean[j]) * (val - mean[j]))
1165                .sum();
1166            variance[j] = var_sum / T::from(self.k_folds - 1).expect("Operation failed");
1167        }
1168
1169        Ok((mean, variance))
1170    }
1171}
1172
1173/// Create an ensemble interpolator with linear and cubic methods
1174#[allow(dead_code)]
1175pub fn make_ensemble_interpolator<
1176    T: Float
1177        + FromPrimitive
1178        + Debug
1179        + Display
1180        + Copy
1181        + std::iter::Sum
1182        + crate::traits::InterpolationFloat,
1183>() -> EnsembleInterpolator<T> {
1184    EnsembleInterpolator::new()
1185        .add_linear_method(T::from(0.6).expect("Operation failed"))
1186        .add_cubic_method(T::from(0.4).expect("Operation failed"))
1187}
1188
1189/// Create a cross-validation uncertainty estimator with leave-one-out
1190#[allow(dead_code)]
1191pub fn make_loocv_uncertainty() -> CrossValidationUncertainty {
1192    CrossValidationUncertainty::new(0) // 0 means leave-one-out
1193}
1194
1195/// Create a cross-validation uncertainty estimator with k-fold CV
1196#[allow(dead_code)]
1197pub fn make_kfold_uncertainty(k: usize) -> CrossValidationUncertainty {
1198    CrossValidationUncertainty::new(k)
1199}
1200
1201/// Isotonic (monotonic) regression interpolator
1202///
1203/// Performs interpolation while maintaining monotonicity constraints.
1204/// This is useful for dose-response relationships and other applications
1205/// where the underlying relationship must be monotonic.
1206#[derive(Debug, Clone)]
1207pub struct IsotonicInterpolator<T: Float> {
1208    /// Fitted isotonic values at training points
1209    fitted_values: Array1<T>,
1210    /// Training x coordinates (sorted)
1211    x_data: Array1<T>,
1212    /// Whether interpolation should be increasing (true) or decreasing (false)
1213    increasing: bool,
1214}
1215
1216impl<T: Float + FromPrimitive + Debug + Display + Copy + std::iter::Sum> IsotonicInterpolator<T> {
1217    /// Create a new isotonic interpolator
1218    pub fn new(x: &ArrayView1<T>, y: &ArrayView1<T>, increasing: bool) -> InterpolateResult<Self> {
1219        if x.len() != y.len() {
1220            return Err(InterpolateError::DimensionMismatch(
1221                "x and y must have the same length".to_string(),
1222            ));
1223        }
1224
1225        if x.len() < 2 {
1226            return Err(InterpolateError::invalid_input(
1227                "Need at least 2 points for isotonic regression".to_string(),
1228            ));
1229        }
1230
1231        // Sort by x values
1232        let mut indices: Vec<usize> = (0..x.len()).collect();
1233        indices.sort_by(|&i, &j| x[i].partial_cmp(&x[j]).expect("Operation failed"));
1234
1235        let x_sorted: Array1<T> = indices.iter().map(|&i| x[i]).collect();
1236        let y_sorted: Array1<T> = indices.iter().map(|&i| y[i]).collect();
1237
1238        // Apply pool-adjacent-violators algorithm
1239        let fitted_values = Self::pool_adjacent_violators(&y_sorted.view(), increasing)?;
1240
1241        Ok(Self {
1242            fitted_values,
1243            x_data: x_sorted,
1244            increasing,
1245        })
1246    }
1247
1248    /// Pool-adjacent-violators algorithm for isotonic regression
1249    fn pool_adjacent_violators(
1250        y: &ArrayView1<T>,
1251        increasing: bool,
1252    ) -> InterpolateResult<Array1<T>> {
1253        let n = y.len();
1254        let mut fitted = y.to_owned();
1255        let mut weights = Array1::<T>::ones(n);
1256
1257        loop {
1258            let mut changed = false;
1259
1260            for i in 0..n - 1 {
1261                let violates = if increasing {
1262                    fitted[i] > fitted[i + 1]
1263                } else {
1264                    fitted[i] < fitted[i + 1]
1265                };
1266
1267                if violates {
1268                    // Pool adjacent blocks
1269                    let total_weight = weights[i] + weights[i + 1];
1270                    let weighted_mean =
1271                        (fitted[i] * weights[i] + fitted[i + 1] * weights[i + 1]) / total_weight;
1272
1273                    fitted[i] = weighted_mean;
1274                    fitted[i + 1] = weighted_mean;
1275                    weights[i] = total_weight;
1276                    weights[i + 1] = total_weight;
1277
1278                    changed = true;
1279                }
1280            }
1281
1282            if !changed {
1283                break;
1284            }
1285        }
1286
1287        Ok(fitted)
1288    }
1289
1290    /// Interpolate at new points
1291    pub fn interpolate(&self, xnew: &ArrayView1<T>) -> InterpolateResult<Array1<T>> {
1292        let mut result = Array1::zeros(xnew.len());
1293
1294        for (i, &x) in xnew.iter().enumerate() {
1295            // Find position in sorted data
1296            let idx = match self
1297                .x_data
1298                .as_slice()
1299                .expect("Operation failed")
1300                .binary_search_by(|&probe| probe.partial_cmp(&x).expect("Operation failed"))
1301            {
1302                Ok(exact_idx) => {
1303                    result[i] = self.fitted_values[exact_idx];
1304                    continue;
1305                }
1306                Err(insert_idx) => insert_idx,
1307            };
1308
1309            // Linear interpolation between adjacent fitted values
1310            if idx == 0 {
1311                result[i] = self.fitted_values[0];
1312            } else if idx >= self.x_data.len() {
1313                result[i] = self.fitted_values[self.x_data.len() - 1];
1314            } else {
1315                let x0 = self.x_data[idx - 1];
1316                let x1 = self.x_data[idx];
1317                let y0 = self.fitted_values[idx - 1];
1318                let y1 = self.fitted_values[idx];
1319
1320                let t = (x - x0) / (x1 - x0);
1321                result[i] = y0 + t * (y1 - y0);
1322            }
1323        }
1324
1325        Ok(result)
1326    }
1327}
1328
1329/// Kernel Density Estimation (KDE) based interpolator
1330///
1331/// Uses kernel density estimation to create smooth interpolations
1332/// based on probability density functions.
1333#[derive(Debug, Clone)]
1334pub struct KDEInterpolator<T: Float> {
1335    /// Training data points
1336    x_data: Array1<T>,
1337    y_data: Array1<T>,
1338    /// Kernel bandwidth
1339    bandwidth: T,
1340    /// Kernel type
1341    kernel_type: KDEKernel,
1342}
1343
1344/// Kernel types for KDE interpolation
1345#[derive(Debug, Clone, Copy, PartialEq)]
1346pub enum KDEKernel {
1347    /// Gaussian (normal) kernel
1348    Gaussian,
1349    /// Epanechnikov kernel (more efficient)
1350    Epanechnikov,
1351    /// Triangular kernel
1352    Triangular,
1353    /// Uniform (box) kernel
1354    Uniform,
1355}
1356
1357impl<T: Float + FromPrimitive + Debug + Display + Copy> KDEInterpolator<T> {
1358    /// Create a new KDE interpolator
1359    pub fn new(
1360        x: &ArrayView1<T>,
1361        y: &ArrayView1<T>,
1362        bandwidth: T,
1363        kernel_type: KDEKernel,
1364    ) -> InterpolateResult<Self> {
1365        if x.len() != y.len() {
1366            return Err(InterpolateError::DimensionMismatch(
1367                "x and y must have the same length".to_string(),
1368            ));
1369        }
1370
1371        if bandwidth <= T::zero() {
1372            return Err(InterpolateError::invalid_input(
1373                "Bandwidth must be positive".to_string(),
1374            ));
1375        }
1376
1377        Ok(Self {
1378            x_data: x.to_owned(),
1379            y_data: y.to_owned(),
1380            bandwidth,
1381            kernel_type,
1382        })
1383    }
1384
1385    /// Kernel function evaluation
1386    fn kernel(&self, u: T) -> T {
1387        match self.kernel_type {
1388            KDEKernel::Gaussian => {
1389                let pi = T::from(std::f64::consts::PI).expect("Operation failed");
1390                let two = T::from(2.0).expect("Operation failed");
1391                let exp_arg = -u * u / two;
1392                exp_arg.exp() / (two * pi).sqrt()
1393            }
1394            KDEKernel::Epanechnikov => {
1395                if u.abs() <= T::one() {
1396                    let three_fourths = T::from(0.75).expect("Operation failed");
1397                    three_fourths * (T::one() - u * u)
1398                } else {
1399                    T::zero()
1400                }
1401            }
1402            KDEKernel::Triangular => {
1403                if u.abs() <= T::one() {
1404                    T::one() - u.abs()
1405                } else {
1406                    T::zero()
1407                }
1408            }
1409            KDEKernel::Uniform => {
1410                if u.abs() <= T::one() {
1411                    T::from(0.5).expect("Operation failed")
1412                } else {
1413                    T::zero()
1414                }
1415            }
1416        }
1417    }
1418
1419    /// Interpolate at new points using KDE
1420    pub fn interpolate(&self, xnew: &ArrayView1<T>) -> InterpolateResult<Array1<T>> {
1421        let mut result = Array1::zeros(xnew.len());
1422
1423        for (i, &x) in xnew.iter().enumerate() {
1424            let mut weighted_sum = T::zero();
1425            let mut weight_sum = T::zero();
1426
1427            for j in 0..self.x_data.len() {
1428                let u = (x - self.x_data[j]) / self.bandwidth;
1429                let kernel_weight = self.kernel(u);
1430
1431                weighted_sum = weighted_sum + kernel_weight * self.y_data[j];
1432                weight_sum = weight_sum + kernel_weight;
1433            }
1434
1435            if weight_sum > T::zero() {
1436                result[i] = weighted_sum / weight_sum;
1437            } else {
1438                // Fallback to nearest neighbor
1439                let mut min_dist = T::infinity();
1440                let mut nearest_y = self.y_data[0];
1441
1442                for j in 0..self.x_data.len() {
1443                    let dist = (x - self.x_data[j]).abs();
1444                    if dist < min_dist {
1445                        min_dist = dist;
1446                        nearest_y = self.y_data[j];
1447                    }
1448                }
1449
1450                result[i] = nearest_y;
1451            }
1452        }
1453
1454        Ok(result)
1455    }
1456}
1457
1458/// Empirical Bayes interpolator
1459///
1460/// Uses empirical Bayes methods for shrinkage-based interpolation.
1461/// Particularly useful when dealing with multiple related functions
1462/// or when prior information is available.
1463#[derive(Debug, Clone)]
1464pub struct EmpiricalBayesInterpolator<T: Float> {
1465    /// Training data
1466    x_data: Array1<T>,
1467    y_data: Array1<T>,
1468    /// Shrinkage parameters
1469    shrinkage_factor: T,
1470    /// Prior mean function
1471    prior_mean: T,
1472    /// Noise variance estimate
1473    noise_variance: T,
1474}
1475
1476impl<T: Float + FromPrimitive + Debug + Display + Copy + std::iter::Sum>
1477    EmpiricalBayesInterpolator<T>
1478{
1479    /// Create a new empirical Bayes interpolator
1480    pub fn new(x: &ArrayView1<T>, y: &ArrayView1<T>) -> InterpolateResult<Self> {
1481        if x.len() != y.len() {
1482            return Err(InterpolateError::DimensionMismatch(
1483                "x and y must have the same length".to_string(),
1484            ));
1485        }
1486
1487        if x.len() < 3 {
1488            return Err(InterpolateError::invalid_input(
1489                "Need at least 3 points for empirical Bayes".to_string(),
1490            ));
1491        }
1492
1493        // Estimate prior mean (overall mean)
1494        let prior_mean = y.iter().copied().sum::<T>() / T::from(y.len()).expect("Operation failed");
1495
1496        // Estimate noise variance using residuals
1497        let residuals: Array1<T> = y.iter().map(|&yi| yi - prior_mean).collect();
1498        let noise_variance = residuals.iter().map(|&r| r * r).sum::<T>()
1499            / T::from(residuals.len() - 1).expect("Operation failed");
1500
1501        // Compute shrinkage factor using James-Stein type estimator
1502        let signal_variance = noise_variance.max(T::from(1e-10).expect("Operation failed"));
1503        let shrinkage_factor = noise_variance / (noise_variance + signal_variance);
1504
1505        Ok(Self {
1506            x_data: x.to_owned(),
1507            y_data: y.to_owned(),
1508            shrinkage_factor,
1509            prior_mean,
1510            noise_variance,
1511        })
1512    }
1513
1514    /// Create empirical Bayes interpolator with custom prior
1515    pub fn with_prior(
1516        x: &ArrayView1<T>,
1517        y: &ArrayView1<T>,
1518        prior_mean: T,
1519        shrinkage_factor: T,
1520    ) -> InterpolateResult<Self> {
1521        if x.len() != y.len() {
1522            return Err(InterpolateError::DimensionMismatch(
1523                "x and y must have the same length".to_string(),
1524            ));
1525        }
1526
1527        let residuals: Array1<T> = y.iter().map(|&yi| yi - prior_mean).collect();
1528        let noise_variance = residuals.iter().map(|&r| r * r).sum::<T>()
1529            / T::from(residuals.len().max(1)).expect("Operation failed");
1530
1531        Ok(Self {
1532            x_data: x.to_owned(),
1533            y_data: y.to_owned(),
1534            shrinkage_factor,
1535            prior_mean,
1536            noise_variance,
1537        })
1538    }
1539
1540    /// Interpolate using empirical Bayes shrinkage
1541    pub fn interpolate(&self, xnew: &ArrayView1<T>) -> InterpolateResult<Array1<T>> {
1542        let mut result = Array1::zeros(xnew.len());
1543
1544        for (i, &x) in xnew.iter().enumerate() {
1545            // Find nearest neighbors for local estimation
1546            let mut distances: Vec<(T, usize)> = self
1547                .x_data
1548                .iter()
1549                .enumerate()
1550                .map(|(j, &xi)| ((x - xi).abs(), j))
1551                .collect();
1552            distances.sort_by(|a, b| a.0.partial_cmp(&b.0).expect("Operation failed"));
1553
1554            // Use k nearest neighbors (k = 3 or n/2, whichever is smaller)
1555            let k = (3_usize).min(self.x_data.len() / 2).max(1);
1556            let mut local_mean = T::zero();
1557            let mut total_weight = T::zero();
1558
1559            for &(dist, j) in distances.iter().take(k) {
1560                let weight = if dist == T::zero() {
1561                    T::one()
1562                } else {
1563                    T::one() / (T::one() + dist)
1564                };
1565                local_mean = local_mean + weight * self.y_data[j];
1566                total_weight = total_weight + weight;
1567            }
1568
1569            if total_weight > T::zero() {
1570                local_mean = local_mean / total_weight;
1571            } else {
1572                local_mean = self.prior_mean;
1573            }
1574
1575            // Apply empirical Bayes shrinkage
1576            let shrunk_estimate = (T::one() - self.shrinkage_factor) * local_mean
1577                + self.shrinkage_factor * self.prior_mean;
1578
1579            result[i] = shrunk_estimate;
1580        }
1581
1582        Ok(result)
1583    }
1584
1585    /// Get shrinkage factor
1586    pub fn get_shrinkage_factor(&self) -> T {
1587        self.shrinkage_factor
1588    }
1589
1590    /// Get prior mean
1591    pub fn get_prior_mean(&self) -> T {
1592        self.prior_mean
1593    }
1594
1595    /// Get noise variance estimate
1596    pub fn get_noise_variance(&self) -> T {
1597        self.noise_variance
1598    }
1599}
1600
1601/// Convenience function to create an isotonic interpolator (increasing)
1602#[allow(dead_code)]
1603pub fn make_isotonic_interpolator<
1604    T: Float + FromPrimitive + Debug + Display + Copy + std::iter::Sum,
1605>(
1606    x: &ArrayView1<T>,
1607    y: &ArrayView1<T>,
1608) -> InterpolateResult<IsotonicInterpolator<T>> {
1609    IsotonicInterpolator::new(x, y, true)
1610}
1611
1612/// Convenience function to create a decreasing isotonic interpolator
1613#[allow(dead_code)]
1614pub fn make_decreasing_isotonic_interpolator<
1615    T: Float + FromPrimitive + Debug + Display + Copy + std::iter::Sum,
1616>(
1617    x: &ArrayView1<T>,
1618    y: &ArrayView1<T>,
1619) -> InterpolateResult<IsotonicInterpolator<T>> {
1620    IsotonicInterpolator::new(x, y, false)
1621}
1622
1623/// Convenience function to create a KDE interpolator with Gaussian kernel
1624#[allow(dead_code)]
1625pub fn make_kde_interpolator<T: crate::traits::InterpolationFloat + Copy>(
1626    x: &ArrayView1<T>,
1627    y: &ArrayView1<T>,
1628    bandwidth: T,
1629) -> InterpolateResult<KDEInterpolator<T>> {
1630    KDEInterpolator::new(x, y, bandwidth, KDEKernel::Gaussian)
1631}
1632
1633/// Convenience function to create a KDE interpolator with automatic bandwidth selection
1634#[allow(dead_code)]
1635pub fn make_auto_kde_interpolator<
1636    T: Float + FromPrimitive + Debug + Display + Copy + std::iter::Sum,
1637>(
1638    x: &ArrayView1<T>,
1639    y: &ArrayView1<T>,
1640) -> InterpolateResult<KDEInterpolator<T>> {
1641    // Scott's rule for bandwidth selection
1642    let n = T::from(x.len()).expect("Operation failed");
1643    let x_std = {
1644        let mean = x.iter().copied().sum::<T>() / n;
1645        let variance = x.iter().map(|&xi| (xi - mean) * (xi - mean)).sum::<T>() / (n - T::one());
1646        variance.sqrt()
1647    };
1648
1649    let bandwidth = x_std * n.powf(-T::from(0.2).expect("Operation failed")); // n^(-1/5)
1650    KDEInterpolator::new(x, y, bandwidth, KDEKernel::Gaussian)
1651}
1652
1653/// Convenience function to create an empirical Bayes interpolator
1654#[allow(dead_code)]
1655pub fn make_empirical_bayes_interpolator<
1656    T: Float + FromPrimitive + Debug + Display + Copy + std::iter::Sum,
1657>(
1658    x: &ArrayView1<T>,
1659    y: &ArrayView1<T>,
1660) -> InterpolateResult<EmpiricalBayesInterpolator<T>> {
1661    EmpiricalBayesInterpolator::new(x, y)
1662}