#![allow(clippy::too_many_arguments)]
#![allow(dead_code)]
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 scirs2_core::random::{Distribution, Normal, StandardNormal};
use statrs::statistics::Statistics;
use std::fmt::{Debug, Display};
#[derive(Debug, Clone)]
pub struct BootstrapConfig {
pub n_samples: usize,
pub confidence_level: f64,
pub seed: Option<u64>,
}
impl Default for BootstrapConfig {
fn default() -> Self {
Self {
n_samples: 1000,
confidence_level: 0.95,
seed: None,
}
}
}
#[derive(Debug, Clone)]
pub struct BootstrapResult<T: Float> {
pub estimate: Array1<T>,
pub lower_bound: Array1<T>,
pub upper_bound: Array1<T>,
pub std_error: Array1<T>,
}
pub struct BootstrapInterpolator<T: Float> {
config: BootstrapConfig,
interpolator_factory:
Box<dyn Fn(&ArrayView1<T>, &ArrayView1<T>) -> InterpolateResult<Box<dyn Fn(T) -> T>>>,
}
impl<T: Float + FromPrimitive + Debug + Display + std::iter::Sum> BootstrapInterpolator<T> {
pub fn new<F>(config: BootstrapConfig, interpolator_factory: F) -> Self
where
F: Fn(&ArrayView1<T>, &ArrayView1<T>) -> InterpolateResult<Box<dyn Fn(T) -> T>> + 'static,
{
Self {
config,
interpolator_factory: Box::new(interpolator_factory),
}
}
pub fn interpolate(
&self,
x: &ArrayView1<T>,
y: &ArrayView1<T>,
xnew: &ArrayView1<T>,
) -> InterpolateResult<BootstrapResult<T>> {
if x.len() != y.len() {
return Err(InterpolateError::DimensionMismatch(
"x and y must have the same length".to_string(),
));
}
let n = x.len();
let m = xnew.len();
let mut rng = match self.config.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::<T>::zeros((self.config.n_samples, m));
for i in 0..self.config.n_samples {
let indices: Vec<usize> = (0..n).map(|_| rng.random_range(0..n)).collect();
let x_resampled = indices.iter().map(|&idx| x[idx]).collect::<Array1<_>>();
let y_resampled = indices.iter().map(|&idx| y[idx]).collect::<Array1<_>>();
let interpolator =
(self.interpolator_factory)(&x_resampled.view(), &y_resampled.view())?;
for (j, &x_val) in xnew.iter().enumerate() {
bootstrap_results[[i, j]] = interpolator(x_val);
}
}
let alpha = T::from(1.0 - self.config.confidence_level).expect("Operation failed");
let lower_percentile = alpha / T::from(2.0).expect("Operation failed");
let upper_percentile = T::one() - alpha / T::from(2.0).expect("Operation failed");
let mut estimate = Array1::zeros(m);
let mut lower_bound = Array1::zeros(m);
let mut upper_bound = Array1::zeros(m);
let mut std_error = Array1::zeros(m);
for j in 0..m {
let column = bootstrap_results.index_axis(Axis(1), j);
let mut sorted_col = column.to_vec();
sorted_col.sort_by(|a, b| a.partial_cmp(b).expect("Operation failed"));
let median_idx = self.config.n_samples / 2;
estimate[j] = sorted_col[median_idx];
let lower_idx = (lower_percentile
* T::from(self.config.n_samples).expect("Operation failed"))
.to_usize()
.expect("Operation failed");
let upper_idx = (upper_percentile
* T::from(self.config.n_samples).expect("Operation failed"))
.to_usize()
.expect("Operation failed");
lower_bound[j] = sorted_col[lower_idx];
upper_bound[j] = sorted_col[upper_idx];
let mean = column.mean().expect("Operation failed");
let variance = column
.iter()
.map(|&val| (val - mean) * (val - mean))
.sum::<T>()
/ T::from(self.config.n_samples - 1).expect("Operation failed");
std_error[j] = variance.sqrt();
}
Ok(BootstrapResult {
estimate,
lower_bound,
upper_bound,
std_error,
})
}
}
pub struct BayesianConfig<T: Float> {
pub prior_mean: Box<dyn Fn(T) -> T>,
pub prior_variance: T,
pub noise_variance: T,
pub length_scale: T,
pub n_posterior_samples: usize,
}
impl<T: Float + FromPrimitive> Default for BayesianConfig<T> {
fn default() -> Self {
Self {
prior_mean: Box::new(|_| T::zero()),
prior_variance: T::one(),
noise_variance: T::from(0.01).expect("Operation failed"),
length_scale: T::one(),
n_posterior_samples: 100,
}
}
}
impl<T: Float + FromPrimitive> BayesianConfig<T> {
pub fn with_length_scale(mut self, lengthscale: T) -> Self {
self.length_scale = lengthscale;
self
}
pub fn with_prior_variance(mut self, variance: T) -> Self {
self.prior_variance = variance;
self
}
pub fn with_noise_variance(mut self, variance: T) -> Self {
self.noise_variance = variance;
self
}
pub fn with_n_posterior_samples(mut self, nsamples: usize) -> Self {
self.n_posterior_samples = nsamples;
self
}
}
pub struct BayesianInterpolator<T: Float> {
config: BayesianConfig<T>,
x_obs: Array1<T>,
y_obs: Array1<T>,
}
impl<
T: Float
+ FromPrimitive
+ Debug
+ Display
+ std::ops::AddAssign
+ std::ops::SubAssign
+ std::ops::MulAssign
+ std::ops::DivAssign
+ std::ops::RemAssign,
> BayesianInterpolator<T>
{
pub fn new(
x: &ArrayView1<T>,
y: &ArrayView1<T>,
config: BayesianConfig<T>,
) -> InterpolateResult<Self> {
if x.len() != y.len() {
return Err(InterpolateError::DimensionMismatch(
"x and y must have the same length".to_string(),
));
}
Ok(Self {
config,
x_obs: x.to_owned(),
y_obs: y.to_owned(),
})
}
pub fn posterior_mean(&self, xnew: &ArrayView1<T>) -> InterpolateResult<Array1<T>> {
let n = self.x_obs.len();
let m = xnew.len();
if n == 0 {
return Err(InterpolateError::invalid_input(
"No observed data points".to_string(),
));
}
let mut k_xx = Array2::<T>::zeros((n, n));
let length_scale = self.config.length_scale;
for i in 0..n {
for j in 0..n {
let dist_sq = (self.x_obs[i] - self.x_obs[j]).powi(2);
k_xx[[i, j]] = self.config.prior_variance
* (-dist_sq / (T::from(2.0).expect("Operation failed") * length_scale.powi(2)))
.exp();
if i == j {
k_xx[[i, j]] += self.config.noise_variance;
}
}
}
let weights = match self.solve_gp_system(&k_xx.view(), &self.y_obs.view()) {
Ok(w) => w,
Err(_) => {
let regularization = T::from(1e-6).expect("Operation failed");
for i in 0..n {
k_xx[[i, i]] += regularization;
}
self.solve_gp_system(&k_xx.view(), &self.y_obs.view())?
}
};
let mut k_star_x = Array2::<T>::zeros((m, n));
for i in 0..m {
for j in 0..n {
let dist_sq = (xnew[i] - self.x_obs[j]).powi(2);
k_star_x[[i, j]] = self.config.prior_variance
* (-dist_sq / (T::from(2.0).expect("Operation failed") * length_scale.powi(2)))
.exp();
}
}
let mut mean = Array1::zeros(m);
for i in 0..m {
let mut sum = T::zero();
for j in 0..n {
sum += k_star_x[[i, j]] * weights[j];
}
mean[i] = (self.config.prior_mean)(xnew[i]) + sum;
}
Ok(mean)
}
fn solve_gp_system(
&self,
k_matrix: &ArrayView2<T>,
y_obs: &ArrayView1<T>,
) -> InterpolateResult<Array1<T>> {
use crate::structured_matrix::solve_dense_system;
match solve_dense_system(k_matrix, y_obs) {
Ok(solution) => Ok(solution),
Err(_) => {
let n = y_obs.len();
let weights =
Array1::from_elem(n, T::one() / T::from(n).expect("Operation failed"));
Ok(weights)
}
}
}
pub fn posterior_samples(
&self,
xnew: &ArrayView1<T>,
n_samples: usize,
) -> InterpolateResult<Array2<T>> {
let mean = self.posterior_mean(xnew)?;
let m = xnew.len();
let mut samples = Array2::zeros((n_samples, m));
let mut rng = scirs2_core::random::rng();
let length_scale = T::one();
for j in 0..m {
let mut reduction_factor = T::zero();
let mut total_influence = T::zero();
for i in 0..self.x_obs.len() {
let dist_sq = (xnew[j] - self.x_obs[i]).powi(2);
let influence = (-dist_sq
/ (T::from(2.0).expect("Operation failed") * length_scale.powi(2)))
.exp();
total_influence += influence;
reduction_factor += influence * influence;
}
let noise_ratio = self.config.noise_variance / self.config.prior_variance;
let posterior_var = self.config.prior_variance
* (T::one()
- reduction_factor
/ (total_influence
+ noise_ratio
+ T::from(1e-8).expect("Operation failed")));
let std_dev = posterior_var
.max(T::from(1e-12).expect("Operation failed"))
.sqrt();
for i in 0..n_samples {
if let Ok(normal) = Normal::new(
mean[j].to_f64().expect("Operation failed"),
std_dev.to_f64().expect("Operation failed"),
) {
samples[[i, j]] = T::from(normal.sample(&mut rng)).expect("Operation failed");
} else {
samples[[i, j]] = mean[j];
}
}
}
Ok(samples)
}
}
pub struct QuantileInterpolator<T: Float> {
quantile: T,
bandwidth: T,
}
impl<T: Float + FromPrimitive + Debug + Display> QuantileInterpolator<T>
where
T: std::iter::Sum<T> + for<'a> std::iter::Sum<&'a T>,
{
pub fn new(quantile: T, bandwidth: T) -> InterpolateResult<Self> {
if quantile <= T::zero() || quantile >= T::one() {
return Err(InterpolateError::InvalidValue(
"Quantile must be between 0 and 1".to_string(),
));
}
Ok(Self {
quantile,
bandwidth,
})
}
pub fn interpolate(
&self,
x: &ArrayView1<T>,
y: &ArrayView1<T>,
xnew: &ArrayView1<T>,
) -> InterpolateResult<Array1<T>> {
if x.len() != y.len() {
return Err(InterpolateError::DimensionMismatch(
"x and y must have the same length".to_string(),
));
}
let n = x.len();
let m = xnew.len();
let mut result = Array1::zeros(m);
for j in 0..m {
let x_target = xnew[j];
let mut weights = Vec::with_capacity(n);
let mut weighted_values = Vec::with_capacity(n);
for i in 0..n {
let dist = (x[i] - x_target).abs() / self.bandwidth;
let weight = if dist < T::one() {
(T::one() - dist * dist * dist).powi(3) } else {
T::zero()
};
if weight > T::epsilon() {
weights.push(weight);
weighted_values.push((y[i], weight));
}
}
if weighted_values.is_empty() {
let nearest_idx = x
.iter()
.enumerate()
.min_by_key(|(_, &xi)| {
((xi - x_target).abs().to_f64().expect("Operation failed") * 1e6) as i64
})
.map(|(i_, _)| i_)
.expect("Operation failed");
result[j] = y[nearest_idx];
} else {
weighted_values.sort_by(|a, b| a.0.partial_cmp(&b.0).expect("Operation failed"));
let total_weight: T = weights.iter().sum();
let target_weight = self.quantile * total_weight;
let mut cumulative_weight = T::zero();
for (val, weight) in weighted_values {
cumulative_weight = cumulative_weight + weight;
if cumulative_weight >= target_weight {
result[j] = val;
break;
}
}
}
}
Ok(result)
}
}
pub struct RobustInterpolator<T: Float> {
tuning_constant: T,
max_iterations: usize,
tolerance: T,
}
impl<T: Float + FromPrimitive + Debug + Display> RobustInterpolator<T> {
pub fn new(_tuningconstant: T) -> Self {
Self {
tuning_constant: _tuningconstant,
max_iterations: 100,
tolerance: T::from(1e-6).expect("Operation failed"),
}
}
pub fn interpolate(
&self,
x: &ArrayView1<T>,
y: &ArrayView1<T>,
xnew: &ArrayView1<T>,
) -> InterpolateResult<Array1<T>> {
let n = x.len();
let m = xnew.len();
let mut result = Array1::zeros(m);
for j in 0..m {
let x_target = xnew[j];
let mut weights = vec![T::one(); n];
let mut prev_estimate = T::zero();
for _iter in 0..self.max_iterations {
let mut sum_w = T::zero();
let mut sum_wx = T::zero();
let mut sum_wy = T::zero();
let mut sum_wxx = T::zero();
let mut sum_wxy = T::zero();
for i in 0..n {
let w = weights[i];
let dx = x[i] - x_target;
sum_w = sum_w + w;
sum_wx = sum_wx + w * dx;
sum_wy = sum_wy + w * y[i];
sum_wxx = sum_wxx + w * dx * dx;
sum_wxy = sum_wxy + w * dx * y[i];
}
let det = sum_w * sum_wxx - sum_wx * sum_wx;
let estimate = if det.abs() > T::epsilon() {
(sum_wxx * sum_wy - sum_wx * sum_wxy) / det
} else {
sum_wy / sum_w
};
if (estimate - prev_estimate).abs() < self.tolerance {
result[j] = estimate;
break;
}
prev_estimate = estimate;
for i in 0..n {
let residual = y[i] - estimate;
let scaled_residual = residual / self.tuning_constant;
weights[i] = if scaled_residual.abs() <= T::one() {
T::one()
} else {
T::one() / scaled_residual.abs()
};
}
}
result[j] = prev_estimate;
}
Ok(result)
}
}
pub struct StochasticInterpolator<T: Float> {
correlation_length: T,
field_variance: T,
n_realizations: usize,
}
impl<T: Float + FromPrimitive + Debug + Display> StochasticInterpolator<T> {
pub fn new(correlation_length: T, field_variance: T, n_realizations: usize) -> Self {
Self {
correlation_length,
field_variance,
n_realizations,
}
}
pub fn interpolate_realizations(
&self,
x: &ArrayView1<T>,
y: &ArrayView1<T>,
xnew: &ArrayView1<T>,
) -> InterpolateResult<Array2<T>> {
let n = x.len();
let m = xnew.len();
let mut realizations = Array2::zeros((self.n_realizations, m));
let mut rng = scirs2_core::random::rng();
for r in 0..self.n_realizations {
for j in 0..m {
let x_target = xnew[j];
let mut weighted_sum = T::zero();
let mut weight_sum = T::zero();
for i in 0..n {
let dist = (x[i] - x_target).abs() / self.correlation_length;
let weight = (-dist * dist).exp();
weighted_sum = weighted_sum + weight * y[i];
weight_sum = weight_sum + weight;
}
let mean = if weight_sum > T::epsilon() {
weighted_sum / weight_sum
} else {
T::zero()
};
let std_dev = (self.field_variance
* (T::one() - weight_sum / T::from(n).expect("Operation failed")))
.sqrt();
let normal_sample: f64 = StandardNormal.sample(&mut rng);
let noise: T = T::from(normal_sample).expect("Operation failed") * std_dev;
realizations[[r, j]] = mean + noise;
}
}
Ok(realizations)
}
pub fn interpolate_statistics(
&self,
x: &ArrayView1<T>,
y: &ArrayView1<T>,
xnew: &ArrayView1<T>,
) -> InterpolateResult<(Array1<T>, Array1<T>)> {
let realizations = self.interpolate_realizations(x, y, xnew)?;
let mean = realizations.mean_axis(Axis(0)).expect("Operation failed");
let variance = realizations.var_axis(Axis(0), T::from(1.0).expect("Operation failed"));
Ok((mean, variance))
}
}
#[allow(dead_code)]
pub fn make_bootstrap_linear_interpolator<
T: Float + FromPrimitive + Debug + Display + 'static + std::iter::Sum,
>(
config: BootstrapConfig,
) -> BootstrapInterpolator<T> {
BootstrapInterpolator::new(config, |x, y| {
let x_owned = x.to_owned();
let y_owned = y.to_owned();
Ok(Box::new(move |xnew| {
if xnew <= x_owned[0] {
y_owned[0]
} else if xnew >= x_owned[x_owned.len() - 1] {
y_owned[y_owned.len() - 1]
} else {
let mut i = 0;
for j in 1..x_owned.len() {
if xnew <= x_owned[j] {
i = j - 1;
break;
}
}
let alpha = (xnew - x_owned[i]) / (x_owned[i + 1] - x_owned[i]);
y_owned[i] * (T::one() - alpha) + y_owned[i + 1] * alpha
}
}))
})
}
#[allow(dead_code)]
pub fn make_bayesian_interpolator<T: crate::traits::InterpolationFloat>(
x: &ArrayView1<T>,
y: &ArrayView1<T>,
) -> InterpolateResult<BayesianInterpolator<T>> {
BayesianInterpolator::new(x, y, BayesianConfig::default())
}
#[allow(dead_code)]
pub fn make_median_interpolator<T>(bandwidth: T) -> InterpolateResult<QuantileInterpolator<T>>
where
T: Float + FromPrimitive + Debug + Display + std::iter::Sum<T> + for<'a> std::iter::Sum<&'a T>,
{
QuantileInterpolator::new(T::from(0.5).expect("Operation failed"), bandwidth)
}
#[allow(dead_code)]
pub fn make_robust_interpolator<T: crate::traits::InterpolationFloat>() -> RobustInterpolator<T> {
RobustInterpolator::new(T::from(1.345).expect("Operation failed")) }
#[allow(dead_code)]
pub fn make_stochastic_interpolator<T: crate::traits::InterpolationFloat>(
correlation_length: T,
) -> StochasticInterpolator<T> {
StochasticInterpolator::new(correlation_length, T::one(), 100)
}
pub struct EnsembleInterpolator<T: Float> {
weights: Array1<T>,
methods: Vec<
Box<dyn Fn(&ArrayView1<T>, &ArrayView1<T>, &ArrayView1<T>) -> InterpolateResult<Array1<T>>>,
>,
normalize_weights: bool,
}
impl<T: crate::traits::InterpolationFloat> EnsembleInterpolator<T> {
pub fn new() -> Self {
Self {
weights: Array1::zeros(0),
methods: Vec::new(),
normalize_weights: true,
}
}
pub fn add_linear_method(mut self, weight: T) -> Self {
self.weights = if self.weights.is_empty() {
Array1::from_vec(vec![weight])
} else {
let mut new_weights = self.weights.to_vec();
new_weights.push(weight);
Array1::from_vec(new_weights)
};
self.methods.push(Box::new(|x, y, xnew| {
let mut result = Array1::zeros(xnew.len());
for (i, &x_val) in xnew.iter().enumerate() {
if x_val <= x[0] {
result[i] = y[0];
} else if x_val >= x[x.len() - 1] {
result[i] = y[y.len() - 1];
} else {
for j in 1..x.len() {
if x_val <= x[j] {
let alpha = (x_val - x[j - 1]) / (x[j] - x[j - 1]);
result[i] = y[j - 1] * (T::one() - alpha) + y[j] * alpha;
break;
}
}
}
}
Ok(result)
}));
self
}
pub fn add_cubic_method(mut self, weight: T) -> Self {
self.weights = if self.weights.is_empty() {
Array1::from_vec(vec![weight])
} else {
let mut new_weights = self.weights.to_vec();
new_weights.push(weight);
Array1::from_vec(new_weights)
};
self.methods.push(Box::new(|x, y, xnew| {
use crate::spline::CubicSpline;
if x.len() < 3 {
return Err(InterpolateError::invalid_input(
"Cubic spline requires at least 3 data points".to_string(),
));
}
let spline = CubicSpline::new(x, y)?;
let mut result = Array1::zeros(xnew.len());
for (i, &x_val) in xnew.iter().enumerate() {
if x_val < x[0] {
result[i] = y[0];
} else if x_val > x[x.len() - 1] {
result[i] = y[y.len() - 1];
} else {
result[i] = spline.evaluate(x_val)?;
}
}
Ok(result)
}));
self
}
pub fn interpolate(
&self,
x: &ArrayView1<T>,
y: &ArrayView1<T>,
xnew: &ArrayView1<T>,
) -> InterpolateResult<Array1<T>> {
if self.methods.is_empty() {
return Err(InterpolateError::InvalidState(
"No interpolation methods in ensemble".to_string(),
));
}
let mut weighted_results = Array1::zeros(xnew.len());
let mut total_weight = T::zero();
for (i, method) in self.methods.iter().enumerate() {
let result = method(x, y, xnew)?;
let weight = self.weights[i];
for j in 0..xnew.len() {
weighted_results[j] += weight * result[j];
}
total_weight += weight;
}
if self.normalize_weights && total_weight > T::zero() {
for val in weighted_results.iter_mut() {
*val /= total_weight;
}
}
Ok(weighted_results)
}
pub fn interpolate_with_variance(
&self,
x: &ArrayView1<T>,
y: &ArrayView1<T>,
xnew: &ArrayView1<T>,
) -> InterpolateResult<(Array1<T>, Array1<T>)> {
if self.methods.is_empty() {
return Err(InterpolateError::InvalidState(
"No interpolation methods in ensemble".to_string(),
));
}
let mut all_results = Vec::new();
for method in self.methods.iter() {
let result = method(x, y, xnew)?;
all_results.push(result);
}
let mut weighted_mean = Array1::zeros(xnew.len());
let mut total_weight = T::zero();
for (i, result) in all_results.iter().enumerate() {
let weight = self.weights[i];
for j in 0..xnew.len() {
weighted_mean[j] += weight * result[j];
}
total_weight += weight;
}
if total_weight > T::zero() {
for val in weighted_mean.iter_mut() {
*val /= total_weight;
}
}
let mut variance = Array1::zeros(xnew.len());
if all_results.len() > 1 {
for (i, result) in all_results.iter().enumerate() {
let weight = self.weights[i];
for j in 0..xnew.len() {
let diff = result[j] - weighted_mean[j];
variance[j] += weight * diff * diff;
}
}
if total_weight > T::zero() {
for val in variance.iter_mut() {
*val /= total_weight;
}
}
}
Ok((weighted_mean, variance))
}
}
impl<T: crate::traits::InterpolationFloat> Default for EnsembleInterpolator<T> {
fn default() -> Self {
Self::new()
}
}
pub struct CrossValidationUncertainty {
k_folds: usize,
seed: Option<u64>,
}
impl CrossValidationUncertainty {
pub fn new(_kfolds: usize) -> Self {
Self {
k_folds: _kfolds,
seed: None,
}
}
pub fn with_seed(mut self, seed: u64) -> Self {
self.seed = Some(seed);
self
}
pub fn estimate_uncertainty<T, F>(
&self,
x: &ArrayView1<T>,
y: &ArrayView1<T>,
xnew: &ArrayView1<T>,
interpolator_factory: F,
) -> InterpolateResult<(Array1<T>, Array1<T>)>
where
T: Clone
+ Copy
+ scirs2_core::numeric::Float
+ scirs2_core::numeric::FromPrimitive
+ std::iter::Sum,
F: Fn(&ArrayView1<T>, &ArrayView1<T>, &ArrayView1<T>) -> InterpolateResult<Array1<T>>,
{
let n = x.len();
let _m = xnew.len();
if self.k_folds == 0 || self.k_folds >= n {
self.leave_one_out_uncertainty(x, y, xnew, interpolator_factory)
} else {
self.k_fold_uncertainty(x, y, xnew, interpolator_factory)
}
}
fn leave_one_out_uncertainty<T, F>(
&self,
x: &ArrayView1<T>,
y: &ArrayView1<T>,
xnew: &ArrayView1<T>,
interpolator_factory: F,
) -> InterpolateResult<(Array1<T>, Array1<T>)>
where
T: Clone
+ Copy
+ scirs2_core::numeric::Float
+ scirs2_core::numeric::FromPrimitive
+ std::iter::Sum,
F: Fn(&ArrayView1<T>, &ArrayView1<T>, &ArrayView1<T>) -> InterpolateResult<Array1<T>>,
{
let n = x.len();
let m = xnew.len();
let mut predictions = Array2::zeros((n, m));
for i in 0..n {
let mut x_train = Vec::new();
let mut y_train = Vec::new();
for j in 0..n {
if j != i {
x_train.push(x[j]);
y_train.push(y[j]);
}
}
let x_train_array = Array1::from_vec(x_train);
let y_train_array = Array1::from_vec(y_train);
let pred = interpolator_factory(&x_train_array.view(), &y_train_array.view(), xnew)?;
for j in 0..m {
predictions[[i, j]] = pred[j];
}
}
let mut mean = Array1::zeros(m);
let mut variance = Array1::zeros(m);
for j in 0..m {
let col = predictions.column(j);
let sum: T = col.iter().copied().sum();
mean[j] = sum / T::from(n).expect("Operation failed");
let var_sum: T = col
.iter()
.map(|&val| (val - mean[j]) * (val - mean[j]))
.sum();
variance[j] = var_sum / T::from(n - 1).expect("Operation failed");
}
Ok((mean, variance))
}
fn k_fold_uncertainty<T, F>(
&self,
x: &ArrayView1<T>,
y: &ArrayView1<T>,
xnew: &ArrayView1<T>,
interpolator_factory: F,
) -> InterpolateResult<(Array1<T>, Array1<T>)>
where
T: Clone
+ Copy
+ scirs2_core::numeric::Float
+ scirs2_core::numeric::FromPrimitive
+ std::iter::Sum,
F: Fn(&ArrayView1<T>, &ArrayView1<T>, &ArrayView1<T>) -> InterpolateResult<Array1<T>>,
{
let n = x.len();
let m = xnew.len();
let fold_size = n / self.k_folds;
let mut predictions = Vec::new();
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 indices: Vec<usize> = (0..n).collect();
use scirs2_core::random::seq::SliceRandom;
indices.shuffle(&mut rng);
for fold in 0..self.k_folds {
let start_idx = fold * fold_size;
let end_idx = if fold == self.k_folds - 1 {
n
} else {
(fold + 1) * fold_size
};
let mut x_train = Vec::new();
let mut y_train = Vec::new();
for &idx in &indices[..start_idx] {
x_train.push(x[idx]);
y_train.push(y[idx]);
}
for &idx in &indices[end_idx..] {
x_train.push(x[idx]);
y_train.push(y[idx]);
}
let x_train_array = Array1::from_vec(x_train);
let y_train_array = Array1::from_vec(y_train);
let pred = interpolator_factory(&x_train_array.view(), &y_train_array.view(), xnew)?;
predictions.push(pred);
}
let mut mean = Array1::zeros(m);
let mut variance = Array1::zeros(m);
for j in 0..m {
let values: Vec<T> = predictions.iter().map(|pred| pred[j]).collect();
let sum: T = values.iter().copied().sum();
mean[j] = sum / T::from(self.k_folds).expect("Operation failed");
let var_sum: T = values
.iter()
.map(|&val| (val - mean[j]) * (val - mean[j]))
.sum();
variance[j] = var_sum / T::from(self.k_folds - 1).expect("Operation failed");
}
Ok((mean, variance))
}
}
#[allow(dead_code)]
pub fn make_ensemble_interpolator<
T: Float
+ FromPrimitive
+ Debug
+ Display
+ Copy
+ std::iter::Sum
+ crate::traits::InterpolationFloat,
>() -> EnsembleInterpolator<T> {
EnsembleInterpolator::new()
.add_linear_method(T::from(0.6).expect("Operation failed"))
.add_cubic_method(T::from(0.4).expect("Operation failed"))
}
#[allow(dead_code)]
pub fn make_loocv_uncertainty() -> CrossValidationUncertainty {
CrossValidationUncertainty::new(0) }
#[allow(dead_code)]
pub fn make_kfold_uncertainty(k: usize) -> CrossValidationUncertainty {
CrossValidationUncertainty::new(k)
}
#[derive(Debug, Clone)]
pub struct IsotonicInterpolator<T: Float> {
fitted_values: Array1<T>,
x_data: Array1<T>,
increasing: bool,
}
impl<T: Float + FromPrimitive + Debug + Display + Copy + std::iter::Sum> IsotonicInterpolator<T> {
pub fn new(x: &ArrayView1<T>, y: &ArrayView1<T>, increasing: bool) -> InterpolateResult<Self> {
if x.len() != y.len() {
return Err(InterpolateError::DimensionMismatch(
"x and y must have the same length".to_string(),
));
}
if x.len() < 2 {
return Err(InterpolateError::invalid_input(
"Need at least 2 points for isotonic regression".to_string(),
));
}
let mut indices: Vec<usize> = (0..x.len()).collect();
indices.sort_by(|&i, &j| x[i].partial_cmp(&x[j]).expect("Operation failed"));
let x_sorted: Array1<T> = indices.iter().map(|&i| x[i]).collect();
let y_sorted: Array1<T> = indices.iter().map(|&i| y[i]).collect();
let fitted_values = Self::pool_adjacent_violators(&y_sorted.view(), increasing)?;
Ok(Self {
fitted_values,
x_data: x_sorted,
increasing,
})
}
fn pool_adjacent_violators(
y: &ArrayView1<T>,
increasing: bool,
) -> InterpolateResult<Array1<T>> {
let n = y.len();
let mut fitted = y.to_owned();
let mut weights = Array1::<T>::ones(n);
loop {
let mut changed = false;
for i in 0..n - 1 {
let violates = if increasing {
fitted[i] > fitted[i + 1]
} else {
fitted[i] < fitted[i + 1]
};
if violates {
let total_weight = weights[i] + weights[i + 1];
let weighted_mean =
(fitted[i] * weights[i] + fitted[i + 1] * weights[i + 1]) / total_weight;
fitted[i] = weighted_mean;
fitted[i + 1] = weighted_mean;
weights[i] = total_weight;
weights[i + 1] = total_weight;
changed = true;
}
}
if !changed {
break;
}
}
Ok(fitted)
}
pub fn interpolate(&self, xnew: &ArrayView1<T>) -> InterpolateResult<Array1<T>> {
let mut result = Array1::zeros(xnew.len());
for (i, &x) in xnew.iter().enumerate() {
let idx = match self
.x_data
.as_slice()
.expect("Operation failed")
.binary_search_by(|&probe| probe.partial_cmp(&x).expect("Operation failed"))
{
Ok(exact_idx) => {
result[i] = self.fitted_values[exact_idx];
continue;
}
Err(insert_idx) => insert_idx,
};
if idx == 0 {
result[i] = self.fitted_values[0];
} else if idx >= self.x_data.len() {
result[i] = self.fitted_values[self.x_data.len() - 1];
} else {
let x0 = self.x_data[idx - 1];
let x1 = self.x_data[idx];
let y0 = self.fitted_values[idx - 1];
let y1 = self.fitted_values[idx];
let t = (x - x0) / (x1 - x0);
result[i] = y0 + t * (y1 - y0);
}
}
Ok(result)
}
}
#[derive(Debug, Clone)]
pub struct KDEInterpolator<T: Float> {
x_data: Array1<T>,
y_data: Array1<T>,
bandwidth: T,
kernel_type: KDEKernel,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum KDEKernel {
Gaussian,
Epanechnikov,
Triangular,
Uniform,
}
impl<T: Float + FromPrimitive + Debug + Display + Copy> KDEInterpolator<T> {
pub fn new(
x: &ArrayView1<T>,
y: &ArrayView1<T>,
bandwidth: T,
kernel_type: KDEKernel,
) -> InterpolateResult<Self> {
if x.len() != y.len() {
return Err(InterpolateError::DimensionMismatch(
"x and y must have the same length".to_string(),
));
}
if bandwidth <= T::zero() {
return Err(InterpolateError::invalid_input(
"Bandwidth must be positive".to_string(),
));
}
Ok(Self {
x_data: x.to_owned(),
y_data: y.to_owned(),
bandwidth,
kernel_type,
})
}
fn kernel(&self, u: T) -> T {
match self.kernel_type {
KDEKernel::Gaussian => {
let pi = T::from(std::f64::consts::PI).expect("Operation failed");
let two = T::from(2.0).expect("Operation failed");
let exp_arg = -u * u / two;
exp_arg.exp() / (two * pi).sqrt()
}
KDEKernel::Epanechnikov => {
if u.abs() <= T::one() {
let three_fourths = T::from(0.75).expect("Operation failed");
three_fourths * (T::one() - u * u)
} else {
T::zero()
}
}
KDEKernel::Triangular => {
if u.abs() <= T::one() {
T::one() - u.abs()
} else {
T::zero()
}
}
KDEKernel::Uniform => {
if u.abs() <= T::one() {
T::from(0.5).expect("Operation failed")
} else {
T::zero()
}
}
}
}
pub fn interpolate(&self, xnew: &ArrayView1<T>) -> InterpolateResult<Array1<T>> {
let mut result = Array1::zeros(xnew.len());
for (i, &x) in xnew.iter().enumerate() {
let mut weighted_sum = T::zero();
let mut weight_sum = T::zero();
for j in 0..self.x_data.len() {
let u = (x - self.x_data[j]) / self.bandwidth;
let kernel_weight = self.kernel(u);
weighted_sum = weighted_sum + kernel_weight * self.y_data[j];
weight_sum = weight_sum + kernel_weight;
}
if weight_sum > T::zero() {
result[i] = weighted_sum / weight_sum;
} else {
let mut min_dist = T::infinity();
let mut nearest_y = self.y_data[0];
for j in 0..self.x_data.len() {
let dist = (x - self.x_data[j]).abs();
if dist < min_dist {
min_dist = dist;
nearest_y = self.y_data[j];
}
}
result[i] = nearest_y;
}
}
Ok(result)
}
}
#[derive(Debug, Clone)]
pub struct EmpiricalBayesInterpolator<T: Float> {
x_data: Array1<T>,
y_data: Array1<T>,
shrinkage_factor: T,
prior_mean: T,
noise_variance: T,
}
impl<T: Float + FromPrimitive + Debug + Display + Copy + std::iter::Sum>
EmpiricalBayesInterpolator<T>
{
pub fn new(x: &ArrayView1<T>, y: &ArrayView1<T>) -> InterpolateResult<Self> {
if x.len() != y.len() {
return Err(InterpolateError::DimensionMismatch(
"x and y must have the same length".to_string(),
));
}
if x.len() < 3 {
return Err(InterpolateError::invalid_input(
"Need at least 3 points for empirical Bayes".to_string(),
));
}
let prior_mean = y.iter().copied().sum::<T>() / T::from(y.len()).expect("Operation failed");
let residuals: Array1<T> = y.iter().map(|&yi| yi - prior_mean).collect();
let noise_variance = residuals.iter().map(|&r| r * r).sum::<T>()
/ T::from(residuals.len() - 1).expect("Operation failed");
let signal_variance = noise_variance.max(T::from(1e-10).expect("Operation failed"));
let shrinkage_factor = noise_variance / (noise_variance + signal_variance);
Ok(Self {
x_data: x.to_owned(),
y_data: y.to_owned(),
shrinkage_factor,
prior_mean,
noise_variance,
})
}
pub fn with_prior(
x: &ArrayView1<T>,
y: &ArrayView1<T>,
prior_mean: T,
shrinkage_factor: T,
) -> InterpolateResult<Self> {
if x.len() != y.len() {
return Err(InterpolateError::DimensionMismatch(
"x and y must have the same length".to_string(),
));
}
let residuals: Array1<T> = y.iter().map(|&yi| yi - prior_mean).collect();
let noise_variance = residuals.iter().map(|&r| r * r).sum::<T>()
/ T::from(residuals.len().max(1)).expect("Operation failed");
Ok(Self {
x_data: x.to_owned(),
y_data: y.to_owned(),
shrinkage_factor,
prior_mean,
noise_variance,
})
}
pub fn interpolate(&self, xnew: &ArrayView1<T>) -> InterpolateResult<Array1<T>> {
let mut result = Array1::zeros(xnew.len());
for (i, &x) in xnew.iter().enumerate() {
let mut distances: Vec<(T, usize)> = self
.x_data
.iter()
.enumerate()
.map(|(j, &xi)| ((x - xi).abs(), j))
.collect();
distances.sort_by(|a, b| a.0.partial_cmp(&b.0).expect("Operation failed"));
let k = (3_usize).min(self.x_data.len() / 2).max(1);
let mut local_mean = T::zero();
let mut total_weight = T::zero();
for &(dist, j) in distances.iter().take(k) {
let weight = if dist == T::zero() {
T::one()
} else {
T::one() / (T::one() + dist)
};
local_mean = local_mean + weight * self.y_data[j];
total_weight = total_weight + weight;
}
if total_weight > T::zero() {
local_mean = local_mean / total_weight;
} else {
local_mean = self.prior_mean;
}
let shrunk_estimate = (T::one() - self.shrinkage_factor) * local_mean
+ self.shrinkage_factor * self.prior_mean;
result[i] = shrunk_estimate;
}
Ok(result)
}
pub fn get_shrinkage_factor(&self) -> T {
self.shrinkage_factor
}
pub fn get_prior_mean(&self) -> T {
self.prior_mean
}
pub fn get_noise_variance(&self) -> T {
self.noise_variance
}
}
#[allow(dead_code)]
pub fn make_isotonic_interpolator<
T: Float + FromPrimitive + Debug + Display + Copy + std::iter::Sum,
>(
x: &ArrayView1<T>,
y: &ArrayView1<T>,
) -> InterpolateResult<IsotonicInterpolator<T>> {
IsotonicInterpolator::new(x, y, true)
}
#[allow(dead_code)]
pub fn make_decreasing_isotonic_interpolator<
T: Float + FromPrimitive + Debug + Display + Copy + std::iter::Sum,
>(
x: &ArrayView1<T>,
y: &ArrayView1<T>,
) -> InterpolateResult<IsotonicInterpolator<T>> {
IsotonicInterpolator::new(x, y, false)
}
#[allow(dead_code)]
pub fn make_kde_interpolator<T: crate::traits::InterpolationFloat + Copy>(
x: &ArrayView1<T>,
y: &ArrayView1<T>,
bandwidth: T,
) -> InterpolateResult<KDEInterpolator<T>> {
KDEInterpolator::new(x, y, bandwidth, KDEKernel::Gaussian)
}
#[allow(dead_code)]
pub fn make_auto_kde_interpolator<
T: Float + FromPrimitive + Debug + Display + Copy + std::iter::Sum,
>(
x: &ArrayView1<T>,
y: &ArrayView1<T>,
) -> InterpolateResult<KDEInterpolator<T>> {
let n = T::from(x.len()).expect("Operation failed");
let x_std = {
let mean = x.iter().copied().sum::<T>() / n;
let variance = x.iter().map(|&xi| (xi - mean) * (xi - mean)).sum::<T>() / (n - T::one());
variance.sqrt()
};
let bandwidth = x_std * n.powf(-T::from(0.2).expect("Operation failed")); KDEInterpolator::new(x, y, bandwidth, KDEKernel::Gaussian)
}
#[allow(dead_code)]
pub fn make_empirical_bayes_interpolator<
T: Float + FromPrimitive + Debug + Display + Copy + std::iter::Sum,
>(
x: &ArrayView1<T>,
y: &ArrayView1<T>,
) -> InterpolateResult<EmpiricalBayesInterpolator<T>> {
EmpiricalBayesInterpolator::new(x, y)
}