use crate::error::{InterpolateError, InterpolateResult};
use scirs2_core::ndarray::{Array1, Array2, ArrayView1, ArrayView2, Axis};
use scirs2_core::numeric::{Float, FromPrimitive};
use scirs2_core::random::{rngs::StdRng, Rng, RngExt, SeedableRng};
use std::fmt::{Debug, Display};
use std::marker::PhantomData;
#[derive(Debug, Clone)]
pub struct VariationalSparseGP<F: Float> {
pub inducing_points: Array2<F>,
pub variational_mean: Array1<F>,
pub variational_cov_chol: Array2<F>,
pub kernel_params: KernelParameters<F>,
pub noise_variance: F,
pub elbo: F,
}
#[derive(Debug, Clone)]
pub struct KernelParameters<F: Float> {
pub signal_variance: F,
pub length_scales: Array1<F>,
pub kernel_type: KernelType,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum KernelType {
RBF,
Matern32,
Matern52,
RationalQuadratic,
}
impl<F> VariationalSparseGP<F>
where
F: Float
+ FromPrimitive
+ Debug
+ Display
+ std::iter::Sum
+ 'static
+ std::ops::AddAssign
+ scirs2_core::ndarray::ScalarOperand
+ std::ops::SubAssign
+ std::ops::DivAssign,
{
pub fn new(
inducing_points: Array2<F>,
kernel_params: KernelParameters<F>,
noise_variance: F,
) -> Self {
let n_inducing = inducing_points.nrows();
let variational_mean = Array1::zeros(n_inducing);
let variational_cov_chol = Array2::eye(n_inducing);
Self {
inducing_points,
variational_mean,
variational_cov_chol,
kernel_params,
noise_variance,
elbo: F::neg_infinity(),
}
}
pub fn fit(
&mut self,
x_train: &ArrayView2<F>,
y_train: &ArrayView1<F>,
max_iter: usize,
_learning_rate: F,
tolerance: F,
) -> InterpolateResult<()> {
let _n_data = x_train.nrows();
let n_inducing = self.inducing_points.nrows();
for _iter in 0..max_iter {
let k_uu = self.compute_kernel_matrix(
&self.inducing_points.view(),
&self.inducing_points.view(),
)?;
let k_fu = self.compute_kernel_matrix(x_train, &self.inducing_points.view())?;
let mut k_uu_jitter = k_uu.clone();
let jitter = F::from_f64(1e-6).unwrap_or_else(|| F::epsilon());
for i in 0..n_inducing {
k_uu_jitter[[i, i]] += jitter;
}
let l_uu = self.cholesky_decomposition(&k_uu_jitter)?;
let a_matrix = self.solve_triangular_system(&l_uu, &k_fu.t().to_owned())?;
let sigma_inv = F::one() / self.noise_variance;
let lambda = Array2::eye(n_inducing) + &(a_matrix.dot(&a_matrix.t()) * sigma_inv);
let y_centered = y_train.to_owned();
let ata_y = a_matrix.dot(&y_centered);
self.variational_mean = self.solve_system(&lambda, &ata_y)? * sigma_inv;
self.variational_cov_chol = self.cholesky_decomposition(&lambda)?;
let new_elbo = self.compute_elbo(x_train, y_train, &k_uu, &k_fu, &a_matrix)?;
if _iter > 0 && (new_elbo - self.elbo).abs() < tolerance {
break;
}
self.elbo = new_elbo;
}
Ok(())
}
pub fn predict(&self, xtest: &ArrayView2<F>) -> InterpolateResult<(Array1<F>, Array1<F>)> {
let n_test = xtest.nrows();
let k_uu =
self.compute_kernel_matrix(&self.inducing_points.view(), &self.inducing_points.view())?;
let k_su = self.compute_kernel_matrix(xtest, &self.inducing_points.view())?;
let k_ss_diag = self.compute_kernel_diagonal(xtest)?;
let mut k_uu_jitter = k_uu.clone();
let jitter = F::from_f64(1e-6).unwrap_or_else(|| F::epsilon());
for i in 0..k_uu_jitter.nrows() {
k_uu_jitter[[i, i]] += jitter;
}
let l_uu = self.cholesky_decomposition(&k_uu_jitter)?;
let alpha = self.solve_triangular_system(&l_uu, &k_su.t().to_owned())?;
let mean = k_su.dot(&self.variational_mean);
let mut variance = Array1::zeros(n_test);
for i in 0..n_test {
let _k_s = k_su.row(i);
let alpha_i = alpha.column(i);
let var_term1 = k_ss_diag[i];
let var_term2 = alpha_i.dot(&alpha_i);
let var_term3 = self.compute_trace_correction(&alpha_i)?;
variance[i] = var_term1 - var_term2 + var_term3 + self.noise_variance;
}
Ok((mean, variance))
}
fn compute_kernel_matrix(
&self,
x1: &ArrayView2<F>,
x2: &ArrayView2<F>,
) -> InterpolateResult<Array2<F>> {
let n1 = x1.nrows();
let n2 = x2.nrows();
let mut k = Array2::zeros((n1, n2));
for i in 0..n1 {
for j in 0..n2 {
let distsq = self.squared_distance(&x1.row(i), &x2.row(j))?;
k[[i, j]] = self.kernel_function(distsq);
}
}
Ok(k)
}
fn compute_kernel_diagonal(&self, x: &ArrayView2<F>) -> InterpolateResult<Array1<F>> {
let n = x.nrows();
let mut diag = Array1::zeros(n);
for i in 0..n {
diag[i] = self.kernel_params.signal_variance;
}
Ok(diag)
}
fn squared_distance(&self, x1: &ArrayView1<F>, x2: &ArrayView1<F>) -> InterpolateResult<F> {
if x1.len() != x2.len() || x1.len() != self.kernel_params.length_scales.len() {
return Err(InterpolateError::DimensionMismatch(
"Point dimensions must match length scales".to_string(),
));
}
let mut distsq = F::zero();
for i in 0..x1.len() {
let diff = x1[i] - x2[i];
let scaled_diff = diff / self.kernel_params.length_scales[i];
distsq += scaled_diff * scaled_diff;
}
Ok(distsq)
}
fn kernel_function(&self, distsq: F) -> F {
match self.kernel_params.kernel_type {
KernelType::RBF => {
self.kernel_params.signal_variance
* (-F::from_f64(0.5).expect("Failed to convert f64 to target float type")
* distsq)
.exp()
}
KernelType::Matern32 => {
let dist = distsq.sqrt();
let sqrt3 = F::from_f64(3.0_f64.sqrt())
.expect("Failed to convert f64 to target float type");
let term = sqrt3 * dist;
self.kernel_params.signal_variance * (F::one() + term) * (-term).exp()
}
KernelType::Matern52 => {
let dist = distsq.sqrt();
let sqrt5 = F::from_f64(5.0_f64.sqrt())
.expect("Failed to convert f64 to target float type");
let term = sqrt5 * dist;
let term2 = F::from_f64(5.0).expect("Failed to convert f64 to target float type")
* distsq
/ F::from_f64(3.0).expect("Failed to convert f64 to target float type");
self.kernel_params.signal_variance * (F::one() + term + term2) * (-term).exp()
}
KernelType::RationalQuadratic => {
let alpha = F::from_f64(1.0).expect("Failed to convert f64 to target float type"); self.kernel_params.signal_variance
* (F::one()
+ distsq
/ (F::from_f64(2.0)
.expect("Failed to convert f64 to target float type")
* alpha))
.powf(-alpha)
}
}
}
fn cholesky_decomposition(&self, matrix: &Array2<F>) -> InterpolateResult<Array2<F>> {
let n = matrix.nrows();
let mut l = Array2::zeros((n, n));
for i in 0..n {
for j in 0..=i {
if i == j {
let mut sum = F::zero();
for k in 0..j {
sum += l[[j, k]] * l[[j, k]];
}
let diag_val = matrix[[j, j]] - sum;
if diag_val <= F::zero() {
return Err(InterpolateError::ComputationError(
"Matrix is not positive definite".to_string(),
));
}
l[[j, j]] = diag_val.sqrt();
} else {
let mut sum = F::zero();
for k in 0..j {
sum += l[[i, k]] * l[[j, k]];
}
l[[i, j]] = (matrix[[i, j]] - sum) / l[[j, j]];
}
}
}
Ok(l)
}
fn solve_triangular_system(
&self,
l: &Array2<F>,
b: &Array2<F>,
) -> InterpolateResult<Array2<F>> {
let n = l.nrows();
let m = b.ncols();
let mut x = Array2::zeros((n, m));
for col in 0..m {
for i in 0..n {
let mut sum = F::zero();
for j in 0..i {
sum += l[[i, j]] * x[[j, col]];
}
x[[i, col]] = (b[[i, col]] - sum) / l[[i, i]];
}
}
Ok(x)
}
fn solve_system(&self, a: &Array2<F>, b: &Array1<F>) -> InterpolateResult<Array1<F>> {
let n = a.nrows();
let mut x = Array1::zeros(n);
let mut aug = Array2::zeros((n, n + 1));
for i in 0..n {
for j in 0..n {
aug[[i, j]] = a[[i, j]];
}
aug[[i, n]] = b[i];
}
for i in 0..n {
let mut max_row = i;
for k in i + 1..n {
if aug[[k, i]].abs() > aug[[max_row, i]].abs() {
max_row = k;
}
}
for j in 0..=n {
let temp = aug[[max_row, j]];
aug[[max_row, j]] = aug[[i, j]];
aug[[i, j]] = temp;
}
for k in i + 1..n {
if aug[[i, i]].abs() < F::epsilon() {
return Err(InterpolateError::ComputationError(
"Matrix is singular".to_string(),
));
}
let c = aug[[k, i]] / aug[[i, i]];
for j in i..=n {
let aug_i_j = aug[[i, j]];
aug[[k, j]] -= c * aug_i_j;
}
}
}
for i in (0..n).rev() {
let mut sum = aug[[i, n]];
for j in i + 1..n {
sum -= aug[[i, j]] * x[j];
}
x[i] = sum / aug[[i, i]];
}
Ok(x)
}
fn compute_trace_correction(&self, alpha: &ArrayView1<F>) -> InterpolateResult<F> {
let trace_term = alpha.dot(alpha)
* F::from_f64(0.1).expect("Failed to convert f64 to target float type");
Ok(trace_term)
}
fn compute_elbo(
&self,
x_train: &ArrayView2<F>,
y_train: &ArrayView1<F>,
_k_uu: &Array2<F>,
k_fu: &Array2<F>,
_a_matrix: &Array2<F>,
) -> InterpolateResult<F> {
let n_data = x_train.nrows();
let n_inducing = self.inducing_points.nrows();
let y_pred = k_fu.dot(&self.variational_mean);
let residuals = y_train - &y_pred;
let data_fit = -F::from_f64(0.5).expect("Failed to convert f64 to target float type")
* residuals.dot(&residuals)
/ self.noise_variance;
let log_det_term = F::from_f64(n_inducing as f64 * (2.0 * std::f64::consts::PI).ln())
.expect("Operation failed");
let trace_term = self.variational_cov_chol.diag().mapv(|x| x.ln()).sum();
let kl_penalty = -F::from_f64(0.5).expect("Failed to convert f64 to target float type")
* (log_det_term
+ F::from_f64(2.0).expect("Failed to convert f64 to target float type")
* trace_term);
let noise_term = -F::from_f64(0.5 * n_data as f64)
.expect("Failed to convert f64 to target float type")
* self.noise_variance.ln();
Ok(data_fit + kl_penalty + noise_term)
}
}
#[derive(Debug, Clone)]
pub struct StatisticalSpline<F: Float> {
pub coefficients: Array1<F>,
pub knots: Array1<F>,
pub coef_covariance: Array2<F>,
pub residual_std_error: F,
pub degrees_of_freedom: usize,
}
impl<F> StatisticalSpline<F>
where
F: Float
+ FromPrimitive
+ Debug
+ Display
+ scirs2_core::ndarray::ScalarOperand
+ std::ops::AddAssign
+ std::ops::SubAssign
+ std::ops::MulAssign
+ std::ops::DivAssign,
{
pub fn fit(
x: &ArrayView1<F>,
y: &ArrayView1<F>,
n_knots: usize,
smoothing_parameter: F,
) -> InterpolateResult<Self> {
let n = x.len();
if n != y.len() {
return Err(InterpolateError::DimensionMismatch(
"x and y must have same length".to_string(),
));
}
let x_min = x.fold(F::infinity(), |a, &b| a.min(b));
let x_max = x.fold(F::neg_infinity(), |a, &b| a.max(b));
let mut knots = Array1::zeros(n_knots);
for i in 0..n_knots {
let t = F::from_usize(i).expect("Failed to convert usize to float")
/ F::from_usize(n_knots - 1).expect("Failed to convert usize to float");
knots[i] = x_min + t * (x_max - x_min);
}
let design_matrix = Self::build_bspline_matrix(x, &knots)?;
let penalty_matrix = Self::build_penalty_matrix(n_knots)?;
let penalized_matrix =
design_matrix.t().dot(&design_matrix) + &(penalty_matrix.clone() * smoothing_parameter);
let rhs = design_matrix.t().dot(y);
let coefficients = Self::solve_penalized_system(&penalized_matrix, &rhs)?;
let fitted_values = design_matrix.dot(&coefficients);
let residuals = y - &fitted_values;
let rss = residuals.dot(&residuals);
let dof = n - Self::effective_degrees_of_freedom(&design_matrix, smoothing_parameter)?;
let residual_std_error =
(rss / F::from_usize(dof).expect("Failed to convert usize to float")).sqrt();
let coef_covariance = Self::compute_coefficient_covariance(
&design_matrix,
&penalty_matrix,
smoothing_parameter,
residual_std_error,
)?;
Ok(Self {
coefficients,
knots,
coef_covariance,
residual_std_error,
degrees_of_freedom: dof,
})
}
pub fn predict_with_bands(
&self,
x_new: &ArrayView1<F>,
confidence_level: F,
) -> InterpolateResult<(Array1<F>, Array1<F>, Array1<F>, Array1<F>, Array1<F>)> {
let design_new = Self::build_bspline_matrix(x_new, &self.knots)?;
let predictions = design_new.dot(&self.coefficients);
let mut std_errors = Array1::zeros(x_new.len());
let mut prediction_std_errors = Array1::zeros(x_new.len());
for i in 0..x_new.len() {
let x_row = design_new.row(i);
let variance = x_row.dot(&self.coef_covariance.dot(&x_row));
std_errors[i] = variance.sqrt();
prediction_std_errors[i] =
(variance + self.residual_std_error * self.residual_std_error).sqrt();
}
let _alpha = F::one() - confidence_level;
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);
let conf_upper = &predictions + &(std_errors * t_crit);
let pred_lower = &predictions - &(prediction_std_errors.clone() * t_crit);
let pred_upper = &predictions + &(prediction_std_errors * t_crit);
Ok((predictions, conf_lower, conf_upper, pred_lower, pred_upper))
}
fn build_bspline_matrix(x: &ArrayView1<F>, knots: &Array1<F>) -> InterpolateResult<Array2<F>> {
let n = x.len();
let m = knots.len();
let mut matrix = Array2::zeros((n, m));
for i in 0..n {
for j in 0..m {
if j == 0 {
matrix[[i, j]] = F::one();
} else {
matrix[[i, j]] =
x[i].powf(F::from_usize(j).expect("Failed to convert usize to float"));
}
}
}
Ok(matrix)
}
fn build_penalty_matrix(_nknots: usize) -> InterpolateResult<Array2<F>> {
let mut penalty = Array2::zeros((_nknots, _nknots));
for i in 2.._nknots {
penalty[[i - 2, i - 2]] += F::one();
penalty[[i - 2, i - 1]] -=
F::from_f64(2.0).expect("Failed to convert f64 to target float type");
penalty[[i - 2, i]] += F::one();
penalty[[i - 1, i - 2]] -=
F::from_f64(2.0).expect("Failed to convert f64 to target float type");
penalty[[i - 1, i - 1]] +=
F::from_f64(4.0).expect("Failed to convert f64 to target float type");
penalty[[i - 1, i]] -=
F::from_f64(2.0).expect("Failed to convert f64 to target float type");
penalty[[i, i - 2]] += F::one();
penalty[[i, i - 1]] -=
F::from_f64(2.0).expect("Failed to convert f64 to target float type");
penalty[[i, i]] += F::one();
}
Ok(penalty)
}
fn solve_penalized_system(a: &Array2<F>, b: &Array1<F>) -> InterpolateResult<Array1<F>> {
let n = a.nrows();
let mut x = Array1::zeros(n);
for _iter in 0..100 {
let mut max_change = F::zero();
for i in 0..n {
let mut sum = b[i];
for j in 0..n {
if i != j {
sum -= a[[i, j]] * x[j];
}
}
let new_val = sum / a[[i, i]];
let change = (new_val - x[i]).abs();
if change > max_change {
max_change = change;
}
x[i] = new_val;
}
if max_change < F::from_f64(1e-8).expect("Failed to convert f64 to target float type") {
break;
}
}
Ok(x)
}
fn effective_degrees_of_freedom(
design: &Array2<F>,
smoothing_parameter: F,
) -> InterpolateResult<usize> {
let base_dof = design.ncols();
let penalty_reduction = (smoothing_parameter.ln()
* F::from_f64(0.1).expect("Failed to convert f64 to target float type"))
.exp();
let effective_dof = F::from_usize(base_dof).expect("Failed to convert usize to float")
* (F::one() - penalty_reduction);
Ok(effective_dof.to_usize().unwrap_or(base_dof))
}
fn compute_coefficient_covariance(
design: &Array2<F>,
penalty: &Array2<F>,
smoothing_parameter: F,
residual_std_error: F,
) -> InterpolateResult<Array2<F>> {
let penalized_matrix = design.t().dot(design) + &(penalty * smoothing_parameter);
let n = penalized_matrix.nrows();
let mut inv_matrix = Array2::eye(n);
for i in 0..n {
inv_matrix[[i, i]] = F::one() / penalized_matrix[[i, i]];
}
Ok(inv_matrix * residual_std_error * residual_std_error)
}
}
#[derive(Debug, Clone)]
pub struct AdvancedBootstrap<F: Float> {
pub block_size: usize,
pub method: BootstrapMethod,
pub n_samples: usize,
pub seed: Option<u64>,
pub _phantom: PhantomData<F>,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum BootstrapMethod {
Standard,
Block,
Residual,
Wild,
}
impl<F> AdvancedBootstrap<F>
where
F: Float + FromPrimitive + Debug + Display + std::iter::Sum,
{
pub fn new(method: BootstrapMethod, n_samples: usize, blocksize: usize) -> Self {
Self {
block_size: blocksize,
method,
n_samples,
seed: None,
_phantom: PhantomData,
}
}
pub fn bootstrap_interpolate<InterpolatorFn>(
&self,
x: &ArrayView1<F>,
y: &ArrayView1<F>,
x_new: &ArrayView1<F>,
interpolator_factory: InterpolatorFn,
) -> InterpolateResult<(Array1<F>, Array1<F>, Array1<F>)>
where
InterpolatorFn:
Fn(&ArrayView1<F>, &ArrayView1<F>, &ArrayView1<F>) -> InterpolateResult<Array1<F>>,
{
let _n = x.len();
let m = x_new.len();
let mut rng = match self.seed {
Some(seed) => StdRng::seed_from_u64(seed),
None => StdRng::seed_from_u64(42),
};
let mut bootstrap_results = Array2::zeros((self.n_samples, m));
for sample in 0..self.n_samples {
let (x_boot, y_boot) = match self.method {
BootstrapMethod::Standard => self.standard_bootstrap(x, y, &mut rng)?,
BootstrapMethod::Block => self.block_bootstrap(x, y, &mut rng)?,
BootstrapMethod::Residual => {
self.residual_bootstrap(x, y, &mut rng, &interpolator_factory)?
}
BootstrapMethod::Wild => {
self.wild_bootstrap(x, y, &mut rng, &interpolator_factory)?
}
};
let y_pred = interpolator_factory(&x_boot.view(), &y_boot.view(), x_new)?;
bootstrap_results.row_mut(sample).assign(&y_pred);
}
let mean = bootstrap_results
.mean_axis(Axis(0))
.expect("Failed to compute mean along axis");
let _std_dev = bootstrap_results.std_axis(Axis(0), F::zero());
let mut conf_lower = Array1::zeros(m);
let mut conf_upper = Array1::zeros(m);
for i in 0..m {
let mut column: Vec<F> = bootstrap_results.column(i).to_vec();
column.sort_by(|a, b| a.partial_cmp(b).expect("Float comparison failed"));
let lower_idx = ((F::from_f64(0.025)
.expect("Failed to convert f64 to target float type")
* F::from_usize(self.n_samples).expect("Failed to convert usize to float"))
.to_usize()
.expect("Failed to convert to usize"))
.min(self.n_samples - 1);
let upper_idx = ((F::from_f64(0.975)
.expect("Failed to convert f64 to target float type")
* F::from_usize(self.n_samples).expect("Failed to convert usize to float"))
.to_usize()
.expect("Failed to convert to usize"))
.min(self.n_samples - 1);
conf_lower[i] = column[lower_idx];
conf_upper[i] = column[upper_idx];
}
Ok((mean, conf_lower, conf_upper))
}
fn standard_bootstrap(
&self,
x: &ArrayView1<F>,
y: &ArrayView1<F>,
rng: &mut StdRng,
) -> InterpolateResult<(Array1<F>, Array1<F>)> {
let n = x.len();
let mut indices = Vec::with_capacity(n);
for _ in 0..n {
indices.push(rng.random_range(0..n));
}
let x_boot = Array1::from_iter(indices.iter().map(|&i| x[i]));
let y_boot = Array1::from_iter(indices.iter().map(|&i| y[i]));
Ok((x_boot, y_boot))
}
fn block_bootstrap(
&self,
x: &ArrayView1<F>,
y: &ArrayView1<F>,
rng: &mut StdRng,
) -> InterpolateResult<(Array1<F>, Array1<F>)> {
let n = x.len();
let n_blocks = n.div_ceil(self.block_size);
let mut x_boot = Vec::new();
let mut y_boot = Vec::new();
for _ in 0..n_blocks {
let start_idx = rng.random_range(0..=(n.saturating_sub(self.block_size)));
let end_idx = (start_idx + self.block_size).min(n);
for i in start_idx..end_idx {
x_boot.push(x[i]);
y_boot.push(y[i]);
if x_boot.len() >= n {
break;
}
}
if x_boot.len() >= n {
break;
}
}
x_boot.truncate(n);
y_boot.truncate(n);
Ok((Array1::from(x_boot), Array1::from(y_boot)))
}
fn residual_bootstrap<InterpolatorFn>(
&self,
x: &ArrayView1<F>,
y: &ArrayView1<F>,
rng: &mut StdRng,
interpolator_factory: &InterpolatorFn,
) -> InterpolateResult<(Array1<F>, Array1<F>)>
where
InterpolatorFn:
Fn(&ArrayView1<F>, &ArrayView1<F>, &ArrayView1<F>) -> InterpolateResult<Array1<F>>,
{
let n = x.len();
let y_fitted = interpolator_factory(x, y, x)?;
let residuals = y - &y_fitted;
let mut resampled_residuals = Array1::zeros(n);
for i in 0..n {
let idx = rng.random_range(0..n);
resampled_residuals[i] = residuals[idx];
}
let y_boot = y_fitted + resampled_residuals;
Ok((x.to_owned(), y_boot))
}
fn wild_bootstrap<InterpolatorFn>(
&self,
x: &ArrayView1<F>,
y: &ArrayView1<F>,
rng: &mut StdRng,
interpolator_factory: &InterpolatorFn,
) -> InterpolateResult<(Array1<F>, Array1<F>)>
where
InterpolatorFn:
Fn(&ArrayView1<F>, &ArrayView1<F>, &ArrayView1<F>) -> InterpolateResult<Array1<F>>,
{
let n = x.len();
let y_fitted = interpolator_factory(x, y, x)?;
let residuals = y - &y_fitted;
let mut multipliers = Array1::zeros(n);
for i in 0..n {
multipliers[i] = if rng.random::<f64>() < 0.5 {
F::from_f64(-1.0).expect("Failed to convert f64 to target float type")
} else {
F::one()
};
}
let y_boot = y_fitted + &residuals * &multipliers;
Ok((x.to_owned(), y_boot))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_variational_sparse_gp() {
let x_train = Array2::from_shape_vec((5, 1), vec![0.0, 1.0, 2.0, 3.0, 4.0])
.expect("Operation failed");
let y_train = Array1::from(vec![0.0, 1.0, 4.0, 9.0, 16.0]);
let kernel_params = KernelParameters {
signal_variance: 1.0,
length_scales: Array1::from(vec![1.0]),
kernel_type: KernelType::RBF,
};
let inducing_points =
Array2::from_shape_vec((3, 1), vec![0.0, 2.0, 4.0]).expect("Operation failed");
let mut sparse_gp = VariationalSparseGP::new(
inducing_points,
kernel_params,
0.1, );
let result = sparse_gp.fit(&x_train.view(), &y_train.view(), 10, 0.01, 1e-6);
assert!(result.is_ok());
let xtest = Array2::from_shape_vec((3, 1), vec![0.5, 1.5, 2.5]).expect("Operation failed");
let (mean, variance) = sparse_gp.predict(&xtest.view()).expect("Operation failed");
assert_eq!(mean.len(), 3);
assert_eq!(variance.len(), 3);
assert!(variance.iter().all(|&v| v > 0.0));
}
#[test]
fn test_statistical_spline() {
let x = Array1::from(vec![0.0, 1.0, 2.0, 3.0, 4.0]);
let y = Array1::from(vec![0.0, 1.0, 4.0, 9.0, 16.0]);
let spline =
StatisticalSpline::fit(&x.view(), &y.view(), 5, 0.1).expect("Operation failed");
let x_new = Array1::from(vec![0.5, 1.5, 2.5, 3.5]);
let (pred, conf_lower, conf_upper, pred_lower, pred_upper) = spline
.predict_with_bands(&x_new.view(), 0.95)
.expect("Operation failed");
assert_eq!(pred.len(), 4);
assert!(conf_lower
.iter()
.zip(conf_upper.iter())
.all(|(&l, &u)| l < u));
assert!(pred_lower
.iter()
.zip(pred_upper.iter())
.all(|(&l, &u)| l < u));
assert!(conf_lower
.iter()
.zip(pred_lower.iter())
.all(|(&c, &p)| c >= p));
assert!(conf_upper
.iter()
.zip(pred_upper.iter())
.all(|(&c, &p)| c <= p));
}
#[test]
fn test_advanced_bootstrap() {
let x = Array1::from(vec![0.0, 1.0, 2.0, 3.0, 4.0]);
let y = Array1::from(vec![0.0, 1.0, 4.0, 9.0, 16.0]);
let x_new = Array1::from(vec![0.5, 1.5, 2.5]);
let bootstrap = AdvancedBootstrap::new(BootstrapMethod::Standard, 100, 2);
let interpolator =
|x_data: &ArrayView1<f64>, y_data: &ArrayView1<f64>, x_pred: &ArrayView1<f64>| {
let mut result = Array1::zeros(x_pred.len());
for (i, &x_val) in x_pred.iter().enumerate() {
if x_val <= x_data[0] {
result[i] = y_data[0];
} else if x_val >= x_data[x_data.len() - 1] {
result[i] = y_data[y_data.len() - 1];
} else {
for j in 0..x_data.len() - 1 {
if x_val >= x_data[j] && x_val <= x_data[j + 1] {
let t = (x_val - x_data[j]) / (x_data[j + 1] - x_data[j]);
result[i] = y_data[j] + t * (y_data[j + 1] - y_data[j]);
break;
}
}
}
}
Ok(result)
};
let (mean, lower, upper) = bootstrap
.bootstrap_interpolate(&x.view(), &y.view(), &x_new.view(), interpolator)
.expect("Bootstrap interpolation failed");
assert_eq!(mean.len(), 3);
assert!(lower.iter().zip(upper.iter()).all(|(&l, &u)| l <= u));
}
}
#[derive(Debug, Clone)]
pub struct SavitzkyGolayFilter<F: Float> {
pub window_length: usize,
pub polynomial_order: usize,
pub derivative_order: usize,
pub _phantom: PhantomData<F>,
}
impl<F> SavitzkyGolayFilter<F>
where
F: Float + FromPrimitive + Debug + std::iter::Sum + 'static,
{
pub fn new(
window_length: usize,
polynomial_order: usize,
derivative_order: usize,
) -> InterpolateResult<Self> {
if window_length.is_multiple_of(2) {
return Err(InterpolateError::InvalidValue(
"Window _length must be odd".to_string(),
));
}
if polynomial_order >= window_length {
return Err(InterpolateError::InvalidValue(
"Polynomial _order must be less than window _length".to_string(),
));
}
if derivative_order > polynomial_order {
return Err(InterpolateError::InvalidValue(
"Derivative _order cannot exceed polynomial _order".to_string(),
));
}
Ok(Self {
window_length,
polynomial_order,
derivative_order: 0,
_phantom: PhantomData,
})
}
pub fn filter(&self, y: &ArrayView1<F>) -> InterpolateResult<Array1<F>> {
let n = y.len();
if n < self.window_length {
return Err(InterpolateError::InvalidValue(
"Data length must be at least window length".to_string(),
));
}
let mut result = Array1::zeros(n);
let half_window = self.window_length / 2;
let coeffs = self.compute_coefficients()?;
for i in 0..n {
let mut sum = F::zero();
for j in 0..self.window_length {
let data_idx = if i < half_window {
j.min(n - 1)
} else if i >= n - half_window {
(n - self.window_length + j).max(0).min(n - 1)
} else {
i - half_window + j
};
sum = sum + coeffs[j] * y[data_idx];
}
result[i] = sum;
}
Ok(result)
}
fn compute_coefficients(&self) -> InterpolateResult<Array1<F>> {
let m = self.window_length;
let n = self.polynomial_order + 1;
let half_window = (m - 1) / 2;
let mut design = Array2::<F>::zeros((m, n));
for i in 0..m {
let x = F::from_isize(i as isize - half_window as isize)
.expect("Failed to convert isize to float");
for j in 0..n {
design[[i, j]] = x.powi(j as i32);
}
}
let xtx = design.t().dot(&design);
let mut rhs = Array1::<F>::zeros(n);
let mut factorial = F::one();
for i in 1..=self.derivative_order {
factorial = factorial * F::from_usize(i).expect("Failed to convert usize to float");
}
rhs[self.derivative_order] = factorial;
let coeffs_polynomial = self.solve_linear_system(&xtx, &rhs)?;
let filter_coeffs = design.dot(&coeffs_polynomial);
Ok(filter_coeffs)
}
fn solve_linear_system(&self, a: &Array2<F>, b: &Array1<F>) -> InterpolateResult<Array1<F>> {
let n = a.nrows();
let mut x = Array1::<F>::zeros(n);
for i in 0..n {
if a[[i, i]].abs()
< F::from_f64(1e-12).expect("Failed to convert f64 to target float type")
{
return Err(InterpolateError::ComputationError(
"Singular matrix in Savitzky-Golay computation".to_string(),
));
}
x[i] = b[i] / a[[i, i]];
}
Ok(x)
}
}
#[derive(Debug, Clone)]
pub struct BcaBootstrap<F: Float> {
pub n_bootstrap: usize,
pub confidence_level: F,
pub seed: Option<u64>,
}
impl<F> BcaBootstrap<F>
where
F: Float + FromPrimitive + Debug + std::iter::Sum,
{
pub fn new(n_bootstrap: usize, confidence_level: F, seed: Option<u64>) -> Self {
Self {
n_bootstrap,
confidence_level,
seed,
}
}
pub fn confidence_intervals<G>(
&self,
x: &ArrayView1<F>,
y: &ArrayView1<F>,
x_new: &ArrayView1<F>,
interpolator: G,
) -> InterpolateResult<(Array1<F>, Array1<F>, Array1<F>)>
where
G: Fn(&ArrayView1<F>, &ArrayView1<F>, &ArrayView1<F>) -> InterpolateResult<Array1<F>>,
{
let n_data = x.len();
let n_pred = x_new.len();
let mut rng = match self.seed {
Some(seed) => StdRng::seed_from_u64(seed),
None => {
let mut rng = scirs2_core::random::rng();
StdRng::from_rng(&mut rng)
}
};
let mut bootstrap_results = Array2::<F>::zeros((self.n_bootstrap, n_pred));
for b in 0..self.n_bootstrap {
let mut x_boot = Array1::<F>::zeros(n_data);
let mut y_boot = Array1::<F>::zeros(n_data);
for i in 0..n_data {
let idx = rng.random_range(0..n_data);
x_boot[i] = x[idx];
y_boot[i] = y[idx];
}
let pred = interpolator(&x_boot.view(), &y_boot.view(), x_new)?;
bootstrap_results.row_mut(b).assign(&pred);
}
let original_pred = interpolator(x, y, x_new)?;
let bias_correction = self.compute_bias_correction(&bootstrap_results, &original_pred)?;
let acceleration = self.compute_acceleration(x, y, x_new, &interpolator)?;
let alpha = (F::one() - self.confidence_level)
/ F::from_f64(2.0).expect("Failed to convert f64 to target float type");
let z_alpha = self.inverse_normal_cdf(alpha)?;
let z_1_alpha = self.inverse_normal_cdf(F::one() - alpha)?;
let mut lower = Array1::<F>::zeros(n_pred);
let mut upper = Array1::<F>::zeros(n_pred);
for i in 0..n_pred {
let bc = bias_correction[i];
let acc = acceleration[i];
let alpha1 =
self.normal_cdf(bc + (bc + z_alpha) / (F::one() - acc * (bc + z_alpha)))?;
let alpha2 =
self.normal_cdf(bc + (bc + z_1_alpha) / (F::one() - acc * (bc + z_1_alpha)))?;
let mut column: Vec<F> = bootstrap_results.column(i).to_vec();
column.sort_by(|a, b| a.partial_cmp(b).expect("Float comparison failed"));
let idx1 = ((alpha1
* F::from_usize(self.n_bootstrap).expect("Failed to convert usize to float"))
.floor()
.to_usize()
.expect("Failed to convert to usize"))
.min(self.n_bootstrap - 1);
let idx2 = ((alpha2
* F::from_usize(self.n_bootstrap).expect("Failed to convert usize to float"))
.floor()
.to_usize()
.expect("Failed to convert to usize"))
.min(self.n_bootstrap - 1);
lower[i] = column[idx1];
upper[i] = column[idx2];
}
Ok((original_pred, lower, upper))
}
fn compute_bias_correction(
&self,
bootstrap_results: &Array2<F>,
original_pred: &Array1<F>,
) -> InterpolateResult<Array1<F>> {
let n_pred = original_pred.len();
let mut bias_correction = Array1::<F>::zeros(n_pred);
for i in 0..n_pred {
let column = bootstrap_results.column(i);
let count_less = column.iter().filter(|&&val| val < original_pred[i]).count();
let proportion = F::from_usize(count_less).expect("Failed to convert usize to float")
/ F::from_usize(self.n_bootstrap).expect("Failed to convert usize to float");
bias_correction[i] = self.inverse_normal_cdf(proportion)?;
}
Ok(bias_correction)
}
fn compute_acceleration<G>(
&self,
x: &ArrayView1<F>,
y: &ArrayView1<F>,
x_new: &ArrayView1<F>,
interpolator: &G,
) -> InterpolateResult<Array1<F>>
where
G: Fn(&ArrayView1<F>, &ArrayView1<F>, &ArrayView1<F>) -> InterpolateResult<Array1<F>>,
{
let n_data = x.len();
let n_pred = x_new.len();
let mut jackknife_results = Array2::<F>::zeros((n_data, n_pred));
for i in 0..n_data {
let mut x_jack = Array1::<F>::zeros(n_data - 1);
let mut y_jack = Array1::<F>::zeros(n_data - 1);
let mut idx = 0;
for j in 0..n_data {
if j != i {
x_jack[idx] = x[j];
y_jack[idx] = y[j];
idx += 1;
}
}
let pred = interpolator(&x_jack.view(), &y_jack.view(), x_new)?;
jackknife_results.row_mut(i).assign(&pred);
}
let jack_mean = jackknife_results
.mean_axis(Axis(0))
.expect("Failed to compute mean along axis");
let mut acceleration = Array1::<F>::zeros(n_pred);
for i in 0..n_pred {
let mut sum_cubed = F::zero();
let mut sum_squared = F::zero();
for j in 0..n_data {
let diff = jack_mean[i] - jackknife_results[[j, i]];
sum_cubed = sum_cubed + diff * diff * diff;
sum_squared = sum_squared + diff * diff;
}
if sum_squared > F::zero() {
acceleration[i] = sum_cubed
/ (F::from_f64(6.0).expect("Failed to convert f64 to target float type")
* sum_squared.powf(
F::from_f64(1.5).expect("Failed to convert f64 to target float type"),
));
}
}
Ok(acceleration)
}
fn normal_cdf(&self, x: F) -> InterpolateResult<F> {
let result = (F::one()
+ (x / F::from_f64(1.414).expect("Failed to convert f64 to target float type")).tanh())
/ F::from_f64(2.0).expect("Failed to convert f64 to target float type");
Ok(result)
}
fn inverse_normal_cdf(&self, p: F) -> InterpolateResult<F> {
if p <= F::zero() || p >= F::one() {
return Ok(F::zero());
}
let x =
F::from_f64(2.0).expect("Failed to convert f64 to target float type") * p - F::one();
Ok(F::from_f64(1.414).expect("Failed to convert f64 to target float type") * x.atanh())
}
}