1use crate::error::{InterpolateError, InterpolateResult};
11use scirs2_core::ndarray::{Array1, Array2, ArrayView1, ArrayView2, Axis};
12use scirs2_core::numeric::{Float, FromPrimitive};
13use scirs2_core::random::{rngs::StdRng, Rng, RngExt, SeedableRng};
14use std::fmt::{Debug, Display};
15use std::marker::PhantomData;
16
17#[derive(Debug, Clone)]
22pub struct VariationalSparseGP<F: Float> {
23 pub inducing_points: Array2<F>,
25 pub variational_mean: Array1<F>,
27 pub variational_cov_chol: Array2<F>,
29 pub kernel_params: KernelParameters<F>,
31 pub noise_variance: F,
33 pub elbo: F,
35}
36
37#[derive(Debug, Clone)]
39pub struct KernelParameters<F: Float> {
40 pub signal_variance: F,
42 pub length_scales: Array1<F>,
44 pub kernel_type: KernelType,
46}
47
48#[derive(Debug, Clone, Copy, PartialEq)]
50pub enum KernelType {
51 RBF,
53 Matern32,
55 Matern52,
57 RationalQuadratic,
59}
60
61impl<F> VariationalSparseGP<F>
62where
63 F: Float
64 + FromPrimitive
65 + Debug
66 + Display
67 + std::iter::Sum
68 + 'static
69 + std::ops::AddAssign
70 + scirs2_core::ndarray::ScalarOperand
71 + std::ops::SubAssign
72 + std::ops::DivAssign,
73{
74 pub fn new(
76 inducing_points: Array2<F>,
77 kernel_params: KernelParameters<F>,
78 noise_variance: F,
79 ) -> Self {
80 let n_inducing = inducing_points.nrows();
81 let variational_mean = Array1::zeros(n_inducing);
82 let variational_cov_chol = Array2::eye(n_inducing);
83
84 Self {
85 inducing_points,
86 variational_mean,
87 variational_cov_chol,
88 kernel_params,
89 noise_variance,
90 elbo: F::neg_infinity(),
91 }
92 }
93
94 pub fn fit(
96 &mut self,
97 x_train: &ArrayView2<F>,
98 y_train: &ArrayView1<F>,
99 max_iter: usize,
100 _learning_rate: F,
101 tolerance: F,
102 ) -> InterpolateResult<()> {
103 let _n_data = x_train.nrows();
104 let n_inducing = self.inducing_points.nrows();
105
106 for _iter in 0..max_iter {
107 let k_uu = self.compute_kernel_matrix(
109 &self.inducing_points.view(),
110 &self.inducing_points.view(),
111 )?;
112 let k_fu = self.compute_kernel_matrix(x_train, &self.inducing_points.view())?;
113
114 let mut k_uu_jitter = k_uu.clone();
116 let jitter = F::from_f64(1e-6).unwrap_or_else(|| F::epsilon());
117 for i in 0..n_inducing {
118 k_uu_jitter[[i, i]] += jitter;
119 }
120
121 let l_uu = self.cholesky_decomposition(&k_uu_jitter)?;
123
124 let a_matrix = self.solve_triangular_system(&l_uu, &k_fu.t().to_owned())?;
126
127 let sigma_inv = F::one() / self.noise_variance;
129 let lambda = Array2::eye(n_inducing) + &(a_matrix.dot(&a_matrix.t()) * sigma_inv);
130
131 let y_centered = y_train.to_owned();
133 let ata_y = a_matrix.dot(&y_centered);
134 self.variational_mean = self.solve_system(&lambda, &ata_y)? * sigma_inv;
135
136 self.variational_cov_chol = self.cholesky_decomposition(&lambda)?;
138
139 let new_elbo = self.compute_elbo(x_train, y_train, &k_uu, &k_fu, &a_matrix)?;
141
142 if _iter > 0 && (new_elbo - self.elbo).abs() < tolerance {
144 break;
145 }
146
147 self.elbo = new_elbo;
148
149 }
152
153 Ok(())
154 }
155
156 pub fn predict(&self, xtest: &ArrayView2<F>) -> InterpolateResult<(Array1<F>, Array1<F>)> {
158 let n_test = xtest.nrows();
159
160 let k_uu =
162 self.compute_kernel_matrix(&self.inducing_points.view(), &self.inducing_points.view())?;
163 let k_su = self.compute_kernel_matrix(xtest, &self.inducing_points.view())?;
164 let k_ss_diag = self.compute_kernel_diagonal(xtest)?;
165
166 let mut k_uu_jitter = k_uu.clone();
168 let jitter = F::from_f64(1e-6).unwrap_or_else(|| F::epsilon());
169 for i in 0..k_uu_jitter.nrows() {
170 k_uu_jitter[[i, i]] += jitter;
171 }
172
173 let l_uu = self.cholesky_decomposition(&k_uu_jitter)?;
175 let alpha = self.solve_triangular_system(&l_uu, &k_su.t().to_owned())?;
176
177 let mean = k_su.dot(&self.variational_mean);
179
180 let mut variance = Array1::zeros(n_test);
182 for i in 0..n_test {
183 let _k_s = k_su.row(i);
184 let alpha_i = alpha.column(i);
185
186 let var_term1 = k_ss_diag[i];
188 let var_term2 = alpha_i.dot(&alpha_i);
189 let var_term3 = self.compute_trace_correction(&alpha_i)?;
190
191 variance[i] = var_term1 - var_term2 + var_term3 + self.noise_variance;
192 }
193
194 Ok((mean, variance))
195 }
196
197 fn compute_kernel_matrix(
199 &self,
200 x1: &ArrayView2<F>,
201 x2: &ArrayView2<F>,
202 ) -> InterpolateResult<Array2<F>> {
203 let n1 = x1.nrows();
204 let n2 = x2.nrows();
205 let mut k = Array2::zeros((n1, n2));
206
207 for i in 0..n1 {
208 for j in 0..n2 {
209 let distsq = self.squared_distance(&x1.row(i), &x2.row(j))?;
210 k[[i, j]] = self.kernel_function(distsq);
211 }
212 }
213
214 Ok(k)
215 }
216
217 fn compute_kernel_diagonal(&self, x: &ArrayView2<F>) -> InterpolateResult<Array1<F>> {
219 let n = x.nrows();
220 let mut diag = Array1::zeros(n);
221
222 for i in 0..n {
223 diag[i] = self.kernel_params.signal_variance;
224 }
225
226 Ok(diag)
227 }
228
229 fn squared_distance(&self, x1: &ArrayView1<F>, x2: &ArrayView1<F>) -> InterpolateResult<F> {
231 if x1.len() != x2.len() || x1.len() != self.kernel_params.length_scales.len() {
232 return Err(InterpolateError::DimensionMismatch(
233 "Point dimensions must match length scales".to_string(),
234 ));
235 }
236
237 let mut distsq = F::zero();
238 for i in 0..x1.len() {
239 let diff = x1[i] - x2[i];
240 let scaled_diff = diff / self.kernel_params.length_scales[i];
241 distsq += scaled_diff * scaled_diff;
242 }
243
244 Ok(distsq)
245 }
246
247 fn kernel_function(&self, distsq: F) -> F {
249 match self.kernel_params.kernel_type {
250 KernelType::RBF => {
251 self.kernel_params.signal_variance
252 * (-F::from_f64(0.5).expect("Failed to convert f64 to target float type")
253 * distsq)
254 .exp()
255 }
256 KernelType::Matern32 => {
257 let dist = distsq.sqrt();
258 let sqrt3 = F::from_f64(3.0_f64.sqrt())
259 .expect("Failed to convert f64 to target float type");
260 let term = sqrt3 * dist;
261 self.kernel_params.signal_variance * (F::one() + term) * (-term).exp()
262 }
263 KernelType::Matern52 => {
264 let dist = distsq.sqrt();
265 let sqrt5 = F::from_f64(5.0_f64.sqrt())
266 .expect("Failed to convert f64 to target float type");
267 let term = sqrt5 * dist;
268 let term2 = F::from_f64(5.0).expect("Failed to convert f64 to target float type")
269 * distsq
270 / F::from_f64(3.0).expect("Failed to convert f64 to target float type");
271 self.kernel_params.signal_variance * (F::one() + term + term2) * (-term).exp()
272 }
273 KernelType::RationalQuadratic => {
274 let alpha = F::from_f64(1.0).expect("Failed to convert f64 to target float type"); self.kernel_params.signal_variance
276 * (F::one()
277 + distsq
278 / (F::from_f64(2.0)
279 .expect("Failed to convert f64 to target float type")
280 * alpha))
281 .powf(-alpha)
282 }
283 }
284 }
285
286 fn cholesky_decomposition(&self, matrix: &Array2<F>) -> InterpolateResult<Array2<F>> {
288 let n = matrix.nrows();
289 let mut l = Array2::zeros((n, n));
290
291 for i in 0..n {
292 for j in 0..=i {
293 if i == j {
294 let mut sum = F::zero();
296 for k in 0..j {
297 sum += l[[j, k]] * l[[j, k]];
298 }
299 let diag_val = matrix[[j, j]] - sum;
300 if diag_val <= F::zero() {
301 return Err(InterpolateError::ComputationError(
302 "Matrix is not positive definite".to_string(),
303 ));
304 }
305 l[[j, j]] = diag_val.sqrt();
306 } else {
307 let mut sum = F::zero();
309 for k in 0..j {
310 sum += l[[i, k]] * l[[j, k]];
311 }
312 l[[i, j]] = (matrix[[i, j]] - sum) / l[[j, j]];
313 }
314 }
315 }
316
317 Ok(l)
318 }
319
320 fn solve_triangular_system(
322 &self,
323 l: &Array2<F>,
324 b: &Array2<F>,
325 ) -> InterpolateResult<Array2<F>> {
326 let n = l.nrows();
327 let m = b.ncols();
328 let mut x = Array2::zeros((n, m));
329
330 for col in 0..m {
331 for i in 0..n {
332 let mut sum = F::zero();
333 for j in 0..i {
334 sum += l[[i, j]] * x[[j, col]];
335 }
336 x[[i, col]] = (b[[i, col]] - sum) / l[[i, i]];
337 }
338 }
339
340 Ok(x)
341 }
342
343 fn solve_system(&self, a: &Array2<F>, b: &Array1<F>) -> InterpolateResult<Array1<F>> {
345 let n = a.nrows();
347 let mut x = Array1::zeros(n);
348
349 let mut aug = Array2::zeros((n, n + 1));
351 for i in 0..n {
352 for j in 0..n {
353 aug[[i, j]] = a[[i, j]];
354 }
355 aug[[i, n]] = b[i];
356 }
357
358 for i in 0..n {
360 let mut max_row = i;
362 for k in i + 1..n {
363 if aug[[k, i]].abs() > aug[[max_row, i]].abs() {
364 max_row = k;
365 }
366 }
367
368 for j in 0..=n {
370 let temp = aug[[max_row, j]];
371 aug[[max_row, j]] = aug[[i, j]];
372 aug[[i, j]] = temp;
373 }
374
375 for k in i + 1..n {
377 if aug[[i, i]].abs() < F::epsilon() {
378 return Err(InterpolateError::ComputationError(
379 "Matrix is singular".to_string(),
380 ));
381 }
382 let c = aug[[k, i]] / aug[[i, i]];
383 for j in i..=n {
384 let aug_i_j = aug[[i, j]];
385 aug[[k, j]] -= c * aug_i_j;
386 }
387 }
388 }
389
390 for i in (0..n).rev() {
392 let mut sum = aug[[i, n]];
393 for j in i + 1..n {
394 sum -= aug[[i, j]] * x[j];
395 }
396 x[i] = sum / aug[[i, i]];
397 }
398
399 Ok(x)
400 }
401
402 fn compute_trace_correction(&self, alpha: &ArrayView1<F>) -> InterpolateResult<F> {
404 let trace_term = alpha.dot(alpha)
406 * F::from_f64(0.1).expect("Failed to convert f64 to target float type");
407 Ok(trace_term)
408 }
409
410 fn compute_elbo(
412 &self,
413 x_train: &ArrayView2<F>,
414 y_train: &ArrayView1<F>,
415 _k_uu: &Array2<F>,
416 k_fu: &Array2<F>,
417 _a_matrix: &Array2<F>,
418 ) -> InterpolateResult<F> {
419 let n_data = x_train.nrows();
420 let n_inducing = self.inducing_points.nrows();
421
422 let y_pred = k_fu.dot(&self.variational_mean);
424 let residuals = y_train - &y_pred;
425 let data_fit = -F::from_f64(0.5).expect("Failed to convert f64 to target float type")
426 * residuals.dot(&residuals)
427 / self.noise_variance;
428
429 let log_det_term = F::from_f64(n_inducing as f64 * (2.0 * std::f64::consts::PI).ln())
431 .expect("Operation failed");
432 let trace_term = self.variational_cov_chol.diag().mapv(|x| x.ln()).sum();
433 let kl_penalty = -F::from_f64(0.5).expect("Failed to convert f64 to target float type")
434 * (log_det_term
435 + F::from_f64(2.0).expect("Failed to convert f64 to target float type")
436 * trace_term);
437
438 let noise_term = -F::from_f64(0.5 * n_data as f64)
440 .expect("Failed to convert f64 to target float type")
441 * self.noise_variance.ln();
442
443 Ok(data_fit + kl_penalty + noise_term)
444 }
445}
446
447#[derive(Debug, Clone)]
452pub struct StatisticalSpline<F: Float> {
453 pub coefficients: Array1<F>,
455 pub knots: Array1<F>,
457 pub coef_covariance: Array2<F>,
459 pub residual_std_error: F,
461 pub degrees_of_freedom: usize,
463}
464
465impl<F> StatisticalSpline<F>
466where
467 F: Float
468 + FromPrimitive
469 + Debug
470 + Display
471 + scirs2_core::ndarray::ScalarOperand
472 + std::ops::AddAssign
473 + std::ops::SubAssign
474 + std::ops::MulAssign
475 + std::ops::DivAssign,
476{
477 pub fn fit(
479 x: &ArrayView1<F>,
480 y: &ArrayView1<F>,
481 n_knots: usize,
482 smoothing_parameter: F,
483 ) -> InterpolateResult<Self> {
484 let n = x.len();
485 if n != y.len() {
486 return Err(InterpolateError::DimensionMismatch(
487 "x and y must have same length".to_string(),
488 ));
489 }
490
491 let x_min = x.fold(F::infinity(), |a, &b| a.min(b));
493 let x_max = x.fold(F::neg_infinity(), |a, &b| a.max(b));
494 let mut knots = Array1::zeros(n_knots);
495 for i in 0..n_knots {
496 let t = F::from_usize(i).expect("Failed to convert usize to float")
497 / F::from_usize(n_knots - 1).expect("Failed to convert usize to float");
498 knots[i] = x_min + t * (x_max - x_min);
499 }
500
501 let design_matrix = Self::build_bspline_matrix(x, &knots)?;
503
504 let penalty_matrix = Self::build_penalty_matrix(n_knots)?;
506 let penalized_matrix =
507 design_matrix.t().dot(&design_matrix) + &(penalty_matrix.clone() * smoothing_parameter);
508
509 let rhs = design_matrix.t().dot(y);
511 let coefficients = Self::solve_penalized_system(&penalized_matrix, &rhs)?;
512
513 let fitted_values = design_matrix.dot(&coefficients);
515 let residuals = y - &fitted_values;
516 let rss = residuals.dot(&residuals);
517 let dof = n - Self::effective_degrees_of_freedom(&design_matrix, smoothing_parameter)?;
518 let residual_std_error =
519 (rss / F::from_usize(dof).expect("Failed to convert usize to float")).sqrt();
520
521 let coef_covariance = Self::compute_coefficient_covariance(
523 &design_matrix,
524 &penalty_matrix,
525 smoothing_parameter,
526 residual_std_error,
527 )?;
528
529 Ok(Self {
530 coefficients,
531 knots,
532 coef_covariance,
533 residual_std_error,
534 degrees_of_freedom: dof,
535 })
536 }
537
538 pub fn predict_with_bands(
540 &self,
541 x_new: &ArrayView1<F>,
542 confidence_level: F,
543 ) -> InterpolateResult<(Array1<F>, Array1<F>, Array1<F>, Array1<F>, Array1<F>)> {
544 let design_new = Self::build_bspline_matrix(x_new, &self.knots)?;
545
546 let predictions = design_new.dot(&self.coefficients);
548
549 let mut std_errors = Array1::zeros(x_new.len());
551 let mut prediction_std_errors = Array1::zeros(x_new.len());
552
553 for i in 0..x_new.len() {
554 let x_row = design_new.row(i);
555 let variance = x_row.dot(&self.coef_covariance.dot(&x_row));
556 std_errors[i] = variance.sqrt();
557 prediction_std_errors[i] =
558 (variance + self.residual_std_error * self.residual_std_error).sqrt();
559 }
560
561 let _alpha = F::one() - confidence_level;
563 let t_crit = F::from_f64(1.96).expect("Failed to convert f64 to target float type"); let conf_lower = &predictions - &(std_errors.clone() * t_crit);
567 let conf_upper = &predictions + &(std_errors * t_crit);
568
569 let pred_lower = &predictions - &(prediction_std_errors.clone() * t_crit);
571 let pred_upper = &predictions + &(prediction_std_errors * t_crit);
572
573 Ok((predictions, conf_lower, conf_upper, pred_lower, pred_upper))
574 }
575
576 fn build_bspline_matrix(x: &ArrayView1<F>, knots: &Array1<F>) -> InterpolateResult<Array2<F>> {
578 let n = x.len();
579 let m = knots.len();
580 let mut matrix = Array2::zeros((n, m));
581
582 for i in 0..n {
584 for j in 0..m {
585 if j == 0 {
586 matrix[[i, j]] = F::one();
587 } else {
588 matrix[[i, j]] =
589 x[i].powf(F::from_usize(j).expect("Failed to convert usize to float"));
590 }
591 }
592 }
593
594 Ok(matrix)
595 }
596
597 fn build_penalty_matrix(_nknots: usize) -> InterpolateResult<Array2<F>> {
599 let mut penalty = Array2::zeros((_nknots, _nknots));
600
601 for i in 2.._nknots {
603 penalty[[i - 2, i - 2]] += F::one();
604 penalty[[i - 2, i - 1]] -=
605 F::from_f64(2.0).expect("Failed to convert f64 to target float type");
606 penalty[[i - 2, i]] += F::one();
607 penalty[[i - 1, i - 2]] -=
608 F::from_f64(2.0).expect("Failed to convert f64 to target float type");
609 penalty[[i - 1, i - 1]] +=
610 F::from_f64(4.0).expect("Failed to convert f64 to target float type");
611 penalty[[i - 1, i]] -=
612 F::from_f64(2.0).expect("Failed to convert f64 to target float type");
613 penalty[[i, i - 2]] += F::one();
614 penalty[[i, i - 1]] -=
615 F::from_f64(2.0).expect("Failed to convert f64 to target float type");
616 penalty[[i, i]] += F::one();
617 }
618
619 Ok(penalty)
620 }
621
622 fn solve_penalized_system(a: &Array2<F>, b: &Array1<F>) -> InterpolateResult<Array1<F>> {
624 let n = a.nrows();
626 let mut x = Array1::zeros(n);
627
628 for _iter in 0..100 {
630 let mut max_change = F::zero();
631 for i in 0..n {
632 let mut sum = b[i];
633 for j in 0..n {
634 if i != j {
635 sum -= a[[i, j]] * x[j];
636 }
637 }
638 let new_val = sum / a[[i, i]];
639 let change = (new_val - x[i]).abs();
640 if change > max_change {
641 max_change = change;
642 }
643 x[i] = new_val;
644 }
645
646 if max_change < F::from_f64(1e-8).expect("Failed to convert f64 to target float type") {
647 break;
648 }
649 }
650
651 Ok(x)
652 }
653
654 fn effective_degrees_of_freedom(
656 design: &Array2<F>,
657 smoothing_parameter: F,
658 ) -> InterpolateResult<usize> {
659 let base_dof = design.ncols();
661 let penalty_reduction = (smoothing_parameter.ln()
662 * F::from_f64(0.1).expect("Failed to convert f64 to target float type"))
663 .exp();
664 let effective_dof = F::from_usize(base_dof).expect("Failed to convert usize to float")
665 * (F::one() - penalty_reduction);
666 Ok(effective_dof.to_usize().unwrap_or(base_dof))
667 }
668
669 fn compute_coefficient_covariance(
671 design: &Array2<F>,
672 penalty: &Array2<F>,
673 smoothing_parameter: F,
674 residual_std_error: F,
675 ) -> InterpolateResult<Array2<F>> {
676 let penalized_matrix = design.t().dot(design) + &(penalty * smoothing_parameter);
677
678 let n = penalized_matrix.nrows();
680 let mut inv_matrix = Array2::eye(n);
681
682 for i in 0..n {
684 inv_matrix[[i, i]] = F::one() / penalized_matrix[[i, i]];
685 }
686
687 Ok(inv_matrix * residual_std_error * residual_std_error)
688 }
689}
690
691#[derive(Debug, Clone)]
693pub struct AdvancedBootstrap<F: Float> {
694 pub block_size: usize,
696 pub method: BootstrapMethod,
698 pub n_samples: usize,
700 pub seed: Option<u64>,
702 pub _phantom: PhantomData<F>,
704}
705
706#[derive(Debug, Clone, Copy, PartialEq)]
708pub enum BootstrapMethod {
709 Standard,
711 Block,
713 Residual,
715 Wild,
717}
718
719impl<F> AdvancedBootstrap<F>
720where
721 F: Float + FromPrimitive + Debug + Display + std::iter::Sum,
722{
723 pub fn new(method: BootstrapMethod, n_samples: usize, blocksize: usize) -> Self {
725 Self {
726 block_size: blocksize,
727 method,
728 n_samples,
729 seed: None,
730 _phantom: PhantomData,
731 }
732 }
733
734 pub fn bootstrap_interpolate<InterpolatorFn>(
736 &self,
737 x: &ArrayView1<F>,
738 y: &ArrayView1<F>,
739 x_new: &ArrayView1<F>,
740 interpolator_factory: InterpolatorFn,
741 ) -> InterpolateResult<(Array1<F>, Array1<F>, Array1<F>)>
742 where
743 InterpolatorFn:
744 Fn(&ArrayView1<F>, &ArrayView1<F>, &ArrayView1<F>) -> InterpolateResult<Array1<F>>,
745 {
746 let _n = x.len();
747 let m = x_new.len();
748 let mut rng = match self.seed {
749 Some(seed) => StdRng::seed_from_u64(seed),
750 None => StdRng::seed_from_u64(42),
751 };
752
753 let mut bootstrap_results = Array2::zeros((self.n_samples, m));
754
755 for sample in 0..self.n_samples {
756 let (x_boot, y_boot) = match self.method {
757 BootstrapMethod::Standard => self.standard_bootstrap(x, y, &mut rng)?,
758 BootstrapMethod::Block => self.block_bootstrap(x, y, &mut rng)?,
759 BootstrapMethod::Residual => {
760 self.residual_bootstrap(x, y, &mut rng, &interpolator_factory)?
761 }
762 BootstrapMethod::Wild => {
763 self.wild_bootstrap(x, y, &mut rng, &interpolator_factory)?
764 }
765 };
766
767 let y_pred = interpolator_factory(&x_boot.view(), &y_boot.view(), x_new)?;
768 bootstrap_results.row_mut(sample).assign(&y_pred);
769 }
770
771 let mean = bootstrap_results
773 .mean_axis(Axis(0))
774 .expect("Failed to compute mean along axis");
775 let _std_dev = bootstrap_results.std_axis(Axis(0), F::zero());
776
777 let mut conf_lower = Array1::zeros(m);
779 let mut conf_upper = Array1::zeros(m);
780
781 for i in 0..m {
782 let mut column: Vec<F> = bootstrap_results.column(i).to_vec();
783 column.sort_by(|a, b| a.partial_cmp(b).expect("Float comparison failed"));
784
785 let lower_idx = ((F::from_f64(0.025)
786 .expect("Failed to convert f64 to target float type")
787 * F::from_usize(self.n_samples).expect("Failed to convert usize to float"))
788 .to_usize()
789 .expect("Failed to convert to usize"))
790 .min(self.n_samples - 1);
791 let upper_idx = ((F::from_f64(0.975)
792 .expect("Failed to convert f64 to target float type")
793 * F::from_usize(self.n_samples).expect("Failed to convert usize to float"))
794 .to_usize()
795 .expect("Failed to convert to usize"))
796 .min(self.n_samples - 1);
797
798 conf_lower[i] = column[lower_idx];
799 conf_upper[i] = column[upper_idx];
800 }
801
802 Ok((mean, conf_lower, conf_upper))
803 }
804
805 fn standard_bootstrap(
807 &self,
808 x: &ArrayView1<F>,
809 y: &ArrayView1<F>,
810 rng: &mut StdRng,
811 ) -> InterpolateResult<(Array1<F>, Array1<F>)> {
812 let n = x.len();
813 let mut indices = Vec::with_capacity(n);
814
815 for _ in 0..n {
816 indices.push(rng.random_range(0..n));
817 }
818
819 let x_boot = Array1::from_iter(indices.iter().map(|&i| x[i]));
820 let y_boot = Array1::from_iter(indices.iter().map(|&i| y[i]));
821
822 Ok((x_boot, y_boot))
823 }
824
825 fn block_bootstrap(
827 &self,
828 x: &ArrayView1<F>,
829 y: &ArrayView1<F>,
830 rng: &mut StdRng,
831 ) -> InterpolateResult<(Array1<F>, Array1<F>)> {
832 let n = x.len();
833 let n_blocks = n.div_ceil(self.block_size);
834
835 let mut x_boot = Vec::new();
836 let mut y_boot = Vec::new();
837
838 for _ in 0..n_blocks {
839 let start_idx = rng.random_range(0..=(n.saturating_sub(self.block_size)));
840 let end_idx = (start_idx + self.block_size).min(n);
841
842 for i in start_idx..end_idx {
843 x_boot.push(x[i]);
844 y_boot.push(y[i]);
845 if x_boot.len() >= n {
846 break;
847 }
848 }
849 if x_boot.len() >= n {
850 break;
851 }
852 }
853
854 x_boot.truncate(n);
855 y_boot.truncate(n);
856
857 Ok((Array1::from(x_boot), Array1::from(y_boot)))
858 }
859
860 fn residual_bootstrap<InterpolatorFn>(
862 &self,
863 x: &ArrayView1<F>,
864 y: &ArrayView1<F>,
865 rng: &mut StdRng,
866 interpolator_factory: &InterpolatorFn,
867 ) -> InterpolateResult<(Array1<F>, Array1<F>)>
868 where
869 InterpolatorFn:
870 Fn(&ArrayView1<F>, &ArrayView1<F>, &ArrayView1<F>) -> InterpolateResult<Array1<F>>,
871 {
872 let n = x.len();
873
874 let y_fitted = interpolator_factory(x, y, x)?;
876 let residuals = y - &y_fitted;
877
878 let mut resampled_residuals = Array1::zeros(n);
880 for i in 0..n {
881 let idx = rng.random_range(0..n);
882 resampled_residuals[i] = residuals[idx];
883 }
884
885 let y_boot = y_fitted + resampled_residuals;
887
888 Ok((x.to_owned(), y_boot))
889 }
890
891 fn wild_bootstrap<InterpolatorFn>(
893 &self,
894 x: &ArrayView1<F>,
895 y: &ArrayView1<F>,
896 rng: &mut StdRng,
897 interpolator_factory: &InterpolatorFn,
898 ) -> InterpolateResult<(Array1<F>, Array1<F>)>
899 where
900 InterpolatorFn:
901 Fn(&ArrayView1<F>, &ArrayView1<F>, &ArrayView1<F>) -> InterpolateResult<Array1<F>>,
902 {
903 let n = x.len();
904
905 let y_fitted = interpolator_factory(x, y, x)?;
907 let residuals = y - &y_fitted;
908
909 let mut multipliers = Array1::zeros(n);
911 for i in 0..n {
912 multipliers[i] = if rng.random::<f64>() < 0.5 {
913 F::from_f64(-1.0).expect("Failed to convert f64 to target float type")
914 } else {
915 F::one()
916 };
917 }
918
919 let y_boot = y_fitted + &residuals * &multipliers;
921
922 Ok((x.to_owned(), y_boot))
923 }
924}
925
926#[cfg(test)]
927mod tests {
928 use super::*;
929 #[test]
932 fn test_variational_sparse_gp() {
933 let x_train = Array2::from_shape_vec((5, 1), vec![0.0, 1.0, 2.0, 3.0, 4.0])
935 .expect("Operation failed");
936 let y_train = Array1::from(vec![0.0, 1.0, 4.0, 9.0, 16.0]); let kernel_params = KernelParameters {
940 signal_variance: 1.0,
941 length_scales: Array1::from(vec![1.0]),
942 kernel_type: KernelType::RBF,
943 };
944
945 let inducing_points =
947 Array2::from_shape_vec((3, 1), vec![0.0, 2.0, 4.0]).expect("Operation failed");
948 let mut sparse_gp = VariationalSparseGP::new(
949 inducing_points,
950 kernel_params,
951 0.1, );
953
954 let result = sparse_gp.fit(&x_train.view(), &y_train.view(), 10, 0.01, 1e-6);
956 assert!(result.is_ok());
957
958 let xtest = Array2::from_shape_vec((3, 1), vec![0.5, 1.5, 2.5]).expect("Operation failed");
960 let (mean, variance) = sparse_gp.predict(&xtest.view()).expect("Operation failed");
961
962 assert_eq!(mean.len(), 3);
964 assert_eq!(variance.len(), 3);
965 assert!(variance.iter().all(|&v| v > 0.0));
966 }
967
968 #[test]
969 fn test_statistical_spline() {
970 let x = Array1::from(vec![0.0, 1.0, 2.0, 3.0, 4.0]);
972 let y = Array1::from(vec![0.0, 1.0, 4.0, 9.0, 16.0]);
973
974 let spline =
976 StatisticalSpline::fit(&x.view(), &y.view(), 5, 0.1).expect("Operation failed");
977
978 let x_new = Array1::from(vec![0.5, 1.5, 2.5, 3.5]);
980 let (pred, conf_lower, conf_upper, pred_lower, pred_upper) = spline
981 .predict_with_bands(&x_new.view(), 0.95)
982 .expect("Operation failed");
983
984 assert_eq!(pred.len(), 4);
986 assert!(conf_lower
987 .iter()
988 .zip(conf_upper.iter())
989 .all(|(&l, &u)| l < u));
990 assert!(pred_lower
991 .iter()
992 .zip(pred_upper.iter())
993 .all(|(&l, &u)| l < u));
994 assert!(conf_lower
995 .iter()
996 .zip(pred_lower.iter())
997 .all(|(&c, &p)| c >= p));
998 assert!(conf_upper
999 .iter()
1000 .zip(pred_upper.iter())
1001 .all(|(&c, &p)| c <= p));
1002 }
1003
1004 #[test]
1005 fn test_advanced_bootstrap() {
1006 let x = Array1::from(vec![0.0, 1.0, 2.0, 3.0, 4.0]);
1007 let y = Array1::from(vec![0.0, 1.0, 4.0, 9.0, 16.0]);
1008 let x_new = Array1::from(vec![0.5, 1.5, 2.5]);
1009
1010 let bootstrap = AdvancedBootstrap::new(BootstrapMethod::Standard, 100, 2);
1011
1012 let interpolator =
1014 |x_data: &ArrayView1<f64>, y_data: &ArrayView1<f64>, x_pred: &ArrayView1<f64>| {
1015 let mut result = Array1::zeros(x_pred.len());
1017 for (i, &x_val) in x_pred.iter().enumerate() {
1018 if x_val <= x_data[0] {
1020 result[i] = y_data[0];
1021 } else if x_val >= x_data[x_data.len() - 1] {
1022 result[i] = y_data[y_data.len() - 1];
1023 } else {
1024 for j in 0..x_data.len() - 1 {
1026 if x_val >= x_data[j] && x_val <= x_data[j + 1] {
1027 let t = (x_val - x_data[j]) / (x_data[j + 1] - x_data[j]);
1028 result[i] = y_data[j] + t * (y_data[j + 1] - y_data[j]);
1029 break;
1030 }
1031 }
1032 }
1033 }
1034 Ok(result)
1035 };
1036
1037 let (mean, lower, upper) = bootstrap
1038 .bootstrap_interpolate(&x.view(), &y.view(), &x_new.view(), interpolator)
1039 .expect("Bootstrap interpolation failed");
1040
1041 assert_eq!(mean.len(), 3);
1042 assert!(lower.iter().zip(upper.iter()).all(|(&l, &u)| l <= u));
1043 }
1044}
1045
1046#[derive(Debug, Clone)]
1052pub struct SavitzkyGolayFilter<F: Float> {
1053 pub window_length: usize,
1055 pub polynomial_order: usize,
1057 pub derivative_order: usize,
1059 pub _phantom: PhantomData<F>,
1061}
1062
1063impl<F> SavitzkyGolayFilter<F>
1064where
1065 F: Float + FromPrimitive + Debug + std::iter::Sum + 'static,
1066{
1067 pub fn new(
1069 window_length: usize,
1070 polynomial_order: usize,
1071 derivative_order: usize,
1072 ) -> InterpolateResult<Self> {
1073 if window_length.is_multiple_of(2) {
1074 return Err(InterpolateError::InvalidValue(
1075 "Window _length must be odd".to_string(),
1076 ));
1077 }
1078
1079 if polynomial_order >= window_length {
1080 return Err(InterpolateError::InvalidValue(
1081 "Polynomial _order must be less than window _length".to_string(),
1082 ));
1083 }
1084
1085 if derivative_order > polynomial_order {
1086 return Err(InterpolateError::InvalidValue(
1087 "Derivative _order cannot exceed polynomial _order".to_string(),
1088 ));
1089 }
1090
1091 Ok(Self {
1092 window_length,
1093 polynomial_order,
1094 derivative_order: 0,
1095 _phantom: PhantomData,
1096 })
1097 }
1098
1099 pub fn filter(&self, y: &ArrayView1<F>) -> InterpolateResult<Array1<F>> {
1101 let n = y.len();
1102 if n < self.window_length {
1103 return Err(InterpolateError::InvalidValue(
1104 "Data length must be at least window length".to_string(),
1105 ));
1106 }
1107
1108 let mut result = Array1::zeros(n);
1109 let half_window = self.window_length / 2;
1110
1111 let coeffs = self.compute_coefficients()?;
1113
1114 for i in 0..n {
1116 let mut sum = F::zero();
1117
1118 for j in 0..self.window_length {
1119 let data_idx = if i < half_window {
1120 j.min(n - 1)
1122 } else if i >= n - half_window {
1123 (n - self.window_length + j).max(0).min(n - 1)
1125 } else {
1126 i - half_window + j
1128 };
1129
1130 sum = sum + coeffs[j] * y[data_idx];
1131 }
1132
1133 result[i] = sum;
1134 }
1135
1136 Ok(result)
1137 }
1138
1139 fn compute_coefficients(&self) -> InterpolateResult<Array1<F>> {
1141 let m = self.window_length;
1142 let n = self.polynomial_order + 1;
1143 let half_window = (m - 1) / 2;
1144
1145 let mut design = Array2::<F>::zeros((m, n));
1147 for i in 0..m {
1148 let x = F::from_isize(i as isize - half_window as isize)
1149 .expect("Failed to convert isize to float");
1150 for j in 0..n {
1151 design[[i, j]] = x.powi(j as i32);
1152 }
1153 }
1154
1155 let xtx = design.t().dot(&design);
1158 let mut rhs = Array1::<F>::zeros(n);
1159
1160 let mut factorial = F::one();
1162 for i in 1..=self.derivative_order {
1163 factorial = factorial * F::from_usize(i).expect("Failed to convert usize to float");
1164 }
1165 rhs[self.derivative_order] = factorial;
1166
1167 let coeffs_polynomial = self.solve_linear_system(&xtx, &rhs)?;
1169
1170 let filter_coeffs = design.dot(&coeffs_polynomial);
1172
1173 Ok(filter_coeffs)
1174 }
1175
1176 fn solve_linear_system(&self, a: &Array2<F>, b: &Array1<F>) -> InterpolateResult<Array1<F>> {
1178 let n = a.nrows();
1179 let mut x = Array1::<F>::zeros(n);
1180
1181 for i in 0..n {
1183 if a[[i, i]].abs()
1184 < F::from_f64(1e-12).expect("Failed to convert f64 to target float type")
1185 {
1186 return Err(InterpolateError::ComputationError(
1187 "Singular matrix in Savitzky-Golay computation".to_string(),
1188 ));
1189 }
1190 x[i] = b[i] / a[[i, i]];
1191 }
1192
1193 Ok(x)
1194 }
1195}
1196
1197#[derive(Debug, Clone)]
1202pub struct BcaBootstrap<F: Float> {
1203 pub n_bootstrap: usize,
1205 pub confidence_level: F,
1207 pub seed: Option<u64>,
1209}
1210
1211impl<F> BcaBootstrap<F>
1212where
1213 F: Float + FromPrimitive + Debug + std::iter::Sum,
1214{
1215 pub fn new(n_bootstrap: usize, confidence_level: F, seed: Option<u64>) -> Self {
1217 Self {
1218 n_bootstrap,
1219 confidence_level,
1220 seed,
1221 }
1222 }
1223
1224 pub fn confidence_intervals<G>(
1226 &self,
1227 x: &ArrayView1<F>,
1228 y: &ArrayView1<F>,
1229 x_new: &ArrayView1<F>,
1230 interpolator: G,
1231 ) -> InterpolateResult<(Array1<F>, Array1<F>, Array1<F>)>
1232 where
1233 G: Fn(&ArrayView1<F>, &ArrayView1<F>, &ArrayView1<F>) -> InterpolateResult<Array1<F>>,
1234 {
1235 let n_data = x.len();
1236 let n_pred = x_new.len();
1237
1238 let mut rng = match self.seed {
1240 Some(seed) => StdRng::seed_from_u64(seed),
1241 None => {
1242 let mut rng = scirs2_core::random::rng();
1243 StdRng::from_rng(&mut rng)
1244 }
1245 };
1246
1247 let mut bootstrap_results = Array2::<F>::zeros((self.n_bootstrap, n_pred));
1248
1249 for b in 0..self.n_bootstrap {
1251 let mut x_boot = Array1::<F>::zeros(n_data);
1252 let mut y_boot = Array1::<F>::zeros(n_data);
1253
1254 for i in 0..n_data {
1255 let idx = rng.random_range(0..n_data);
1256 x_boot[i] = x[idx];
1257 y_boot[i] = y[idx];
1258 }
1259
1260 let pred = interpolator(&x_boot.view(), &y_boot.view(), x_new)?;
1261 bootstrap_results.row_mut(b).assign(&pred);
1262 }
1263
1264 let original_pred = interpolator(x, y, x_new)?;
1266
1267 let bias_correction = self.compute_bias_correction(&bootstrap_results, &original_pred)?;
1269
1270 let acceleration = self.compute_acceleration(x, y, x_new, &interpolator)?;
1272
1273 let alpha = (F::one() - self.confidence_level)
1275 / F::from_f64(2.0).expect("Failed to convert f64 to target float type");
1276 let z_alpha = self.inverse_normal_cdf(alpha)?;
1277 let z_1_alpha = self.inverse_normal_cdf(F::one() - alpha)?;
1278
1279 let mut lower = Array1::<F>::zeros(n_pred);
1280 let mut upper = Array1::<F>::zeros(n_pred);
1281
1282 for i in 0..n_pred {
1283 let bc = bias_correction[i];
1284 let acc = acceleration[i];
1285
1286 let alpha1 =
1288 self.normal_cdf(bc + (bc + z_alpha) / (F::one() - acc * (bc + z_alpha)))?;
1289 let alpha2 =
1290 self.normal_cdf(bc + (bc + z_1_alpha) / (F::one() - acc * (bc + z_1_alpha)))?;
1291
1292 let mut column: Vec<F> = bootstrap_results.column(i).to_vec();
1294 column.sort_by(|a, b| a.partial_cmp(b).expect("Float comparison failed"));
1295
1296 let idx1 = ((alpha1
1297 * F::from_usize(self.n_bootstrap).expect("Failed to convert usize to float"))
1298 .floor()
1299 .to_usize()
1300 .expect("Failed to convert to usize"))
1301 .min(self.n_bootstrap - 1);
1302 let idx2 = ((alpha2
1303 * F::from_usize(self.n_bootstrap).expect("Failed to convert usize to float"))
1304 .floor()
1305 .to_usize()
1306 .expect("Failed to convert to usize"))
1307 .min(self.n_bootstrap - 1);
1308
1309 lower[i] = column[idx1];
1310 upper[i] = column[idx2];
1311 }
1312
1313 Ok((original_pred, lower, upper))
1314 }
1315
1316 fn compute_bias_correction(
1318 &self,
1319 bootstrap_results: &Array2<F>,
1320 original_pred: &Array1<F>,
1321 ) -> InterpolateResult<Array1<F>> {
1322 let n_pred = original_pred.len();
1323 let mut bias_correction = Array1::<F>::zeros(n_pred);
1324
1325 for i in 0..n_pred {
1326 let column = bootstrap_results.column(i);
1327 let count_less = column.iter().filter(|&&val| val < original_pred[i]).count();
1328
1329 let proportion = F::from_usize(count_less).expect("Failed to convert usize to float")
1330 / F::from_usize(self.n_bootstrap).expect("Failed to convert usize to float");
1331 bias_correction[i] = self.inverse_normal_cdf(proportion)?;
1332 }
1333
1334 Ok(bias_correction)
1335 }
1336
1337 fn compute_acceleration<G>(
1339 &self,
1340 x: &ArrayView1<F>,
1341 y: &ArrayView1<F>,
1342 x_new: &ArrayView1<F>,
1343 interpolator: &G,
1344 ) -> InterpolateResult<Array1<F>>
1345 where
1346 G: Fn(&ArrayView1<F>, &ArrayView1<F>, &ArrayView1<F>) -> InterpolateResult<Array1<F>>,
1347 {
1348 let n_data = x.len();
1349 let n_pred = x_new.len();
1350
1351 let mut jackknife_results = Array2::<F>::zeros((n_data, n_pred));
1353
1354 for i in 0..n_data {
1355 let mut x_jack = Array1::<F>::zeros(n_data - 1);
1357 let mut y_jack = Array1::<F>::zeros(n_data - 1);
1358
1359 let mut idx = 0;
1360 for j in 0..n_data {
1361 if j != i {
1362 x_jack[idx] = x[j];
1363 y_jack[idx] = y[j];
1364 idx += 1;
1365 }
1366 }
1367
1368 let pred = interpolator(&x_jack.view(), &y_jack.view(), x_new)?;
1369 jackknife_results.row_mut(i).assign(&pred);
1370 }
1371
1372 let jack_mean = jackknife_results
1374 .mean_axis(Axis(0))
1375 .expect("Failed to compute mean along axis");
1376
1377 let mut acceleration = Array1::<F>::zeros(n_pred);
1379 for i in 0..n_pred {
1380 let mut sum_cubed = F::zero();
1381 let mut sum_squared = F::zero();
1382
1383 for j in 0..n_data {
1384 let diff = jack_mean[i] - jackknife_results[[j, i]];
1385 sum_cubed = sum_cubed + diff * diff * diff;
1386 sum_squared = sum_squared + diff * diff;
1387 }
1388
1389 if sum_squared > F::zero() {
1390 acceleration[i] = sum_cubed
1391 / (F::from_f64(6.0).expect("Failed to convert f64 to target float type")
1392 * sum_squared.powf(
1393 F::from_f64(1.5).expect("Failed to convert f64 to target float type"),
1394 ));
1395 }
1396 }
1397
1398 Ok(acceleration)
1399 }
1400
1401 fn normal_cdf(&self, x: F) -> InterpolateResult<F> {
1403 let result = (F::one()
1405 + (x / F::from_f64(1.414).expect("Failed to convert f64 to target float type")).tanh())
1406 / F::from_f64(2.0).expect("Failed to convert f64 to target float type");
1407 Ok(result)
1408 }
1409
1410 fn inverse_normal_cdf(&self, p: F) -> InterpolateResult<F> {
1412 if p <= F::zero() || p >= F::one() {
1414 return Ok(F::zero());
1415 }
1416
1417 let x =
1419 F::from_f64(2.0).expect("Failed to convert f64 to target float type") * p - F::one();
1420 Ok(F::from_f64(1.414).expect("Failed to convert f64 to target float type") * x.atanh())
1421 }
1422}