Skip to main content

scirs2_integrate/
error_estimation.rs

1//! Advanced error estimation and quality assessment for numerical solutions
2//!
3//! This module provides sophisticated error estimation techniques that go beyond
4//! basic embedded error estimators. It includes Richardson extrapolation,
5//! defect correction, spectral error estimation, and adaptive quality metrics.
6
7use crate::common::IntegrateFloat;
8use crate::error::IntegrateResult;
9use scirs2_core::ndarray::{Array1, ArrayView1};
10use std::collections::VecDeque;
11
12/// Advanced error estimator using multiple techniques
13pub struct AdvancedErrorEstimator<F: IntegrateFloat> {
14    /// Tolerance for error estimation algorithms
15    pub tolerance: F,
16    /// Maximum order for Richardson extrapolation
17    pub max_richardson_order: usize,
18    /// History of solutions for spectral analysis
19    solution_history: VecDeque<Array1<F>>,
20    /// History of step sizes
21    step_size_history: VecDeque<F>,
22    /// History of error estimates
23    error_history: VecDeque<F>,
24}
25
26/// Richardson extrapolation error estimator
27pub struct RichardsonExtrapolator<F: IntegrateFloat> {
28    /// Extrapolation order
29    pub order: usize,
30    /// Step size ratios for extrapolation
31    pub step_ratios: Vec<F>,
32    /// Stored solutions at different step sizes
33    solutions: Vec<Array1<F>>,
34}
35
36/// Spectral error indicator based on solution smoothness
37pub struct SpectralErrorIndicator<F: IntegrateFloat> {
38    /// Window size for spectral analysis
39    pub window_size: usize,
40    /// Threshold for spectral decay rate
41    pub decay_threshold: F,
42    /// Solution history for analysis
43    history: VecDeque<Array1<F>>,
44}
45
46/// Defect-based error estimator
47pub struct DefectCorrector<F: IntegrateFloat> {
48    /// Maximum number of defect correction iterations
49    pub max_iterations: usize,
50    /// Tolerance for defect correction
51    pub tolerance: F,
52    /// Whether to use iterative defect correction
53    pub iterative: bool,
54}
55
56/// Comprehensive error analysis result
57#[derive(Debug, Clone)]
58pub struct ErrorAnalysisResult<F: IntegrateFloat> {
59    /// Primary error estimate
60    pub primary_estimate: F,
61    /// Richardson extrapolation error
62    pub richardson_error: Option<F>,
63    /// Spectral error indicator
64    pub spectral_error: Option<F>,
65    /// Defect-based error
66    pub defect_error: Option<F>,
67    /// Solution quality metrics
68    pub quality_metrics: SolutionQualityMetrics<F>,
69    /// Recommended next step size
70    pub recommended_step_size: F,
71    /// Confidence level in error estimate
72    pub confidence: F,
73    /// Error distribution analysis
74    pub error_distribution: ErrorDistribution<F>,
75}
76
77/// Solution quality assessment metrics
78#[derive(Debug, Clone)]
79pub struct SolutionQualityMetrics<F: IntegrateFloat> {
80    /// Smoothness indicator (higher = smoother)
81    pub smoothness: F,
82    /// Regularity indicator (spectral decay rate)
83    pub regularity: F,
84    /// Conservation property deviation
85    pub conservation_error: Option<F>,
86    /// Monotonicity violations
87    pub monotonicity_violations: usize,
88    /// Oscillation indicator
89    pub oscillation_index: F,
90    /// Signal-to-noise ratio
91    pub signal_noise_ratio: F,
92}
93
94/// Error distribution characteristics
95#[derive(Debug, Clone)]
96pub struct ErrorDistribution<F: IntegrateFloat> {
97    /// Mean error across components
98    pub mean_error: F,
99    /// Standard deviation of error
100    pub std_deviation: F,
101    /// Maximum component error
102    pub max_error: F,
103    /// Error distribution skewness
104    pub skewness: F,
105    /// Component-wise error estimates
106    pub component_errors: Vec<F>,
107}
108
109impl<F: IntegrateFloat> AdvancedErrorEstimator<F> {
110    /// Create new advanced error estimator
111    pub fn new(tolerance: F, max_richardsonorder: usize) -> Self {
112        Self {
113            tolerance,
114            max_richardson_order: max_richardsonorder,
115            solution_history: VecDeque::new(),
116            step_size_history: VecDeque::new(),
117            error_history: VecDeque::new(),
118        }
119    }
120
121    /// Perform comprehensive error analysis
122    pub fn analyze_error<Func>(
123        &mut self,
124        current_solution: &Array1<F>,
125        step_size: F,
126        ode_function: Func,
127        embedded_error: Option<F>,
128    ) -> IntegrateResult<ErrorAnalysisResult<F>>
129    where
130        Func: Fn(F, &ArrayView1<F>) -> Array1<F>,
131    {
132        // Store current _solution and step _size
133        self.solution_history.push_back(current_solution.clone());
134        self.step_size_history.push_back(step_size);
135
136        // Maintain history _size (keep last 20 points)
137        while self.solution_history.len() > 20 {
138            self.solution_history.pop_front();
139            self.step_size_history.pop_front();
140        }
141
142        let mut result = ErrorAnalysisResult {
143            primary_estimate: embedded_error
144                .unwrap_or(F::from(1e-8).expect("Failed to convert constant to float")),
145            richardson_error: None,
146            spectral_error: None,
147            defect_error: None,
148            quality_metrics: self.assess_solution_quality()?,
149            recommended_step_size: step_size,
150            confidence: F::from(0.5).expect("Failed to convert constant to float"), // Default confidence
151            error_distribution: self.analyze_error_distribution(current_solution)?,
152        };
153
154        // Richardson extrapolation if we have enough history
155        if self.solution_history.len() >= 3 {
156            result.richardson_error = self.richardson_extrapolation()?;
157        }
158
159        // Spectral _error analysis
160        if self.solution_history.len() >= 5 {
161            result.spectral_error = self.spectral_error_analysis()?;
162        }
163
164        // Defect correction _error estimate
165        result.defect_error = self.defect_based_error(current_solution, &ode_function)?;
166
167        // Compute overall confidence and recommendations
168        result.confidence = Self::compute_confidence(&result);
169        result.recommended_step_size = self.recommend_step_size(&result, step_size);
170
171        // Store _error estimate for history
172        self.error_history.push_back(result.primary_estimate);
173        while self.error_history.len() > 20 {
174            self.error_history.pop_front();
175        }
176
177        Ok(result)
178    }
179
180    /// Richardson extrapolation error estimation
181    fn richardson_extrapolation(&self) -> IntegrateResult<Option<F>> {
182        if self.solution_history.len() < 3 || self.step_size_history.len() < 3 {
183            return Ok(None);
184        }
185
186        let n = self.solution_history.len();
187        let y2 = &self.solution_history[n - 1]; // Current solution
188        let y1 = &self.solution_history[n - 2]; // Previous solution
189        let _y0 = &self.solution_history[n - 3]; // Solution before that
190
191        let h2 = self.step_size_history[n - 1];
192        let h1 = self.step_size_history[n - 2];
193
194        // Assume second-order method for Richardson extrapolation
195        let r = h1 / h2;
196        if (r - F::one()).abs() < F::from(0.1).expect("Failed to convert constant to float") {
197            // Step sizes too similar for reliable extrapolation
198            return Ok(None);
199        }
200
201        // Richardson extrapolation formula for second-order method
202        let extrapolated_error = (y2 - y1).mapv(|x| x.abs()).sum() / (r.powi(2) - F::one());
203
204        Ok(Some(extrapolated_error))
205    }
206
207    /// Spectral error analysis based on solution smoothness
208    fn spectral_error_analysis(&self) -> IntegrateResult<Option<F>> {
209        if self.solution_history.len() < 5 {
210            return Ok(None);
211        }
212
213        let _n = self.solution_history.len();
214        let recent_solutions = &self.solution_history;
215
216        // Compute discrete derivatives to estimate spectral content
217        let mut spectral_norm = F::zero();
218        let mut total_norm = F::zero();
219
220        for component in 0..recent_solutions[0].len() {
221            let values: Vec<F> = recent_solutions.iter().map(|sol| sol[component]).collect();
222
223            // Compute second differences (approximates second derivative)
224            if values.len() >= 3 {
225                for i in 1..values.len() - 1 {
226                    let second_diff = values[i + 1]
227                        - F::from(2.0).expect("Failed to convert constant to float") * values[i]
228                        + values[i - 1];
229                    spectral_norm += second_diff.abs();
230                    total_norm += values[i].abs();
231                }
232            }
233        }
234
235        if total_norm > F::zero() {
236            let spectral_indicator = spectral_norm / total_norm;
237            Ok(Some(spectral_indicator))
238        } else {
239            Ok(None)
240        }
241    }
242
243    /// Defect-based error estimation
244    fn defect_based_error<Func>(
245        &self,
246        current_solution: &Array1<F>,
247        ode_function: &Func,
248    ) -> IntegrateResult<Option<F>>
249    where
250        Func: Fn(F, &ArrayView1<F>) -> Array1<F>,
251    {
252        if self.solution_history.len() < 2 || self.step_size_history.is_empty() {
253            return Ok(None);
254        }
255
256        let h = *self.step_size_history.back().expect("Operation failed");
257        let t = F::zero(); // Assuming we're at some time t
258
259        // Compute defect: residual when substituting numerical _solution
260        // into the original ODE
261        let f_current = ode_function(t, &current_solution.view());
262        let defect_norm = f_current.mapv(|x| x.abs()).sum() * h;
263
264        Ok(Some(defect_norm))
265    }
266
267    /// Assess solution quality metrics
268    fn assess_solution_quality(&self) -> IntegrateResult<SolutionQualityMetrics<F>> {
269        let mut metrics = SolutionQualityMetrics {
270            smoothness: F::zero(),
271            regularity: F::zero(),
272            conservation_error: None,
273            monotonicity_violations: 0,
274            oscillation_index: F::zero(),
275            signal_noise_ratio: F::one(),
276        };
277
278        if self.solution_history.len() < 3 {
279            return Ok(metrics);
280        }
281
282        let n = self.solution_history.len();
283        let solutions = &self.solution_history;
284
285        // Compute smoothness indicator
286        let mut total_variation = F::zero();
287        let mut total_magnitude = F::zero();
288
289        for i in 1..n {
290            let diff = &solutions[i] - &solutions[i - 1];
291            total_variation += diff.mapv(|x| x.abs()).sum();
292            total_magnitude += solutions[i].mapv(|x| x.abs()).sum();
293        }
294
295        if total_magnitude > F::zero() {
296            metrics.smoothness = F::one() / (F::one() + total_variation / total_magnitude);
297        }
298
299        // Compute oscillation index
300        if n >= 3 {
301            let mut oscillations = 0;
302            for comp in 0..solutions[0].len() {
303                for i in 1..n - 1 {
304                    let prev = solutions[i - 1][comp];
305                    let curr = solutions[i][comp];
306                    let next = solutions[i + 1][comp];
307
308                    // Check for local extremum (sign change in derivative)
309                    if (curr - prev) * (next - curr) < F::zero() {
310                        oscillations += 1;
311                    }
312                }
313            }
314            metrics.oscillation_index = F::from(oscillations).expect("Failed to convert to float")
315                / F::from(n * solutions[0].len()).expect("Operation failed");
316        }
317
318        // Estimate signal-to-noise ratio
319        if n >= 4 {
320            let mut signal_power = F::zero();
321            let mut noise_power = F::zero();
322
323            for comp in 0..solutions[0].len() {
324                let values: Vec<F> = solutions.iter().map(|sol| sol[comp]).collect();
325                let mean = values.iter().fold(F::zero(), |acc, &x| acc + x)
326                    / F::from(values.len()).expect("Operation failed");
327
328                // Signal power (variance)
329                for &val in &values {
330                    signal_power += (val - mean).powi(2);
331                }
332
333                // Noise power (second differences)
334                for i in 1..values.len() - 1 {
335                    let second_diff = values[i + 1]
336                        - F::from(2.0).expect("Failed to convert constant to float") * values[i]
337                        + values[i - 1];
338                    noise_power += second_diff.powi(2);
339                }
340            }
341
342            if noise_power > F::zero() {
343                metrics.signal_noise_ratio = signal_power / noise_power;
344            }
345        }
346
347        Ok(metrics)
348    }
349
350    /// Analyze error distribution across solution components
351    fn analyze_error_distribution(
352        &self,
353        solution: &Array1<F>,
354    ) -> IntegrateResult<ErrorDistribution<F>> {
355        let n_components = solution.len();
356        let mut component_errors = vec![F::zero(); n_components];
357
358        // Estimate component-wise errors (simple finite difference approximation)
359        if self.solution_history.len() >= 2 {
360            let prev_solution = self.solution_history.back().expect("Operation failed");
361            for i in 0..n_components {
362                component_errors[i] = (solution[i] - prev_solution[i]).abs();
363            }
364        }
365
366        // Compute distribution statistics
367        let mean_error = component_errors.iter().fold(F::zero(), |acc, &x| acc + x)
368            / F::from(n_components).expect("Failed to convert to float");
369
370        let variance = component_errors
371            .iter()
372            .map(|&err| (err - mean_error).powi(2))
373            .fold(F::zero(), |acc, x| acc + x)
374            / F::from(n_components).expect("Failed to convert to float");
375
376        let std_deviation = variance.sqrt();
377        let max_error = component_errors
378            .iter()
379            .fold(F::zero(), |acc, &x| acc.max(x));
380
381        // Compute skewness
382        let skewness = if std_deviation > F::zero() {
383            component_errors
384                .iter()
385                .map(|&err| ((err - mean_error) / std_deviation).powi(3))
386                .fold(F::zero(), |acc, x| acc + x)
387                / F::from(n_components).expect("Failed to convert to float")
388        } else {
389            F::zero()
390        };
391
392        Ok(ErrorDistribution {
393            mean_error,
394            std_deviation,
395            max_error,
396            skewness,
397            component_errors,
398        })
399    }
400
401    /// Compute confidence in error estimate
402    fn compute_confidence(result: &ErrorAnalysisResult<F>) -> F {
403        let mut confidence_factors = Vec::new();
404
405        // Confidence from multiple error estimates agreement
406        let estimates = [
407            Some(result.primary_estimate),
408            result.richardson_error,
409            result.spectral_error,
410            result.defect_error,
411        ]
412        .iter()
413        .filter_map(|&est| est)
414        .collect::<Vec<_>>();
415
416        if estimates.len() >= 2 {
417            let mean_est = estimates.iter().fold(F::zero(), |acc, &x| acc + x)
418                / F::from(estimates.len()).expect("Operation failed");
419            let relative_std = estimates
420                .iter()
421                .map(|&est| ((est - mean_est) / mean_est).abs())
422                .fold(F::zero(), |acc, x| acc + x)
423                / F::from(estimates.len()).expect("Operation failed");
424
425            // Higher confidence if estimates agree
426            confidence_factors.push(F::one() / (F::one() + relative_std));
427        }
428
429        // Confidence from solution quality
430        confidence_factors.push(result.quality_metrics.smoothness);
431        confidence_factors.push(F::one() / (F::one() + result.quality_metrics.oscillation_index));
432
433        // Average confidence factors
434        if !confidence_factors.is_empty() {
435            confidence_factors.iter().fold(F::zero(), |acc, &x| acc + x)
436                / F::from(confidence_factors.len()).expect("Operation failed")
437        } else {
438            F::from(0.5).expect("Failed to convert constant to float") // Default moderate confidence
439        }
440    }
441
442    /// Recommend optimal step size based on error analysis
443    fn recommend_step_size(&self, result: &ErrorAnalysisResult<F>, currentstep: F) -> F {
444        let target_error = self.tolerance;
445        let current_error = result.primary_estimate;
446
447        if current_error <= F::zero() {
448            return currentstep;
449        }
450
451        // Safety factor based on confidence
452        let safety_factor = F::from(0.8).expect("Failed to convert constant to float")
453            + F::from(0.15).expect("Failed to convert constant to float") * result.confidence;
454
455        // Standard _step size controller (assumes 2nd order method)
456        let ratio = (target_error / current_error)
457            .powf(F::from(0.5).expect("Failed to convert constant to float"));
458        let _basic_recommendation = currentstep * ratio * safety_factor;
459
460        // Adjust based on solution quality
461        let quality_factor = if result.quality_metrics.oscillation_index
462            > F::from(0.1).expect("Failed to convert constant to float")
463        {
464            F::from(0.7).expect("Failed to convert constant to float") // Reduce _step size for oscillatory solutions
465        } else if result.quality_metrics.smoothness
466            > F::from(0.8).expect("Failed to convert constant to float")
467        {
468            F::from(1.2).expect("Failed to convert constant to float") // Increase _step size for smooth solutions
469        } else {
470            F::one()
471        };
472
473        // Limit _step size changes
474        let min_factor = F::from(0.2).expect("Failed to convert constant to float");
475        let max_factor = F::from(5.0).expect("Failed to convert constant to float");
476        let final_factor = (ratio * safety_factor * quality_factor)
477            .max(min_factor)
478            .min(max_factor);
479
480        currentstep * final_factor
481    }
482}
483
484impl<F: IntegrateFloat> RichardsonExtrapolator<F> {
485    /// Create new Richardson extrapolator
486    pub fn new(order: usize) -> Self {
487        Self {
488            order,
489            step_ratios: vec![
490                F::from(0.5).expect("Failed to convert constant to float"),
491                F::from(0.25).expect("Failed to convert constant to float"),
492            ],
493            solutions: Vec::new(),
494        }
495    }
496
497    /// Add solution for extrapolation
498    pub fn add_solution(&mut self, solution: Array1<F>) {
499        self.solutions.push(solution);
500        if self.solutions.len() > self.order + 1 {
501            self.solutions.remove(0);
502        }
503    }
504
505    /// Perform Richardson extrapolation
506    pub fn extrapolate(&self) -> IntegrateResult<Option<Array1<F>>> {
507        if self.solutions.len() < 2 {
508            return Ok(None);
509        }
510
511        let n = self.solutions.len();
512        let mut tableau = Vec::new();
513
514        // Initialize first column with solutions
515        for sol in &self.solutions {
516            tableau.push(vec![sol.clone()]);
517        }
518
519        // Richardson extrapolation tableau
520        for col in 1..n {
521            for row in 0..n - col {
522                let default_ratio = F::from(0.5).expect("Failed to convert constant to float");
523                let r = self.step_ratios.get(col - 1).unwrap_or(&default_ratio);
524                let r_power = r.powi(self.order as i32);
525
526                let numerator = &tableau[row + 1][col - 1] * r_power - &tableau[row][col - 1];
527                let denominator = r_power - F::one();
528
529                if denominator.abs() > F::from(1e-12).expect("Failed to convert constant to float")
530                {
531                    let extrapolated = numerator / denominator;
532                    tableau[row].push(extrapolated);
533                } else {
534                    return Ok(None);
535                }
536            }
537        }
538
539        // Return most extrapolated result
540        Ok(Some(tableau[0][n - 1].clone()))
541    }
542}
543
544impl<F: IntegrateFloat> SpectralErrorIndicator<F> {
545    /// Create new spectral error indicator
546    pub fn new(window_size: usize, decaythreshold: F) -> Self {
547        Self {
548            window_size,
549            decay_threshold: decaythreshold,
550            history: VecDeque::new(),
551        }
552    }
553
554    /// Add solution to history
555    pub fn add_solution(&mut self, solution: Array1<F>) {
556        self.history.push_back(solution);
557        while self.history.len() > self.window_size {
558            self.history.pop_front();
559        }
560    }
561
562    /// Compute spectral error indicator
563    pub fn compute_indicator(&self) -> IntegrateResult<Option<F>> {
564        if self.history.len() < self.window_size {
565            return Ok(None);
566        }
567
568        // Simple spectral analysis: check solution regularity
569        // by examining decay of finite differences
570        let mut total_indicator = F::zero();
571        let n_components = self.history[0].len();
572
573        for comp in 0..n_components {
574            let values: Vec<F> = self.history.iter().map(|sol| sol[comp]).collect();
575
576            // Compute successive differences
577            let mut diff_norms = Vec::new();
578            let mut current_values = values;
579
580            for _order in 0..3 {
581                // Check up to 3rd order differences
582                if current_values.len() < 2 {
583                    break;
584                }
585
586                let mut new_values = Vec::new();
587                let mut norm = F::zero();
588
589                for i in 0..current_values.len() - 1 {
590                    let diff = current_values[i + 1] - current_values[i];
591                    new_values.push(diff);
592                    norm += diff.abs();
593                }
594
595                diff_norms.push(norm);
596                current_values = new_values;
597            }
598
599            // Check decay rate of difference norms
600            if diff_norms.len() >= 2 {
601                for i in 1..diff_norms.len() {
602                    if diff_norms[i - 1] > F::zero() {
603                        let decay_rate = diff_norms[i] / diff_norms[i - 1];
604                        if decay_rate > self.decay_threshold {
605                            total_indicator += decay_rate;
606                        }
607                    }
608                }
609            }
610        }
611
612        Ok(Some(
613            total_indicator / F::from(n_components).expect("Failed to convert to float"),
614        ))
615    }
616}
617
618#[cfg(test)]
619mod tests {
620    use super::*;
621
622    #[test]
623    fn test_advanced_error_estimator() {
624        let mut estimator = AdvancedErrorEstimator::<f64>::new(1e-6, 3);
625
626        let solution = Array1::from_vec(vec![1.0, 2.0, 3.0]);
627        let step_size = 0.01;
628        let ode_fn =
629            |_t: f64, y: &ArrayView1<f64>| Array1::from_vec(y.iter().map(|&yi| -yi).collect());
630
631        let result = estimator.analyze_error(&solution, step_size, ode_fn, Some(1e-8));
632        assert!(result.is_ok());
633
634        let error_analysis = result.expect("Test: error analysis failed");
635        assert!(error_analysis.primary_estimate > 0.0);
636        assert!(error_analysis.confidence >= 0.0 && error_analysis.confidence <= 1.0);
637    }
638
639    #[test]
640    fn test_richardson_extrapolator() {
641        let mut extrapolator = RichardsonExtrapolator::<f64>::new(2);
642
643        extrapolator.add_solution(Array1::from_vec(vec![1.0, 2.0]));
644        extrapolator.add_solution(Array1::from_vec(vec![1.01, 2.01]));
645        extrapolator.add_solution(Array1::from_vec(vec![1.005, 2.005]));
646
647        let result = extrapolator.extrapolate();
648        assert!(result.is_ok());
649    }
650
651    #[test]
652    fn test_spectral_error_indicator() {
653        let mut indicator = SpectralErrorIndicator::<f64>::new(5, 0.5);
654
655        for i in 0..6 {
656            let solution = Array1::from_vec(vec![i as f64, (i as f64).sin()]);
657            indicator.add_solution(solution);
658        }
659
660        let result = indicator.compute_indicator();
661        assert!(result.is_ok());
662    }
663}