scirs2_stats/
numerical_stability_analyzer.rs

1//! Numerical stability analysis framework
2//!
3//! This module provides comprehensive analysis of numerical stability
4//! for statistical functions across various conditions and edge cases.
5//!
6//! ## Features
7//!
8//! - Condition number analysis
9//! - Error propagation tracking  
10//! - Edge case robustness testing
11//! - Precision loss detection
12//! - Algorithm stability recommendations
13
14use crate::error::StatsResult;
15use scirs2_core::ndarray::{Array1, ArrayView1};
16use serde::{Deserialize, Serialize};
17use std::collections::HashMap;
18
19/// Comprehensive numerical stability analyzer
20#[derive(Debug)]
21pub struct NumericalStabilityAnalyzer {
22    config: StabilityConfig,
23    analysis_results: HashMap<String, StabilityAnalysisResult>,
24}
25
26/// Configuration for stability analysis
27#[derive(Debug, Clone)]
28pub struct StabilityConfig {
29    /// Tolerance for considering values as numerically zero
30    pub zero_tolerance: f64,
31    /// Tolerance for detecting loss of precision
32    pub precision_tolerance: f64,
33    /// Maximum condition number considered stable
34    pub max_condition_number: f64,
35    /// Number of perturbation tests to run
36    pub perturbation_tests: usize,
37    /// Magnitude of perturbations for sensitivity analysis
38    pub perturbation_magnitude: f64,
39    /// Enable testing with extreme values
40    pub test_extreme_values: bool,
41    /// Enable testing with nearly singular matrices
42    pub test_singular_cases: bool,
43}
44
45/// Result of numerical stability analysis
46#[derive(Debug, Clone, Serialize, Deserialize)]
47pub struct StabilityAnalysisResult {
48    /// Function name analyzed
49    pub function_name: String,
50    /// Overall stability grade
51    pub stability_grade: StabilityGrade,
52    /// Condition number analysis
53    pub condition_analysis: ConditionNumberAnalysis,
54    /// Error propagation analysis
55    pub error_propagation: ErrorPropagationAnalysis,
56    /// Edge case robustness
57    pub edge_case_robustness: EdgeCaseRobustness,
58    /// Precision loss analysis
59    pub precision_analysis: PrecisionAnalysis,
60    /// Recommendations for improvement
61    pub recommendations: Vec<StabilityRecommendation>,
62    /// Overall stability score (0-100)
63    pub stability_score: f64,
64}
65
66/// Stability grading scale
67#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
68pub enum StabilityGrade {
69    /// Excellent numerical stability
70    Excellent,
71    /// Good stability with minor issues
72    Good,
73    /// Acceptable stability with some concerns
74    Acceptable,
75    /// Poor stability with significant issues
76    Poor,
77    /// Numerically unstable
78    Unstable,
79}
80
81/// Condition number analysis results
82#[derive(Debug, Clone, Serialize, Deserialize)]
83pub struct ConditionNumberAnalysis {
84    /// Estimated condition number
85    pub condition_number: f64,
86    /// Classification of conditioning
87    pub conditioning_class: ConditioningClass,
88    /// Loss of accuracy estimate (digits)
89    pub accuracy_loss_digits: f64,
90    /// Sensitivity to input perturbations
91    pub input_sensitivity: f64,
92}
93
94/// Classification of matrix conditioning
95#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
96pub enum ConditioningClass {
97    /// Well-conditioned (condition number < 1e12)
98    WellConditioned,
99    /// Moderately conditioned (1e12 <= cond < 1e14)
100    ModeratelyConditioned,
101    /// Poorly conditioned (1e14 <= cond < 1e16)
102    PoorlyConditioned,
103    /// Nearly singular (condition number >= 1e16)
104    NearlySingular,
105}
106
107/// Error propagation analysis
108#[derive(Debug, Clone, Serialize, Deserialize)]
109pub struct ErrorPropagationAnalysis {
110    /// Forward error bound
111    pub forward_error_bound: f64,
112    /// Backward error bound
113    pub backward_error_bound: f64,
114    /// Error amplification factor
115    pub error_amplification: f64,
116    /// Stability in the presence of rounding errors
117    pub rounding_error_stability: f64,
118}
119
120/// Edge case robustness analysis
121#[derive(Debug, Clone, Serialize, Deserialize)]
122pub struct EdgeCaseRobustness {
123    /// Handles infinite values correctly
124    pub handles_infinity: bool,
125    /// Handles NaN values correctly
126    pub handles_nan: bool,
127    /// Handles zero values correctly
128    pub handles_zero: bool,
129    /// Handles very large values correctly
130    pub handles_large_values: bool,
131    /// Handles very small values correctly
132    pub handles_small_values: bool,
133    /// Proportion of edge cases handled correctly
134    pub edge_case_success_rate: f64,
135}
136
137/// Precision loss analysis
138#[derive(Debug, Clone, Serialize, Deserialize)]
139pub struct PrecisionAnalysis {
140    /// Estimated bits of precision lost
141    pub precision_loss_bits: f64,
142    /// Relative precision remaining
143    pub relative_precision: f64,
144    /// Cancellation errors detected
145    pub cancellation_errors: Vec<CancellationError>,
146    /// Overflow/underflow risks
147    pub overflow_underflow_risk: OverflowRisk,
148}
149
150/// Cancellation error detection
151#[derive(Debug, Clone, Serialize, Deserialize)]
152pub struct CancellationError {
153    /// Location where cancellation occurs
154    pub location: String,
155    /// Magnitude of precision loss
156    pub precision_loss: f64,
157    /// Suggested mitigation
158    pub mitigation: String,
159}
160
161/// Overflow/underflow risk assessment
162#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
163pub enum OverflowRisk {
164    /// No overflow/underflow risk
165    None,
166    /// Low risk
167    Low,
168    /// Moderate risk
169    Moderate,
170    /// High risk
171    High,
172    /// Certain overflow/underflow
173    Certain,
174}
175
176/// Stability improvement recommendation
177#[derive(Debug, Clone, Serialize, Deserialize)]
178pub struct StabilityRecommendation {
179    /// Type of recommendation
180    pub recommendation_type: RecommendationType,
181    /// Description of the issue
182    pub description: String,
183    /// Suggested improvement
184    pub suggestion: String,
185    /// Priority of this recommendation
186    pub priority: RecommendationPriority,
187    /// Expected improvement in stability
188    pub expected_improvement: f64,
189}
190
191/// Types of stability recommendations
192#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
193pub enum RecommendationType {
194    /// Algorithm modification
195    Algorithm,
196    /// Numerical technique improvement
197    Numerical,
198    /// Input validation enhancement
199    InputValidation,
200    /// Precision management
201    Precision,
202    /// Error handling improvement
203    ErrorHandling,
204}
205
206/// Priority levels for recommendations
207#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
208pub enum RecommendationPriority {
209    /// Critical - must be addressed
210    Critical,
211    /// High priority
212    High,
213    /// Medium priority
214    Medium,
215    /// Low priority
216    Low,
217}
218
219impl Default for StabilityConfig {
220    fn default() -> Self {
221        Self {
222            zero_tolerance: 1e-15,
223            precision_tolerance: 1e-12,
224            max_condition_number: 1e12,
225            perturbation_tests: 100,
226            perturbation_magnitude: 1e-10,
227            test_extreme_values: true,
228            test_singular_cases: true,
229        }
230    }
231}
232
233impl NumericalStabilityAnalyzer {
234    /// Create a new numerical stability analyzer
235    pub fn new(config: StabilityConfig) -> Self {
236        Self {
237            config,
238            analysis_results: HashMap::new(),
239        }
240    }
241
242    /// Create analyzer with default configuration
243    pub fn default() -> Self {
244        Self::new(StabilityConfig::default())
245    }
246
247    /// Analyze numerical stability of a statistical function
248    pub fn analyze_function<F>(
249        &mut self,
250        function_name: &str,
251        function: F,
252        testdata: &ArrayView1<f64>,
253    ) -> StatsResult<StabilityAnalysisResult>
254    where
255        F: Fn(&ArrayView1<f64>) -> StatsResult<f64>,
256    {
257        // Condition number analysis
258        let condition_analysis = self.analyze_condition_number(testdata)?;
259
260        // Error propagation analysis
261        let error_propagation = self.analyze_error_propagation(&function, testdata)?;
262
263        // Edge case robustness testing
264        let edge_case_robustness = self.test_edge_case_robustness(&function)?;
265
266        // Precision loss analysis
267        let precision_analysis = self.analyze_precision_loss(&function, testdata)?;
268
269        // Generate recommendations
270        let recommendations = self.generate_recommendations(
271            &condition_analysis,
272            &error_propagation,
273            &edge_case_robustness,
274            &precision_analysis,
275        );
276
277        // Calculate overall stability score
278        let stability_score = self.calculate_stability_score(
279            &condition_analysis,
280            &error_propagation,
281            &edge_case_robustness,
282            &precision_analysis,
283        );
284
285        // Determine stability grade
286        let stability_grade = self.grade_stability(stability_score);
287
288        let result = StabilityAnalysisResult {
289            function_name: function_name.to_string(),
290            stability_grade,
291            condition_analysis,
292            error_propagation,
293            edge_case_robustness,
294            precision_analysis,
295            recommendations,
296            stability_score,
297        };
298
299        self.analysis_results
300            .insert(function_name.to_string(), result.clone());
301        Ok(result)
302    }
303
304    /// Analyze condition number and sensitivity
305    fn analyze_condition_number(
306        &self,
307        data: &ArrayView1<f64>,
308    ) -> StatsResult<ConditionNumberAnalysis> {
309        // For 1D data, we estimate conditioning based on data characteristics
310        let data_range = data.iter().fold(0.0f64, |acc, &x| acc.max(x.abs()));
311        let data_min = data.iter().fold(f64::INFINITY, |acc, &x| acc.min(x.abs()));
312
313        // Estimate condition number as ratio of max to min (simplified)
314        let condition_number = if data_min > self.config.zero_tolerance {
315            data_range / data_min
316        } else {
317            f64::INFINITY
318        };
319
320        let conditioning_class = if condition_number < 1e12 {
321            ConditioningClass::WellConditioned
322        } else if condition_number < 1e14 {
323            ConditioningClass::ModeratelyConditioned
324        } else if condition_number < 1e16 {
325            ConditioningClass::PoorlyConditioned
326        } else {
327            ConditioningClass::NearlySingular
328        };
329
330        // Estimate accuracy loss (simplified)
331        let accuracy_loss_digits = condition_number.log10().max(0.0);
332
333        // Input sensitivity (simplified estimate)
334        let input_sensitivity = condition_number / 1e16;
335
336        Ok(ConditionNumberAnalysis {
337            condition_number,
338            conditioning_class,
339            accuracy_loss_digits,
340            input_sensitivity,
341        })
342    }
343
344    /// Analyze error propagation through perturbation testing
345    fn analyze_error_propagation<F>(
346        &self,
347        function: &F,
348        data: &ArrayView1<f64>,
349    ) -> StatsResult<ErrorPropagationAnalysis>
350    where
351        F: Fn(&ArrayView1<f64>) -> StatsResult<f64>,
352    {
353        // Get reference result
354        let reference_result = function(data)?;
355
356        let mut forward_errors = Vec::new();
357        let mut backward_errors = Vec::new();
358
359        // Perform perturbation tests
360        for i in 0..self.config.perturbation_tests.min(data.len()) {
361            let mut perturbeddata = data.to_owned();
362            let perturbation = self.config.perturbation_magnitude * perturbeddata[i].abs().max(1.0);
363            perturbeddata[i] += perturbation;
364
365            if let Ok(perturbed_result) = function(&perturbeddata.view()) {
366                let forward_error = (perturbed_result - reference_result).abs();
367                let backward_error = perturbation.abs();
368
369                forward_errors.push(forward_error);
370                backward_errors.push(backward_error);
371            }
372        }
373
374        let forward_error_bound = forward_errors.iter().fold(0.0f64, |acc, &x| acc.max(x));
375        let backward_error_bound = backward_errors.iter().fold(0.0f64, |acc, &x| acc.max(x));
376
377        // Error amplification factor
378        let error_amplification = if backward_error_bound > 0.0 {
379            forward_error_bound / backward_error_bound
380        } else {
381            1.0
382        };
383
384        // Simplified rounding error stability estimate
385        let rounding_error_stability = 1.0 / (1.0 + error_amplification);
386
387        Ok(ErrorPropagationAnalysis {
388            forward_error_bound,
389            backward_error_bound,
390            error_amplification,
391            rounding_error_stability,
392        })
393    }
394
395    /// Test robustness with edge cases
396    fn test_edge_case_robustness<F>(&self, function: &F) -> StatsResult<EdgeCaseRobustness>
397    where
398        F: Fn(&ArrayView1<f64>) -> StatsResult<f64>,
399    {
400        let mut tests_passed = 0;
401        let mut total_tests = 0;
402
403        let mut handles_infinity = false;
404        let mut handles_nan = false;
405        let mut handles_zero = false;
406        let mut handles_large_values = false;
407        let mut handles_small_values = false;
408
409        // Test with infinity
410        if self.config.test_extreme_values {
411            total_tests += 1;
412            let infdata = Array1::from_vec(vec![f64::INFINITY, 1.0, 2.0]);
413            if let Ok(result) = function(&infdata.view()) {
414                if result.is_finite() || result.is_infinite() {
415                    handles_infinity = true;
416                    tests_passed += 1;
417                }
418            }
419
420            // Test with NaN
421            total_tests += 1;
422            let nandata = Array1::from_vec(vec![f64::NAN, 1.0, 2.0]);
423            if let Ok(result) = function(&nandata.view()) {
424                if result.is_nan() || result.is_finite() {
425                    handles_nan = true;
426                    tests_passed += 1;
427                }
428            }
429
430            // Test with zeros
431            total_tests += 1;
432            let zerodata = Array1::from_vec(vec![0.0, 0.0, 0.0]);
433            if function(&zerodata.view()).is_ok() {
434                handles_zero = true;
435                tests_passed += 1;
436            }
437
438            // Test with very large values
439            total_tests += 1;
440            let largedata = Array1::from_vec(vec![1e100, 1e200, 1e300]);
441            if function(&largedata.view()).is_ok() {
442                handles_large_values = true;
443                tests_passed += 1;
444            }
445
446            // Test with very small values
447            total_tests += 1;
448            let smalldata = Array1::from_vec(vec![1e-100, 1e-200, 1e-300]);
449            if function(&smalldata.view()).is_ok() {
450                handles_small_values = true;
451                tests_passed += 1;
452            }
453        }
454
455        let edge_case_success_rate = if total_tests > 0 {
456            tests_passed as f64 / total_tests as f64
457        } else {
458            1.0
459        };
460
461        Ok(EdgeCaseRobustness {
462            handles_infinity,
463            handles_nan,
464            handles_zero,
465            handles_large_values,
466            handles_small_values,
467            edge_case_success_rate,
468        })
469    }
470
471    /// Analyze precision loss and numerical issues
472    fn analyze_precision_loss<F>(
473        &self,
474        function: &F,
475        data: &ArrayView1<f64>,
476    ) -> StatsResult<PrecisionAnalysis>
477    where
478        F: Fn(&ArrayView1<f64>) -> StatsResult<f64>,
479    {
480        // Simplified precision analysis
481        let result = function(data)?;
482
483        // Estimate precision loss based on result characteristics
484        let precision_loss_bits = if result.abs() < self.config.precision_tolerance {
485            16.0 // Significant precision loss
486        } else if result.abs() < 1e-10 {
487            8.0 // Moderate precision loss
488        } else {
489            2.0 // Minimal precision loss
490        };
491
492        let relative_precision = 1.0 - (precision_loss_bits / 64.0);
493
494        // Detect potential cancellation errors (simplified)
495        let mut cancellation_errors = Vec::new();
496        if data.iter().any(|&x| x.abs() < self.config.zero_tolerance) {
497            cancellation_errors.push(CancellationError {
498                location: "inputdata".to_string(),
499                precision_loss: precision_loss_bits,
500                mitigation: "Use higher precision arithmetic or alternative algorithm".to_string(),
501            });
502        }
503
504        // Assess overflow/underflow risk
505        let max_val = data.iter().fold(0.0f64, |acc, &x| acc.max(x.abs()));
506        let overflow_underflow_risk = if max_val > 1e100 {
507            OverflowRisk::High
508        } else if max_val > 1e50 {
509            OverflowRisk::Moderate
510        } else if max_val < 1e-100 {
511            OverflowRisk::Moderate
512        } else {
513            OverflowRisk::Low
514        };
515
516        Ok(PrecisionAnalysis {
517            precision_loss_bits,
518            relative_precision,
519            cancellation_errors,
520            overflow_underflow_risk,
521        })
522    }
523
524    /// Generate stability improvement recommendations
525    fn generate_recommendations(
526        &self,
527        condition_analysis: &ConditionNumberAnalysis,
528        error_propagation: &ErrorPropagationAnalysis,
529        edge_case_robustness: &EdgeCaseRobustness,
530        precision_analysis: &PrecisionAnalysis,
531    ) -> Vec<StabilityRecommendation> {
532        let mut recommendations = Vec::new();
533
534        // Condition number recommendations
535        if matches!(
536            condition_analysis.conditioning_class,
537            ConditioningClass::PoorlyConditioned | ConditioningClass::NearlySingular
538        ) {
539            recommendations.push(StabilityRecommendation {
540                recommendation_type: RecommendationType::Algorithm,
541                description: "Poor conditioning detected".to_string(),
542                suggestion: "Consider using regularization or alternative algorithms for ill-conditioned problems".to_string(),
543                priority: RecommendationPriority::High,
544                expected_improvement: 30.0,
545            });
546        }
547
548        // Error _propagation recommendations
549        if error_propagation.error_amplification > 100.0 {
550            recommendations.push(StabilityRecommendation {
551                recommendation_type: RecommendationType::Numerical,
552                description: "High error amplification detected".to_string(),
553                suggestion: "Implement error _analysis and use more stable numerical methods"
554                    .to_string(),
555                priority: RecommendationPriority::High,
556                expected_improvement: 25.0,
557            });
558        }
559
560        // Edge case recommendations
561        if edge_case_robustness.edge_case_success_rate < 0.8 {
562            recommendations.push(StabilityRecommendation {
563                recommendation_type: RecommendationType::InputValidation,
564                description: "Poor edge case handling".to_string(),
565                suggestion:
566                    "Improve input validation and add special case handling for extreme values"
567                        .to_string(),
568                priority: RecommendationPriority::Medium,
569                expected_improvement: 20.0,
570            });
571        }
572
573        // Precision recommendations
574        if precision_analysis.precision_loss_bits > 10.0 {
575            recommendations.push(StabilityRecommendation {
576                recommendation_type: RecommendationType::Precision,
577                description: "Significant precision loss detected".to_string(),
578                suggestion:
579                    "Consider using higher precision arithmetic or numerically stable algorithms"
580                        .to_string(),
581                priority: RecommendationPriority::High,
582                expected_improvement: 35.0,
583            });
584        }
585
586        recommendations
587    }
588
589    /// Calculate overall stability score
590    fn calculate_stability_score(
591        &self,
592        condition_analysis: &ConditionNumberAnalysis,
593        error_propagation: &ErrorPropagationAnalysis,
594        edge_case_robustness: &EdgeCaseRobustness,
595        precision_analysis: &PrecisionAnalysis,
596    ) -> f64 {
597        let mut score = 100.0;
598
599        // Penalize poor conditioning
600        score -= match condition_analysis.conditioning_class {
601            ConditioningClass::WellConditioned => 0.0,
602            ConditioningClass::ModeratelyConditioned => 10.0,
603            ConditioningClass::PoorlyConditioned => 25.0,
604            ConditioningClass::NearlySingular => 40.0,
605        };
606
607        // Penalize high error amplification
608        score -= (error_propagation.error_amplification.log10() * 5.0).min(30.0);
609
610        // Penalize poor edge case handling
611        score -= (1.0 - edge_case_robustness.edge_case_success_rate) * 20.0;
612
613        // Penalize precision loss
614        score -= (precision_analysis.precision_loss_bits / 64.0) * 30.0;
615
616        score.max(0.0)
617    }
618
619    /// Grade overall stability
620    fn grade_stability(&self, score: f64) -> StabilityGrade {
621        if score >= 90.0 {
622            StabilityGrade::Excellent
623        } else if score >= 75.0 {
624            StabilityGrade::Good
625        } else if score >= 60.0 {
626            StabilityGrade::Acceptable
627        } else if score >= 40.0 {
628            StabilityGrade::Poor
629        } else {
630            StabilityGrade::Unstable
631        }
632    }
633
634    /// Generate comprehensive stability report
635    pub fn generate_stability_report(&self) -> StabilityReport {
636        let results: Vec<_> = self.analysis_results.values().cloned().collect();
637
638        let total_functions = results.len();
639        let excellent_count = results
640            .iter()
641            .filter(|r| r.stability_grade == StabilityGrade::Excellent)
642            .count();
643        let good_count = results
644            .iter()
645            .filter(|r| r.stability_grade == StabilityGrade::Good)
646            .count();
647        let acceptable_count = results
648            .iter()
649            .filter(|r| r.stability_grade == StabilityGrade::Acceptable)
650            .count();
651        let poor_count = results
652            .iter()
653            .filter(|r| r.stability_grade == StabilityGrade::Poor)
654            .count();
655        let unstable_count = results
656            .iter()
657            .filter(|r| r.stability_grade == StabilityGrade::Unstable)
658            .count();
659
660        let average_score = if total_functions > 0 {
661            results.iter().map(|r| r.stability_score).sum::<f64>() / total_functions as f64
662        } else {
663            0.0
664        };
665
666        StabilityReport {
667            total_functions,
668            excellent_count,
669            good_count,
670            acceptable_count,
671            poor_count,
672            unstable_count,
673            average_score,
674            function_results: results,
675            generated_at: chrono::Utc::now(),
676        }
677    }
678}
679
680/// Comprehensive stability analysis report
681#[derive(Debug, Clone, Serialize, Deserialize)]
682pub struct StabilityReport {
683    /// Total number of functions analyzed
684    pub total_functions: usize,
685    /// Number of functions with excellent stability
686    pub excellent_count: usize,
687    /// Number of functions with good stability
688    pub good_count: usize,
689    /// Number of functions with acceptable stability
690    pub acceptable_count: usize,
691    /// Number of functions with poor stability
692    pub poor_count: usize,
693    /// Number of unstable functions
694    pub unstable_count: usize,
695    /// Average stability score across all functions
696    pub average_score: f64,
697    /// Detailed results for each function
698    pub function_results: Vec<StabilityAnalysisResult>,
699    /// Timestamp when report was generated
700    pub generated_at: chrono::DateTime<chrono::Utc>,
701}
702
703#[cfg(test)]
704mod tests {
705    use super::*;
706    use crate::descriptive::mean;
707
708    #[test]
709    fn test_stability_analyzer_creation() {
710        let analyzer = NumericalStabilityAnalyzer::default();
711        assert_eq!(analyzer.config.zero_tolerance, 1e-15);
712        assert_eq!(analyzer.config.precision_tolerance, 1e-12);
713    }
714
715    #[test]
716    fn test_condition_number_analysis() {
717        let analyzer = NumericalStabilityAnalyzer::default();
718        let data = Array1::from_vec(vec![1.0, 2.0, 3.0, 4.0, 5.0]);
719        let result = analyzer
720            .analyze_condition_number(&data.view())
721            .expect("Operation failed");
722
723        assert_eq!(
724            result.conditioning_class,
725            ConditioningClass::WellConditioned
726        );
727        assert!(result.condition_number > 0.0);
728    }
729
730    #[test]
731    fn test_stability_grading() {
732        let analyzer = NumericalStabilityAnalyzer::default();
733
734        assert_eq!(analyzer.grade_stability(95.0), StabilityGrade::Excellent);
735        assert_eq!(analyzer.grade_stability(80.0), StabilityGrade::Good);
736        assert_eq!(analyzer.grade_stability(65.0), StabilityGrade::Acceptable);
737        assert_eq!(analyzer.grade_stability(45.0), StabilityGrade::Poor);
738        assert_eq!(analyzer.grade_stability(20.0), StabilityGrade::Unstable);
739    }
740
741    #[test]
742    fn test_mean_stability_analysis() {
743        let mut analyzer = NumericalStabilityAnalyzer::default();
744        let data = Array1::from_vec(vec![1.0, 2.0, 3.0, 4.0, 5.0]);
745
746        let result = analyzer
747            .analyze_function("mean", |x| mean(x), &data.view())
748            .expect("Operation failed");
749
750        assert_eq!(result.function_name, "mean");
751        assert!(matches!(
752            result.stability_grade,
753            StabilityGrade::Excellent | StabilityGrade::Good
754        ));
755        assert!(result.stability_score > 50.0);
756    }
757}