scirs2_interpolate/extrapolation_modules/
advanced.rs1use scirs2_core::ndarray::Array1;
8use scirs2_core::numeric::{Float, FromPrimitive};
9use std::default::Default;
10use std::ops::AddAssign;
11
12use crate::error::{InterpolateError, InterpolateResult};
13
14use super::config::{
15 AdaptiveExtrapolationConfig, AutoregressiveExtrapolationConfig, ConfidenceExtrapolationConfig,
16 ConfidenceExtrapolationResult, EnsembleExtrapolationConfig,
17};
18use super::core::Extrapolator;
19use super::types::{ARFittingMethod, EnsembleCombinationStrategy, ExtrapolationMethod};
20
21#[derive(Debug, Clone)]
23pub struct AdvancedExtrapolator<T: Float> {
24 pub base_extrapolator: Extrapolator<T>,
26 pub confidence_config: Option<ConfidenceExtrapolationConfig<T>>,
28 pub ensemble_config: Option<EnsembleExtrapolationConfig<T>>,
30 pub adaptive_config: Option<AdaptiveExtrapolationConfig>,
32 pub autoregressive_config: Option<AutoregressiveExtrapolationConfig<T>>,
34 pub historical_data: Option<(Array1<T>, Array1<T>)>,
36}
37
38impl<T: Float + std::fmt::Display + Default + AddAssign> AdvancedExtrapolator<T> {
39 pub fn new(base_extrapolator: Extrapolator<T>) -> Self {
41 Self {
42 base_extrapolator,
43 confidence_config: None,
44 ensemble_config: None,
45 adaptive_config: None,
46 autoregressive_config: None,
47 historical_data: None,
48 }
49 }
50
51 pub fn with_confidence(mut self, config: ConfidenceExtrapolationConfig<T>) -> Self {
53 self.confidence_config = Some(config);
54 self
55 }
56
57 pub fn with_ensemble(mut self, config: EnsembleExtrapolationConfig<T>) -> Self {
59 self.ensemble_config = Some(config);
60 self
61 }
62
63 pub fn with_adaptive(mut self, config: AdaptiveExtrapolationConfig) -> Self {
65 self.adaptive_config = Some(config);
66 self
67 }
68
69 pub fn with_autoregressive(mut self, config: AutoregressiveExtrapolationConfig<T>) -> Self {
71 self.autoregressive_config = Some(config);
72 self
73 }
74
75 pub fn with_historical_data(mut self, x_data: Array1<T>, y_data: Array1<T>) -> Self {
77 self.historical_data = Some((x_data, y_data));
78 self
79 }
80
81 pub fn extrapolate_advanced(&self, x: T) -> InterpolateResult<T> {
83 if self.ensemble_config.is_some() {
85 return self.extrapolate_ensemble(x);
86 }
87
88 if self.adaptive_config.is_some() {
90 return self.extrapolate_adaptive(x);
91 }
92
93 if self.autoregressive_config.is_some() {
95 return self.extrapolate_autoregressive(x);
96 }
97
98 self.base_extrapolator.extrapolate(x)
100 }
101
102 pub fn extrapolate_with_confidence(
104 &self,
105 x: T,
106 ) -> InterpolateResult<ConfidenceExtrapolationResult<T>> {
107 if let Some(config) = &self.confidence_config {
108 let base_result = self.base_extrapolator.extrapolate(x)?;
109
110 let lower_bound = self.base_extrapolator.lower_bound();
112 let upper_bound = self.base_extrapolator.upper_bound();
113
114 let distance_from_domain = if x < lower_bound {
116 lower_bound - x
117 } else if x > upper_bound {
118 x - upper_bound
119 } else {
120 T::zero() };
122
123 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;
128
129 let z_score = if config.confidence_level >= T::from(0.99).unwrap_or_default() {
132 T::from(2.576).unwrap_or_default() } else if config.confidence_level >= T::from(0.95).unwrap_or_default() {
134 T::from(1.96).unwrap_or_default() } else if config.confidence_level >= T::from(0.90).unwrap_or_default() {
136 T::from(1.645).unwrap_or_default() } else {
138 T::from(1.0).unwrap_or_default() };
140
141 let margin_of_error = z_score * _standard_error;
142 let lower_bound_confidence = base_result - margin_of_error;
143 let upper_bound_confidence = base_result + margin_of_error;
144
145 Ok(ConfidenceExtrapolationResult {
146 value: base_result,
147 lower_bound: lower_bound_confidence,
148 upper_bound: upper_bound_confidence,
149 confidence_level: config.confidence_level,
150 })
151 } else {
152 Err(InterpolateError::ComputationError(
153 "Confidence extrapolation not configured".to_string(),
154 ))
155 }
156 }
157
158 pub fn extrapolate_ensemble(&self, x: T) -> InterpolateResult<T> {
160 if let Some(config) = &self.ensemble_config {
161 let mut results = Vec::new();
162 let mut weights = Vec::new();
163
164 for (i, &method) in config.methods.iter().enumerate() {
166 let mut temp_extrapolator = self.base_extrapolator.clone();
168
169 if x < temp_extrapolator.lower_bound() {
171 temp_extrapolator.lower_method = method;
172 } else if x > temp_extrapolator.upper_bound() {
173 temp_extrapolator.upper_method = method;
174 }
175
176 if let Ok(result) = temp_extrapolator.extrapolate(x) {
177 results.push(result);
178 let weight = if let Some(w) = config.weights.as_ref() {
179 w.get(i).copied().unwrap_or(T::one())
180 } else {
181 T::one()
182 };
183 weights.push(weight);
184 }
185 }
186
187 if results.is_empty() {
188 return Err(InterpolateError::ComputationError(
189 "No ensemble methods produced valid results".to_string(),
190 ));
191 }
192
193 match config.combination_strategy {
195 EnsembleCombinationStrategy::Mean => {
196 let sum: T = results.iter().copied().fold(T::zero(), |acc, x| acc + x);
197 Ok(sum / T::from(results.len()).unwrap())
198 }
199 EnsembleCombinationStrategy::WeightedMean => {
200 let weighted_sum: T = results
201 .iter()
202 .zip(weights.iter())
203 .map(|(r, w)| *r * *w)
204 .fold(T::zero(), |acc, x| acc + x);
205 let weight_sum: T = weights.iter().copied().fold(T::zero(), |acc, x| acc + x);
206
207 if weight_sum.is_zero() {
208 return Err(InterpolateError::ComputationError(
209 "Zero total weight in ensemble".to_string(),
210 ));
211 }
212
213 Ok(weighted_sum / weight_sum)
214 }
215 EnsembleCombinationStrategy::Median => {
216 let mut sorted_results = results;
217 sorted_results.sort_by(|a, b| a.partial_cmp(b).unwrap());
218 let mid = sorted_results.len() / 2;
219
220 if sorted_results.len() % 2 == 0 {
221 let two = T::from(2.0).unwrap();
222 Ok((sorted_results[mid - 1] + sorted_results[mid]) / two)
223 } else {
224 Ok(sorted_results[mid])
225 }
226 }
227 EnsembleCombinationStrategy::BestMethod => {
228 Ok(results[0])
230 }
231 EnsembleCombinationStrategy::MinimumVariance => {
232 let sum: T = results.iter().copied().fold(T::zero(), |acc, x| acc + x);
234 Ok(sum / T::from(results.len()).unwrap())
235 }
236 EnsembleCombinationStrategy::BayesianAveraging => {
237 let sum: T = results.iter().copied().fold(T::zero(), |acc, x| acc + x);
239 Ok(sum / T::from(results.len()).unwrap())
240 }
241 EnsembleCombinationStrategy::Voting => {
242 let mut sorted_results = results;
244 sorted_results.sort_by(|a, b| a.partial_cmp(b).unwrap());
245 let mid = sorted_results.len() / 2;
246 Ok(sorted_results[mid])
247 }
248 EnsembleCombinationStrategy::Stacking => {
249 let sum: T = results.iter().copied().fold(T::zero(), |acc, x| acc + x);
251 Ok(sum / T::from(results.len()).unwrap())
252 }
253 }
254 } else {
255 Err(InterpolateError::ComputationError(
256 "Ensemble extrapolation not configured".to_string(),
257 ))
258 }
259 }
260
261 pub fn extrapolate_adaptive(&self, x: T) -> InterpolateResult<T> {
263 if let Some(_config) = &self.adaptive_config {
264 let candidate_methods = vec![
269 ExtrapolationMethod::Linear,
270 ExtrapolationMethod::Quadratic,
271 ExtrapolationMethod::Cubic,
272 ExtrapolationMethod::Exponential,
273 ];
274
275 let mut best_result = None;
276 let mut _best_score = T::infinity();
277
278 for &method in &candidate_methods {
280 let mut temp_extrapolator = self.base_extrapolator.clone();
281
282 if x < temp_extrapolator.lower_bound() {
284 temp_extrapolator.lower_method = method;
285 } else if x > temp_extrapolator.upper_bound() {
286 temp_extrapolator.upper_method = method;
287 }
288
289 if let Ok(result) = temp_extrapolator.extrapolate(x) {
290 if best_result.is_none() {
291 best_result = Some(result);
292 }
294 }
295 }
296
297 best_result.ok_or_else(|| {
298 InterpolateError::ComputationError(
299 "No adaptive methods produced valid results".to_string(),
300 )
301 })
302 } else {
303 Err(InterpolateError::ComputationError(
304 "Adaptive extrapolation not configured".to_string(),
305 ))
306 }
307 }
308
309 pub fn extrapolate_autoregressive(&self, x: T) -> InterpolateResult<T> {
311 if let Some(config) = &self.autoregressive_config {
312 if let Some((x_data, y_data)) = &self.historical_data {
313 let ar_coeffs = self.fit_ar_model(x_data, y_data, config.ar_order)?;
315 self.ar_predict(&ar_coeffs, x_data, y_data, x, config)
316 } else {
317 Err(InterpolateError::ComputationError(
318 "Historical data required for autoregressive extrapolation".to_string(),
319 ))
320 }
321 } else {
322 Err(InterpolateError::ComputationError(
323 "Autoregressive extrapolation not configured".to_string(),
324 ))
325 }
326 }
327
328 fn fit_ar_model(
330 &self,
331 _x_data: &Array1<T>,
332 y_data: &Array1<T>,
333 order: usize,
334 ) -> InterpolateResult<Array1<T>> {
335 if y_data.len() < order + 1 {
336 return Err(InterpolateError::ComputationError(
337 "Insufficient data for AR model fitting".to_string(),
338 ));
339 }
340
341 let n = y_data.len();
343 let mut coeffs = Array1::zeros(order);
344
345 let mut autocorr = Array1::zeros(order + 1);
350 for lag in 0..=order {
351 let mut sum = T::zero();
352 let mut count = 0;
353
354 for i in lag..n {
355 sum += y_data[i] * y_data[i - lag];
356 count += 1;
357 }
358
359 if count > 0 {
360 autocorr[lag] = sum / T::from(count).unwrap_or(T::one());
361 }
362 }
363
364 for i in 0..order {
367 if autocorr[0] != T::zero() {
368 coeffs[i] = autocorr[i + 1] / autocorr[0];
369 }
370 }
371
372 Ok(coeffs)
373 }
374
375 fn ar_predict(
377 &self,
378 coeffs: &Array1<T>,
379 x_data: &Array1<T>,
380 y_data: &Array1<T>,
381 x: T,
382 _config: &AutoregressiveExtrapolationConfig<T>,
383 ) -> InterpolateResult<T> {
384 let order = coeffs.len();
385
386 if y_data.len() < order {
387 return Err(InterpolateError::ComputationError(
388 "Insufficient data for AR prediction".to_string(),
389 ));
390 }
391
392 let mut prediction = T::zero();
394 let start_idx = y_data.len() - order;
395
396 for i in 0..order {
397 prediction += coeffs[i] * y_data[start_idx + i];
398 }
399
400 let last_x = x_data[x_data.len() - 1];
403 let extrapolation_distance = x - last_x;
404
405 if extrapolation_distance != T::zero() && y_data.len() >= 2 {
407 let trend = (y_data[y_data.len() - 1] - y_data[y_data.len() - 2])
408 / (x_data[x_data.len() - 1] - x_data[x_data.len() - 2]);
409 prediction += trend * extrapolation_distance;
410 }
411
412 Ok(prediction)
413 }
414
415 pub fn base(&self) -> &Extrapolator<T> {
417 &self.base_extrapolator
418 }
419
420 pub fn base_mut(&mut self) -> &mut Extrapolator<T> {
422 &mut self.base_extrapolator
423 }
424
425 pub fn has_confidence(&self) -> bool {
427 self.confidence_config.is_some()
428 }
429
430 pub fn has_ensemble(&self) -> bool {
432 self.ensemble_config.is_some()
433 }
434
435 pub fn has_adaptive(&self) -> bool {
437 self.adaptive_config.is_some()
438 }
439
440 pub fn has_autoregressive(&self) -> bool {
442 self.autoregressive_config.is_some()
443 }
444
445 pub fn has_historical_data(&self) -> bool {
447 self.historical_data.is_some()
448 }
449
450 pub fn ar_model_order(&self) -> Option<usize> {
452 self.autoregressive_config.as_ref().map(|c| c.ar_order)
453 }
454
455 pub fn extrapolate_batch(&self, x_values: &[T]) -> Vec<InterpolateResult<T>> {
457 x_values
458 .iter()
459 .map(|&x| self.extrapolate_advanced(x))
460 .collect()
461 }
462
463 pub fn recommend_methods(&self, x: T) -> Vec<ExtrapolationMethod> {
465 let mut recommendations = Vec::new();
466
467 let distance_from_lower = if x < self.base_extrapolator.lower_bound() {
469 self.base_extrapolator.lower_bound() - x
470 } else {
471 T::zero()
472 };
473
474 let distance_from_upper = if x > self.base_extrapolator.upper_bound() {
475 x - self.base_extrapolator.upper_bound()
476 } else {
477 T::zero()
478 };
479
480 let domain_width = self.base_extrapolator.domain_width();
481
482 if distance_from_lower < domain_width * T::from(0.1).unwrap_or(T::one())
484 || distance_from_upper < domain_width * T::from(0.1).unwrap_or(T::one())
485 {
486 recommendations.push(ExtrapolationMethod::Cubic);
487 recommendations.push(ExtrapolationMethod::Quadratic);
488 }
489
490 recommendations.push(ExtrapolationMethod::Linear);
492
493 if distance_from_lower > domain_width * T::from(0.5).unwrap_or(T::one())
495 || distance_from_upper > domain_width * T::from(0.5).unwrap_or(T::one())
496 {
497 recommendations.push(ExtrapolationMethod::Exponential);
498 recommendations.push(ExtrapolationMethod::PowerLaw);
499 }
500
501 recommendations
502 }
503}