use scirs2_core::ndarray::Array1;
use scirs2_core::numeric::{Float, FromPrimitive};
use std::default::Default;
use std::ops::AddAssign;
use crate::error::{InterpolateError, InterpolateResult};
use super::config::{
AdaptiveExtrapolationConfig, AutoregressiveExtrapolationConfig, ConfidenceExtrapolationConfig,
ConfidenceExtrapolationResult, EnsembleExtrapolationConfig,
};
use super::core::Extrapolator;
use super::types::{ARFittingMethod, EnsembleCombinationStrategy, ExtrapolationMethod};
#[derive(Debug, Clone)]
pub struct AdvancedExtrapolator<T: Float> {
pub base_extrapolator: Extrapolator<T>,
pub confidence_config: Option<ConfidenceExtrapolationConfig<T>>,
pub ensemble_config: Option<EnsembleExtrapolationConfig<T>>,
pub adaptive_config: Option<AdaptiveExtrapolationConfig>,
pub autoregressive_config: Option<AutoregressiveExtrapolationConfig<T>>,
pub historical_data: Option<(Array1<T>, Array1<T>)>,
}
impl<T: Float + std::fmt::Display + Default + AddAssign> AdvancedExtrapolator<T> {
pub fn new(base_extrapolator: Extrapolator<T>) -> Self {
Self {
base_extrapolator,
confidence_config: None,
ensemble_config: None,
adaptive_config: None,
autoregressive_config: None,
historical_data: None,
}
}
pub fn with_confidence(mut self, config: ConfidenceExtrapolationConfig<T>) -> Self {
self.confidence_config = Some(config);
self
}
pub fn with_ensemble(mut self, config: EnsembleExtrapolationConfig<T>) -> Self {
self.ensemble_config = Some(config);
self
}
pub fn with_adaptive(mut self, config: AdaptiveExtrapolationConfig) -> Self {
self.adaptive_config = Some(config);
self
}
pub fn with_autoregressive(mut self, config: AutoregressiveExtrapolationConfig<T>) -> Self {
self.autoregressive_config = Some(config);
self
}
pub fn with_historical_data(mut self, x_data: Array1<T>, y_data: Array1<T>) -> Self {
self.historical_data = Some((x_data, y_data));
self
}
pub fn extrapolate_advanced(&self, x: T) -> InterpolateResult<T> {
if self.ensemble_config.is_some() {
return self.extrapolate_ensemble(x);
}
if self.adaptive_config.is_some() {
return self.extrapolate_adaptive(x);
}
if self.autoregressive_config.is_some() {
return self.extrapolate_autoregressive(x);
}
self.base_extrapolator.extrapolate(x)
}
pub fn extrapolate_with_confidence(
&self,
x: T,
) -> InterpolateResult<ConfidenceExtrapolationResult<T>> {
if let Some(config) = &self.confidence_config {
let base_result = self.base_extrapolator.extrapolate(x)?;
let lower_bound = self.base_extrapolator.lower_bound();
let upper_bound = self.base_extrapolator.upper_bound();
let distance_from_domain = if x < lower_bound {
lower_bound - x
} else if x > upper_bound {
x - upper_bound
} else {
T::zero() };
let base_uncertainty = T::from(0.01).unwrap_or_default(); let distance_factor = T::from(0.1).unwrap_or_default(); let _standard_error = base_uncertainty + distance_factor * distance_from_domain;
let z_score = if config.confidence_level >= T::from(0.99).unwrap_or_default() {
T::from(2.576).unwrap_or_default() } else if config.confidence_level >= T::from(0.95).unwrap_or_default() {
T::from(1.96).unwrap_or_default() } else if config.confidence_level >= T::from(0.90).unwrap_or_default() {
T::from(1.645).unwrap_or_default() } else {
T::from(1.0).unwrap_or_default() };
let margin_of_error = z_score * _standard_error;
let lower_bound_confidence = base_result - margin_of_error;
let upper_bound_confidence = base_result + margin_of_error;
Ok(ConfidenceExtrapolationResult {
value: base_result,
lower_bound: lower_bound_confidence,
upper_bound: upper_bound_confidence,
confidence_level: config.confidence_level,
})
} else {
Err(InterpolateError::ComputationError(
"Confidence extrapolation not configured".to_string(),
))
}
}
pub fn extrapolate_ensemble(&self, x: T) -> InterpolateResult<T> {
if let Some(config) = &self.ensemble_config {
let mut results = Vec::new();
let mut weights = Vec::new();
for (i, &method) in config.methods.iter().enumerate() {
let mut temp_extrapolator = self.base_extrapolator.clone();
if x < temp_extrapolator.lower_bound() {
temp_extrapolator.lower_method = method;
} else if x > temp_extrapolator.upper_bound() {
temp_extrapolator.upper_method = method;
}
if let Ok(result) = temp_extrapolator.extrapolate(x) {
results.push(result);
let weight = if let Some(w) = config.weights.as_ref() {
w.get(i).copied().unwrap_or(T::one())
} else {
T::one()
};
weights.push(weight);
}
}
if results.is_empty() {
return Err(InterpolateError::ComputationError(
"No ensemble methods produced valid results".to_string(),
));
}
match config.combination_strategy {
EnsembleCombinationStrategy::Mean => {
let sum: T = results.iter().copied().fold(T::zero(), |acc, x| acc + x);
Ok(sum / T::from(results.len()).expect("Operation failed"))
}
EnsembleCombinationStrategy::WeightedMean => {
let weighted_sum: T = results
.iter()
.zip(weights.iter())
.map(|(r, w)| *r * *w)
.fold(T::zero(), |acc, x| acc + x);
let weight_sum: T = weights.iter().copied().fold(T::zero(), |acc, x| acc + x);
if weight_sum.is_zero() {
return Err(InterpolateError::ComputationError(
"Zero total weight in ensemble".to_string(),
));
}
Ok(weighted_sum / weight_sum)
}
EnsembleCombinationStrategy::Median => {
let mut sorted_results = results;
sorted_results.sort_by(|a, b| a.partial_cmp(b).expect("Operation failed"));
let mid = sorted_results.len() / 2;
if sorted_results.len() % 2 == 0 {
let two = T::from(2.0).expect("Operation failed");
Ok((sorted_results[mid - 1] + sorted_results[mid]) / two)
} else {
Ok(sorted_results[mid])
}
}
EnsembleCombinationStrategy::BestMethod => {
Ok(results[0])
}
EnsembleCombinationStrategy::MinimumVariance => {
let sum: T = results.iter().copied().fold(T::zero(), |acc, x| acc + x);
Ok(sum / T::from(results.len()).expect("Operation failed"))
}
EnsembleCombinationStrategy::BayesianAveraging => {
let sum: T = results.iter().copied().fold(T::zero(), |acc, x| acc + x);
Ok(sum / T::from(results.len()).expect("Operation failed"))
}
EnsembleCombinationStrategy::Voting => {
let mut sorted_results = results;
sorted_results.sort_by(|a, b| a.partial_cmp(b).expect("Operation failed"));
let mid = sorted_results.len() / 2;
Ok(sorted_results[mid])
}
EnsembleCombinationStrategy::Stacking => {
let sum: T = results.iter().copied().fold(T::zero(), |acc, x| acc + x);
Ok(sum / T::from(results.len()).expect("Operation failed"))
}
}
} else {
Err(InterpolateError::ComputationError(
"Ensemble extrapolation not configured".to_string(),
))
}
}
pub fn extrapolate_adaptive(&self, x: T) -> InterpolateResult<T> {
if let Some(_config) = &self.adaptive_config {
let candidate_methods = vec![
ExtrapolationMethod::Linear,
ExtrapolationMethod::Quadratic,
ExtrapolationMethod::Cubic,
ExtrapolationMethod::Exponential,
];
let mut best_result = None;
let mut _best_score = T::infinity();
for &method in &candidate_methods {
let mut temp_extrapolator = self.base_extrapolator.clone();
if x < temp_extrapolator.lower_bound() {
temp_extrapolator.lower_method = method;
} else if x > temp_extrapolator.upper_bound() {
temp_extrapolator.upper_method = method;
}
if let Ok(result) = temp_extrapolator.extrapolate(x) {
if best_result.is_none() {
best_result = Some(result);
}
}
}
best_result.ok_or_else(|| {
InterpolateError::ComputationError(
"No adaptive methods produced valid results".to_string(),
)
})
} else {
Err(InterpolateError::ComputationError(
"Adaptive extrapolation not configured".to_string(),
))
}
}
pub fn extrapolate_autoregressive(&self, x: T) -> InterpolateResult<T> {
if let Some(config) = &self.autoregressive_config {
if let Some((x_data, y_data)) = &self.historical_data {
let ar_coeffs = self.fit_ar_model(x_data, y_data, config.ar_order)?;
self.ar_predict(&ar_coeffs, x_data, y_data, x, config)
} else {
Err(InterpolateError::ComputationError(
"Historical data required for autoregressive extrapolation".to_string(),
))
}
} else {
Err(InterpolateError::ComputationError(
"Autoregressive extrapolation not configured".to_string(),
))
}
}
fn fit_ar_model(
&self,
_x_data: &Array1<T>,
y_data: &Array1<T>,
order: usize,
) -> InterpolateResult<Array1<T>> {
if y_data.len() < order + 1 {
return Err(InterpolateError::ComputationError(
"Insufficient data for AR model fitting".to_string(),
));
}
let n = y_data.len();
let mut coeffs = Array1::zeros(order);
let mut autocorr = Array1::zeros(order + 1);
for lag in 0..=order {
let mut sum = T::zero();
let mut count = 0;
for i in lag..n {
sum += y_data[i] * y_data[i - lag];
count += 1;
}
if count > 0 {
autocorr[lag] = sum / T::from(count).unwrap_or(T::one());
}
}
for i in 0..order {
if autocorr[0] != T::zero() {
coeffs[i] = autocorr[i + 1] / autocorr[0];
}
}
Ok(coeffs)
}
fn ar_predict(
&self,
coeffs: &Array1<T>,
x_data: &Array1<T>,
y_data: &Array1<T>,
x: T,
_config: &AutoregressiveExtrapolationConfig<T>,
) -> InterpolateResult<T> {
let order = coeffs.len();
if y_data.len() < order {
return Err(InterpolateError::ComputationError(
"Insufficient data for AR prediction".to_string(),
));
}
let mut prediction = T::zero();
let start_idx = y_data.len() - order;
for i in 0..order {
prediction += coeffs[i] * y_data[start_idx + i];
}
let last_x = x_data[x_data.len() - 1];
let extrapolation_distance = x - last_x;
if extrapolation_distance != T::zero() && y_data.len() >= 2 {
let trend = (y_data[y_data.len() - 1] - y_data[y_data.len() - 2])
/ (x_data[x_data.len() - 1] - x_data[x_data.len() - 2]);
prediction += trend * extrapolation_distance;
}
Ok(prediction)
}
pub fn base(&self) -> &Extrapolator<T> {
&self.base_extrapolator
}
pub fn base_mut(&mut self) -> &mut Extrapolator<T> {
&mut self.base_extrapolator
}
pub fn has_confidence(&self) -> bool {
self.confidence_config.is_some()
}
pub fn has_ensemble(&self) -> bool {
self.ensemble_config.is_some()
}
pub fn has_adaptive(&self) -> bool {
self.adaptive_config.is_some()
}
pub fn has_autoregressive(&self) -> bool {
self.autoregressive_config.is_some()
}
pub fn has_historical_data(&self) -> bool {
self.historical_data.is_some()
}
pub fn ar_model_order(&self) -> Option<usize> {
self.autoregressive_config.as_ref().map(|c| c.ar_order)
}
pub fn extrapolate_batch(&self, x_values: &[T]) -> Vec<InterpolateResult<T>> {
x_values
.iter()
.map(|&x| self.extrapolate_advanced(x))
.collect()
}
pub fn recommend_methods(&self, x: T) -> Vec<ExtrapolationMethod> {
let mut recommendations = Vec::new();
let distance_from_lower = if x < self.base_extrapolator.lower_bound() {
self.base_extrapolator.lower_bound() - x
} else {
T::zero()
};
let distance_from_upper = if x > self.base_extrapolator.upper_bound() {
x - self.base_extrapolator.upper_bound()
} else {
T::zero()
};
let domain_width = self.base_extrapolator.domain_width();
if distance_from_lower < domain_width * T::from(0.1).unwrap_or(T::one())
|| distance_from_upper < domain_width * T::from(0.1).unwrap_or(T::one())
{
recommendations.push(ExtrapolationMethod::Cubic);
recommendations.push(ExtrapolationMethod::Quadratic);
}
recommendations.push(ExtrapolationMethod::Linear);
if distance_from_lower > domain_width * T::from(0.5).unwrap_or(T::one())
|| distance_from_upper > domain_width * T::from(0.5).unwrap_or(T::one())
{
recommendations.push(ExtrapolationMethod::Exponential);
recommendations.push(ExtrapolationMethod::PowerLaw);
}
recommendations
}
}