Skip to main content

scirs2_stats/
property_based_tests_v2.rs

1//! Enhanced property-based testing framework for mathematical invariants (v2)
2//!
3//! This module provides comprehensive property-based testing capabilities for
4//! statistical operations, ensuring mathematical correctness, numerical stability,
5//! and consistency across different computational approaches.
6
7use crate::error::{StatsError, StatsResult};
8use scirs2_core::ndarray::{Array1, Array2, ArrayView1, ArrayView2};
9use scirs2_core::numeric::{Float, NumCast, One, Zero};
10use scirs2_core::random::{rng, rngs::StdRng, Rng, RngExt, SeedableRng};
11use scirs2_core::{parallel_ops::*, simd_ops::SimdUnifiedOps, validation::*};
12use std::collections::HashMap;
13use std::fmt::Debug;
14use std::marker::PhantomData;
15
16/// Property-based test configuration
17#[derive(Debug, Clone)]
18pub struct PropertyTestConfig {
19    /// Number of test cases to generate
20    pub num_test_cases: usize,
21    /// Random seed for reproducibility
22    pub seed: Option<u64>,
23    /// Tolerance for floating-point comparisons
24    pub tolerance: f64,
25    /// Maximum data size for generated test cases
26    pub maxdatasize: usize,
27    /// Minimum data size for generated test cases
28    pub mindatasize: usize,
29    /// Enable parallel test execution
30    pub parallel_execution: bool,
31    /// Test timeout in milliseconds
32    pub timeout_ms: u64,
33    /// Enable detailed failure reporting
34    pub detailed_failures: bool,
35}
36
37impl Default for PropertyTestConfig {
38    fn default() -> Self {
39        Self {
40            num_test_cases: 1000,
41            seed: Some(42),
42            tolerance: 1e-10,
43            maxdatasize: 10000,
44            mindatasize: 5,
45            parallel_execution: true,
46            timeout_ms: 30000,
47            detailed_failures: true,
48        }
49    }
50}
51
52/// Test result status
53#[derive(Debug, Clone, PartialEq)]
54pub enum TestStatus {
55    Pass,
56    Fail(String),
57    Timeout,
58    Error(String),
59}
60
61/// Individual property test result
62#[derive(Debug, Clone)]
63pub struct PropertyTestResult {
64    /// Test property name
65    pub property_name: String,
66    /// Test case identifier
67    pub test_case_id: usize,
68    /// Test status
69    pub status: TestStatus,
70    /// Input data that caused failure (if any)
71    pub failing_input: Option<TestInput>,
72    /// Expected vs actual values (if applicable)
73    pub comparison: Option<(f64, f64)>,
74    /// Execution time in microseconds
75    pub execution_time_us: u64,
76}
77
78/// Test input data for reproducibility
79#[derive(Debug, Clone)]
80pub struct TestInput {
81    /// Input arrays
82    pub arrays: Vec<Array1<f64>>,
83    /// Input matrices
84    pub matrices: Vec<Array2<f64>>,
85    /// Scalar parameters
86    pub scalars: Vec<f64>,
87    /// Boolean flags
88    pub flags: Vec<bool>,
89    /// String parameters
90    pub strings: Vec<String>,
91}
92
93/// Comprehensive test suite result
94#[derive(Debug, Clone)]
95pub struct TestSuiteResult {
96    /// Total number of tests run
97    pub total_tests: usize,
98    /// Number of passed tests
99    pub passed_tests: usize,
100    /// Number of failed tests
101    pub failed_tests: usize,
102    /// Number of timed out tests
103    pub timeout_tests: usize,
104    /// Number of error tests
105    pub error_tests: usize,
106    /// Individual test results
107    pub test_results: Vec<PropertyTestResult>,
108    /// Summary statistics
109    pub summary: TestSummary,
110}
111
112/// Test summary statistics
113#[derive(Debug, Clone)]
114pub struct TestSummary {
115    /// Success rate (0.0 to 1.0)
116    pub success_rate: f64,
117    /// Average execution time in microseconds
118    pub avg_execution_time_us: f64,
119    /// Maximum execution time in microseconds
120    pub max_execution_time_us: u64,
121    /// Minimum execution time in microseconds
122    pub min_execution_time_us: u64,
123    /// Properties tested
124    pub properties_tested: Vec<String>,
125    /// Most common failure reasons
126    pub failure_reasons: HashMap<String, usize>,
127}
128
129/// Enhanced property-based test framework
130pub struct PropertyBasedTestFramework<F> {
131    config: PropertyTestConfig,
132    rng: StdRng,
133    phantom: PhantomData<F>,
134}
135
136impl<F> PropertyBasedTestFramework<F>
137where
138    F: Float
139        + NumCast
140        + SimdUnifiedOps
141        + Zero
142        + One
143        + PartialOrd
144        + Copy
145        + Send
146        + Sync
147        + Debug
148        + std::fmt::Display,
149{
150    /// Create new property-based test framework
151    pub fn new(config: PropertyTestConfig) -> Self {
152        let rng = match config.seed {
153            Some(seed) => StdRng::seed_from_u64(seed),
154            None => StdRng::from_rng(&mut scirs2_core::random::thread_rng()),
155        };
156
157        Self {
158            config,
159            rng,
160            phantom: PhantomData,
161        }
162    }
163
164    /// Test mathematical invariants for basic statistics
165    pub fn test_descriptive_statistics_invariants(&mut self) -> StatsResult<TestSuiteResult> {
166        let mut results = Vec::new();
167        let start_time = std::time::Instant::now();
168
169        // Test mean properties
170        results.extend(self.test_mean_properties()?);
171
172        // Test variance properties
173        results.extend(self.test_variance_properties()?);
174
175        // Test standard deviation properties
176        results.extend(self.test_std_properties()?);
177
178        // Test skewness properties
179        results.extend(self.test_skewness_properties()?);
180
181        // Test kurtosis properties
182        results.extend(self.test_kurtosis_properties()?);
183
184        // Test quantile properties
185        results.extend(self.test_quantile_properties()?);
186
187        Ok(self.compile_test_results(results, start_time.elapsed()))
188    }
189
190    /// Test correlation analysis invariants
191    pub fn test_correlation_invariants(&mut self) -> StatsResult<TestSuiteResult> {
192        let mut results = Vec::new();
193        let start_time = std::time::Instant::now();
194
195        // Test Pearson correlation properties
196        results.extend(self.test_pearson_correlation_properties()?);
197
198        // Test Spearman correlation properties
199        results.extend(self.test_spearman_correlation_properties()?);
200
201        // Test Kendall tau properties
202        results.extend(self.test_kendall_tau_properties()?);
203
204        // Test correlation matrix properties
205        results.extend(self.test_correlation_matrix_properties()?);
206
207        Ok(self.compile_test_results(results, start_time.elapsed()))
208    }
209
210    /// Test regression analysis invariants
211    pub fn test_regression_invariants(&mut self) -> StatsResult<TestSuiteResult> {
212        let mut results = Vec::new();
213        let start_time = std::time::Instant::now();
214
215        // Test linear regression properties
216        results.extend(self.test_linear_regression_properties()?);
217
218        // Test polynomial regression properties
219        results.extend(self.test_polynomial_regression_properties()?);
220
221        // Test robust regression properties
222        results.extend(self.test_robust_regression_properties()?);
223
224        Ok(self.compile_test_results(results, start_time.elapsed()))
225    }
226
227    /// Test statistical test invariants
228    pub fn test_statistical_test_invariants(&mut self) -> StatsResult<TestSuiteResult> {
229        let mut results = Vec::new();
230        let start_time = std::time::Instant::now();
231
232        // Test t-test properties
233        results.extend(self.test_ttest_properties()?);
234
235        // Test ANOVA properties
236        results.extend(self.test_anova_properties()?);
237
238        // Test non-parametric test properties
239        results.extend(self.test_nonparametric_properties()?);
240
241        // Test normality test properties
242        results.extend(self.test_normality_test_properties()?);
243
244        Ok(self.compile_test_results(results, start_time.elapsed()))
245    }
246
247    /// Test SIMD vs scalar consistency
248    pub fn test_simd_scalar_consistency(&mut self) -> StatsResult<TestSuiteResult> {
249        let mut results = Vec::new();
250        let start_time = std::time::Instant::now();
251
252        // Test SIMD mean vs scalar mean
253        results.extend(self.test_simd_vs_scalar_mean()?);
254
255        // Test SIMD variance vs scalar variance
256        results.extend(self.test_simd_vs_scalar_variance()?);
257
258        // Test SIMD correlation vs scalar correlation
259        results.extend(self.test_simd_vs_scalar_correlation()?);
260
261        Ok(self.compile_test_results(results, start_time.elapsed()))
262    }
263
264    /// Test parallel vs sequential consistency
265    pub fn test_parallel_sequential_consistency(&mut self) -> StatsResult<TestSuiteResult> {
266        let mut results = Vec::new();
267        let start_time = std::time::Instant::now();
268
269        // Test parallel mean vs sequential mean
270        results.extend(self.test_parallel_vs_sequential_mean()?);
271
272        // Test parallel correlation vs sequential correlation
273        results.extend(self.test_parallel_vs_sequential_correlation()?);
274
275        // Test parallel bootstrap vs sequential bootstrap
276        results.extend(self.test_parallel_vs_sequentialbootstrap()?);
277
278        Ok(self.compile_test_results(results, start_time.elapsed()))
279    }
280
281    /// Test numerical stability properties
282    pub fn test_numerical_stability(&mut self) -> StatsResult<TestSuiteResult> {
283        let mut results = Vec::new();
284        let start_time = std::time::Instant::now();
285
286        // Test with extreme values
287        results.extend(self.test_extreme_values_stability()?);
288
289        // Test with near-zero values
290        results.extend(self.test_near_zero_stability()?);
291
292        // Test with large values
293        results.extend(self.test_large_values_stability()?);
294
295        // Test with ill-conditioned data
296        results.extend(self.test_ill_conditioned_stability()?);
297
298        Ok(self.compile_test_results(results, start_time.elapsed()))
299    }
300
301    // Implementation of individual property tests
302
303    fn test_mean_properties(&mut self) -> StatsResult<Vec<PropertyTestResult>> {
304        let mut results = Vec::new();
305
306        for test_case_id in 0..self.config.num_test_cases {
307            let start_time = std::time::Instant::now();
308
309            // Generate test data
310            let data = self.generate_random_array()?;
311            let input = TestInput {
312                arrays: vec![data.clone()],
313                matrices: vec![],
314                scalars: vec![],
315                flags: vec![],
316                strings: vec![],
317            };
318
319            // Test mean invariant: mean of constants should equal the constant
320            let constant_value = 5.0;
321            let constantdata = Array1::from_elem(data.len(), constant_value);
322
323            let result = match crate::descriptive::mean(&constantdata.view()) {
324                Ok(computed_mean) => {
325                    let diff = (computed_mean - constant_value).abs();
326                    if diff < self.config.tolerance {
327                        PropertyTestResult {
328                            property_name: "mean_of_constants".to_string(),
329                            test_case_id,
330                            status: TestStatus::Pass,
331                            failing_input: None,
332                            comparison: Some((computed_mean, constant_value)),
333                            execution_time_us: start_time.elapsed().as_micros() as u64,
334                        }
335                    } else {
336                        PropertyTestResult {
337                            property_name: "mean_of_constants".to_string(),
338                            test_case_id,
339                            status: TestStatus::Fail(format!(
340                                "Mean of constants failed: expected {}, got {}, diff: {}",
341                                constant_value, computed_mean, diff
342                            )),
343                            failing_input: Some(input),
344                            comparison: Some((computed_mean, constant_value)),
345                            execution_time_us: start_time.elapsed().as_micros() as u64,
346                        }
347                    }
348                }
349                Err(e) => PropertyTestResult {
350                    property_name: "mean_of_constants".to_string(),
351                    test_case_id,
352                    status: TestStatus::Error(format!("Error computing mean: {}", e)),
353                    failing_input: Some(input),
354                    comparison: None,
355                    execution_time_us: start_time.elapsed().as_micros() as u64,
356                },
357            };
358
359            results.push(result);
360
361            // Test linearity: mean(a*X + b) = a*mean(X) + b
362            if let Ok(original_mean) = crate::descriptive::mean(&data.view()) {
363                let a = self.rng.random_range(0.1..10.0);
364                let b = self.rng.random_range(-5.0..5.0);
365
366                let transformeddata = data.mapv(|x| a * x + b);
367
368                if let Ok(transformed_mean) = crate::descriptive::mean(&transformeddata.view()) {
369                    let expected_mean = a * original_mean + b;
370                    let diff = (transformed_mean - expected_mean).abs();
371
372                    let result = if diff < self.config.tolerance {
373                        PropertyTestResult {
374                            property_name: "mean_linearity".to_string(),
375                            test_case_id,
376                            status: TestStatus::Pass,
377                            failing_input: None,
378                            comparison: Some((transformed_mean, expected_mean)),
379                            execution_time_us: start_time.elapsed().as_micros() as u64,
380                        }
381                    } else {
382                        PropertyTestResult {
383                            property_name: "mean_linearity".to_string(),
384                            test_case_id,
385                            status: TestStatus::Fail(format!(
386                                "Mean linearity failed: expected {}, got {}, diff: {}",
387                                expected_mean, transformed_mean, diff
388                            )),
389                            failing_input: Some(TestInput {
390                                arrays: vec![data.clone(), transformeddata],
391                                matrices: vec![],
392                                scalars: vec![a, b, original_mean],
393                                flags: vec![],
394                                strings: vec![],
395                            }),
396                            comparison: Some((transformed_mean, expected_mean)),
397                            execution_time_us: start_time.elapsed().as_micros() as u64,
398                        }
399                    };
400
401                    results.push(result);
402                }
403            }
404        }
405
406        Ok(results)
407    }
408
409    fn test_variance_properties(&mut self) -> StatsResult<Vec<PropertyTestResult>> {
410        let mut results = Vec::new();
411
412        for test_case_id in 0..self.config.num_test_cases {
413            let start_time = std::time::Instant::now();
414
415            let data = self.generate_random_array()?;
416
417            // Test variance of constants should be zero
418            let constant_value = 3.0;
419            let constantdata = Array1::from_elem(data.len(), constant_value);
420
421            let result = match crate::descriptive::var(&constantdata.view(), 1, None) {
422                Ok(computed_variance) => {
423                    if computed_variance.abs() < self.config.tolerance {
424                        PropertyTestResult {
425                            property_name: "variance_of_constants".to_string(),
426                            test_case_id,
427                            status: TestStatus::Pass,
428                            failing_input: None,
429                            comparison: Some((computed_variance, 0.0)),
430                            execution_time_us: start_time.elapsed().as_micros() as u64,
431                        }
432                    } else {
433                        PropertyTestResult {
434                            property_name: "variance_of_constants".to_string(),
435                            test_case_id,
436                            status: TestStatus::Fail(format!(
437                                "Variance of constants should be zero, got: {}",
438                                computed_variance
439                            )),
440                            failing_input: Some(TestInput {
441                                arrays: vec![constantdata],
442                                matrices: vec![],
443                                scalars: vec![constant_value],
444                                flags: vec![],
445                                strings: vec![],
446                            }),
447                            comparison: Some((computed_variance, 0.0)),
448                            execution_time_us: start_time.elapsed().as_micros() as u64,
449                        }
450                    }
451                }
452                Err(e) => PropertyTestResult {
453                    property_name: "variance_of_constants".to_string(),
454                    test_case_id,
455                    status: TestStatus::Error(format!("Error computing variance: {}", e)),
456                    failing_input: None,
457                    comparison: None,
458                    execution_time_us: start_time.elapsed().as_micros() as u64,
459                },
460            };
461
462            results.push(result);
463
464            // Test variance scaling: var(a*X) = a²*var(X)
465            if let Ok(original_var) = crate::descriptive::var(&data.view(), 1, None) {
466                let a = self.rng.random_range(0.1..5.0);
467                let scaleddata = data.mapv(|x| a * x);
468
469                if let Ok(scaled_var) = crate::descriptive::var(&scaleddata.view(), 1, None) {
470                    let expected_var = a * a * original_var;
471                    let diff = (scaled_var - expected_var).abs();
472
473                    let result = if diff < self.config.tolerance * expected_var.abs().max(1.0) {
474                        PropertyTestResult {
475                            property_name: "variance_scaling".to_string(),
476                            test_case_id,
477                            status: TestStatus::Pass,
478                            failing_input: None,
479                            comparison: Some((scaled_var, expected_var)),
480                            execution_time_us: start_time.elapsed().as_micros() as u64,
481                        }
482                    } else {
483                        PropertyTestResult {
484                            property_name: "variance_scaling".to_string(),
485                            test_case_id,
486                            status: TestStatus::Fail(format!(
487                                "Variance scaling failed: expected {}, got {}, diff: {}",
488                                expected_var, scaled_var, diff
489                            )),
490                            failing_input: Some(TestInput {
491                                arrays: vec![data.clone(), scaleddata],
492                                matrices: vec![],
493                                scalars: vec![a, original_var],
494                                flags: vec![],
495                                strings: vec![],
496                            }),
497                            comparison: Some((scaled_var, expected_var)),
498                            execution_time_us: start_time.elapsed().as_micros() as u64,
499                        }
500                    };
501
502                    results.push(result);
503                }
504            }
505        }
506
507        Ok(results)
508    }
509
510    fn test_std_properties(&mut self) -> StatsResult<Vec<PropertyTestResult>> {
511        let mut results = Vec::new();
512
513        for test_case_id in 0..self.config.num_test_cases {
514            let start_time = std::time::Instant::now();
515
516            let data = self.generate_random_array()?;
517
518            // Test that std = sqrt(variance)
519            let variance_result = crate::descriptive::var(&data.view(), 1, None);
520            let std_result = crate::descriptive::std(&data.view(), 1, None);
521
522            let result = match (variance_result, std_result) {
523                (Ok(variance), Ok(std_dev)) => {
524                    let expected_std = variance.sqrt();
525                    let diff = (std_dev - expected_std).abs();
526
527                    if diff < self.config.tolerance * expected_std.max(1.0) {
528                        PropertyTestResult {
529                            property_name: "std_sqrt_variance".to_string(),
530                            test_case_id,
531                            status: TestStatus::Pass,
532                            failing_input: None,
533                            comparison: Some((std_dev, expected_std)),
534                            execution_time_us: start_time.elapsed().as_micros() as u64,
535                        }
536                    } else {
537                        PropertyTestResult {
538                            property_name: "std_sqrt_variance".to_string(),
539                            test_case_id,
540                            status: TestStatus::Fail(format!(
541                                "std ≠ sqrt(variance): std={}, sqrt(var)={}, diff={}",
542                                std_dev, expected_std, diff
543                            )),
544                            failing_input: Some(TestInput {
545                                arrays: vec![data.clone()],
546                                matrices: vec![],
547                                scalars: vec![variance, std_dev],
548                                flags: vec![],
549                                strings: vec![],
550                            }),
551                            comparison: Some((std_dev, expected_std)),
552                            execution_time_us: start_time.elapsed().as_micros() as u64,
553                        }
554                    }
555                }
556                _ => PropertyTestResult {
557                    property_name: "std_sqrt_variance".to_string(),
558                    test_case_id,
559                    status: TestStatus::Error("Failed to compute variance or std".to_string()),
560                    failing_input: None,
561                    comparison: None,
562                    execution_time_us: start_time.elapsed().as_micros() as u64,
563                },
564            };
565
566            results.push(result);
567        }
568
569        Ok(results)
570    }
571
572    fn test_skewness_properties(&mut self) -> StatsResult<Vec<PropertyTestResult>> {
573        let mut results = Vec::new();
574
575        for test_case_id in 0..self.config.num_test_cases {
576            let start_time = std::time::Instant::now();
577
578            // Generate symmetric data around zero
579            let n = self
580                .rng
581                .random_range(self.config.mindatasize..self.config.maxdatasize + 1);
582            let mut data = Vec::new();
583
584            for _ in 0..n / 2 {
585                let value = self.rng.random_range(-5.0..5.0);
586                data.push(value);
587                data.push(-value); // Add symmetric value
588            }
589
590            if n % 2 == 1 {
591                data.push(0.0); // Add center point for odd sizes
592            }
593
594            let data_array = Array1::from_vec(data);
595
596            // Test that symmetric data should have near-zero skewness
597            let result = match crate::descriptive::skew(&data_array.view(), false, None) {
598                Ok(skewness) => {
599                    if skewness.abs() < self.config.tolerance * 10.0 {
600                        // Allow some tolerance for finite samples
601                        PropertyTestResult {
602                            property_name: "symmetricdata_skewness".to_string(),
603                            test_case_id,
604                            status: TestStatus::Pass,
605                            failing_input: None,
606                            comparison: Some((skewness, 0.0)),
607                            execution_time_us: start_time.elapsed().as_micros() as u64,
608                        }
609                    } else {
610                        PropertyTestResult {
611                            property_name: "symmetricdata_skewness".to_string(),
612                            test_case_id,
613                            status: TestStatus::Fail(format!(
614                                "Symmetric data should have near-zero skewness, got: {}",
615                                skewness
616                            )),
617                            failing_input: Some(TestInput {
618                                arrays: vec![data_array],
619                                matrices: vec![],
620                                scalars: vec![skewness],
621                                flags: vec![],
622                                strings: vec![],
623                            }),
624                            comparison: Some((skewness, 0.0)),
625                            execution_time_us: start_time.elapsed().as_micros() as u64,
626                        }
627                    }
628                }
629                Err(e) => PropertyTestResult {
630                    property_name: "symmetricdata_skewness".to_string(),
631                    test_case_id,
632                    status: TestStatus::Error(format!("Error computing skewness: {}", e)),
633                    failing_input: None,
634                    comparison: None,
635                    execution_time_us: start_time.elapsed().as_micros() as u64,
636                },
637            };
638
639            results.push(result);
640        }
641
642        Ok(results)
643    }
644
645    fn test_kurtosis_properties(&mut self) -> StatsResult<Vec<PropertyTestResult>> {
646        let mut results = Vec::new();
647
648        for test_case_id in 0..self.config.num_test_cases {
649            let start_time = std::time::Instant::now();
650
651            // Generate normal-like data (should have kurtosis ≈ 0 for Fisher definition)
652            let n = self
653                .rng
654                .random_range(self.config.mindatasize..self.config.maxdatasize + 1);
655            let data: Vec<f64> = (0..n)
656                .map(|_| {
657                    // Box-Muller transform for normal distribution
658                    let u1: f64 = self.rng.random();
659                    let u2: f64 = self.rng.random();
660                    (-2.0 * u1.ln()).sqrt() * (2.0 * std::f64::consts::PI * u2).cos()
661                })
662                .collect();
663
664            let data_array = Array1::from_vec(data);
665
666            // Test that normal data has kurtosis ≈ 0 (Fisher definition)
667            let result = match crate::descriptive::kurtosis(&data_array.view(), true, false, None) {
668                Ok(kurtosis_val) => {
669                    // Allow larger tolerance for finite samples of normal distribution
670                    if kurtosis_val.abs() < 2.0 {
671                        PropertyTestResult {
672                            property_name: "normaldata_kurtosis".to_string(),
673                            test_case_id,
674                            status: TestStatus::Pass,
675                            failing_input: None,
676                            comparison: Some((kurtosis_val, 0.0)),
677                            execution_time_us: start_time.elapsed().as_micros() as u64,
678                        }
679                    } else {
680                        PropertyTestResult {
681                            property_name: "normaldata_kurtosis".to_string(),
682                            test_case_id,
683                            status: TestStatus::Fail(format!(
684                                "Normal data should have kurtosis ≈ 0, got: {}",
685                                kurtosis_val
686                            )),
687                            failing_input: Some(TestInput {
688                                arrays: vec![data_array],
689                                matrices: vec![],
690                                scalars: vec![kurtosis_val],
691                                flags: vec![],
692                                strings: vec![],
693                            }),
694                            comparison: Some((kurtosis_val, 0.0)),
695                            execution_time_us: start_time.elapsed().as_micros() as u64,
696                        }
697                    }
698                }
699                Err(e) => PropertyTestResult {
700                    property_name: "normaldata_kurtosis".to_string(),
701                    test_case_id,
702                    status: TestStatus::Error(format!("Error computing kurtosis: {}", e)),
703                    failing_input: None,
704                    comparison: None,
705                    execution_time_us: start_time.elapsed().as_micros() as u64,
706                },
707            };
708
709            results.push(result);
710        }
711
712        Ok(results)
713    }
714
715    fn test_quantile_properties(&mut self) -> StatsResult<Vec<PropertyTestResult>> {
716        let mut results = Vec::new();
717
718        for test_case_id in 0..self.config.num_test_cases {
719            let start_time = std::time::Instant::now();
720
721            let data = self.generate_random_array()?;
722
723            // Test that quantiles are monotonic
724            let quantiles = [0.25, 0.5, 0.75];
725            let mut computed_quantiles = Vec::new();
726
727            for &q in &quantiles {
728                if let Ok(quantile_val) = crate::quantile::quantile(
729                    &data.view(),
730                    q,
731                    crate::quantile::QuantileInterpolation::Linear,
732                ) {
733                    computed_quantiles.push(quantile_val);
734                }
735            }
736
737            let result = if computed_quantiles.len() == 3 {
738                let monotonic = computed_quantiles[0] <= computed_quantiles[1]
739                    && computed_quantiles[1] <= computed_quantiles[2];
740
741                if monotonic {
742                    PropertyTestResult {
743                        property_name: "quantiles_monotonic".to_string(),
744                        test_case_id,
745                        status: TestStatus::Pass,
746                        failing_input: None,
747                        comparison: None,
748                        execution_time_us: start_time.elapsed().as_micros() as u64,
749                    }
750                } else {
751                    PropertyTestResult {
752                        property_name: "quantiles_monotonic".to_string(),
753                        test_case_id,
754                        status: TestStatus::Fail(format!(
755                            "Quantiles not monotonic: Q25={}, Q50={}, Q75={}",
756                            computed_quantiles[0], computed_quantiles[1], computed_quantiles[2]
757                        )),
758                        failing_input: Some(TestInput {
759                            arrays: vec![data.clone()],
760                            matrices: vec![],
761                            scalars: computed_quantiles,
762                            flags: vec![],
763                            strings: vec![],
764                        }),
765                        comparison: None,
766                        execution_time_us: start_time.elapsed().as_micros() as u64,
767                    }
768                }
769            } else {
770                PropertyTestResult {
771                    property_name: "quantiles_monotonic".to_string(),
772                    test_case_id,
773                    status: TestStatus::Error("Failed to compute all quantiles".to_string()),
774                    failing_input: None,
775                    comparison: None,
776                    execution_time_us: start_time.elapsed().as_micros() as u64,
777                }
778            };
779
780            results.push(result);
781        }
782
783        Ok(results)
784    }
785
786    // Placeholder implementations for other test methods
787    fn test_pearson_correlation_properties(&mut self) -> StatsResult<Vec<PropertyTestResult>> {
788        let mut results = Vec::new();
789
790        for test_case_id in 0..self.config.num_test_cases {
791            let start_time = std::time::Instant::now();
792
793            let data = self.generate_random_array()?;
794
795            // Test correlation of data with itself should be 1.0
796            let result = match crate::correlation::pearson_r(&data.view(), &data.view()) {
797                Ok(correlation) => {
798                    let diff = (correlation - 1.0).abs();
799                    if diff < self.config.tolerance {
800                        PropertyTestResult {
801                            property_name: "pearson_self_correlation".to_string(),
802                            test_case_id,
803                            status: TestStatus::Pass,
804                            failing_input: None,
805                            comparison: Some((correlation, 1.0)),
806                            execution_time_us: start_time.elapsed().as_micros() as u64,
807                        }
808                    } else {
809                        PropertyTestResult {
810                            property_name: "pearson_self_correlation".to_string(),
811                            test_case_id,
812                            status: TestStatus::Fail(format!(
813                                "Self-correlation should be 1.0, got: {}",
814                                correlation
815                            )),
816                            failing_input: Some(TestInput {
817                                arrays: vec![data.clone()],
818                                matrices: vec![],
819                                scalars: vec![correlation],
820                                flags: vec![],
821                                strings: vec![],
822                            }),
823                            comparison: Some((correlation, 1.0)),
824                            execution_time_us: start_time.elapsed().as_micros() as u64,
825                        }
826                    }
827                }
828                Err(e) => PropertyTestResult {
829                    property_name: "pearson_self_correlation".to_string(),
830                    test_case_id,
831                    status: TestStatus::Error(format!("Error computing correlation: {}", e)),
832                    failing_input: None,
833                    comparison: None,
834                    execution_time_us: start_time.elapsed().as_micros() as u64,
835                },
836            };
837
838            results.push(result);
839        }
840
841        Ok(results)
842    }
843
844    fn test_spearman_correlation_properties(&mut self) -> StatsResult<Vec<PropertyTestResult>> {
845        crate::property_based_tests_v2_impl::spearman_correlation_properties(
846            self.config.tolerance,
847            &mut self.rng,
848            self.config.mindatasize,
849            self.config.maxdatasize,
850        )
851    }
852
853    fn test_kendall_tau_properties(&mut self) -> StatsResult<Vec<PropertyTestResult>> {
854        crate::property_based_tests_v2_impl::kendall_tau_properties(
855            self.config.tolerance,
856            &mut self.rng,
857            self.config.mindatasize,
858            self.config.maxdatasize,
859        )
860    }
861
862    fn test_correlation_matrix_properties(&mut self) -> StatsResult<Vec<PropertyTestResult>> {
863        crate::property_based_tests_v2_impl::correlation_matrix_properties(
864            self.config.tolerance,
865            &mut self.rng,
866        )
867    }
868
869    fn test_linear_regression_properties(&mut self) -> StatsResult<Vec<PropertyTestResult>> {
870        crate::property_based_tests_v2_impl::linear_regression_properties(
871            self.config.tolerance,
872            &mut self.rng,
873            self.config.mindatasize,
874            self.config.maxdatasize,
875        )
876    }
877
878    fn test_polynomial_regression_properties(&mut self) -> StatsResult<Vec<PropertyTestResult>> {
879        crate::property_based_tests_v2_impl::polynomial_regression_properties(
880            self.config.tolerance,
881            &mut self.rng,
882        )
883    }
884
885    fn test_robust_regression_properties(&mut self) -> StatsResult<Vec<PropertyTestResult>> {
886        crate::property_based_tests_v2_impl::robust_regression_properties(
887            self.config.tolerance,
888            &mut self.rng,
889        )
890    }
891
892    fn test_ttest_properties(&mut self) -> StatsResult<Vec<PropertyTestResult>> {
893        crate::property_based_tests_v2_impl::ttest_properties(
894            &mut self.rng,
895            self.config.mindatasize,
896            self.config.maxdatasize,
897        )
898    }
899
900    fn test_anova_properties(&mut self) -> StatsResult<Vec<PropertyTestResult>> {
901        crate::property_based_tests_v2_impl::anova_properties(
902            &mut self.rng,
903            self.config.mindatasize,
904            self.config.maxdatasize,
905        )
906    }
907
908    fn test_nonparametric_properties(&mut self) -> StatsResult<Vec<PropertyTestResult>> {
909        crate::property_based_tests_v2_impl::nonparametric_properties(
910            &mut self.rng,
911            self.config.mindatasize,
912            self.config.maxdatasize,
913        )
914    }
915
916    fn test_normality_test_properties(&mut self) -> StatsResult<Vec<PropertyTestResult>> {
917        crate::property_based_tests_v2_impl::normality_test_properties(
918            self.config.tolerance,
919            &mut self.rng,
920            self.config.mindatasize,
921            self.config.maxdatasize,
922        )
923    }
924
925    fn test_simd_vs_scalar_mean(&mut self) -> StatsResult<Vec<PropertyTestResult>> {
926        crate::property_based_tests_v2_impl::simd_vs_scalar_mean(
927            self.config.tolerance,
928            &mut self.rng,
929            self.config.mindatasize,
930            self.config.maxdatasize,
931        )
932    }
933
934    fn test_simd_vs_scalar_variance(&mut self) -> StatsResult<Vec<PropertyTestResult>> {
935        crate::property_based_tests_v2_impl::simd_vs_scalar_variance(
936            self.config.tolerance,
937            &mut self.rng,
938            self.config.mindatasize,
939            self.config.maxdatasize,
940        )
941    }
942
943    fn test_simd_vs_scalar_correlation(&mut self) -> StatsResult<Vec<PropertyTestResult>> {
944        crate::property_based_tests_v2_impl::simd_vs_scalar_correlation(
945            &mut self.rng,
946            self.config.mindatasize,
947            self.config.maxdatasize,
948        )
949    }
950
951    fn test_parallel_vs_sequential_mean(&mut self) -> StatsResult<Vec<PropertyTestResult>> {
952        crate::property_based_tests_v2_impl::parallel_vs_sequential_mean(
953            &mut self.rng,
954            self.config.mindatasize,
955            self.config.maxdatasize,
956        )
957    }
958
959    fn test_parallel_vs_sequential_correlation(&mut self) -> StatsResult<Vec<PropertyTestResult>> {
960        crate::property_based_tests_v2_impl::parallel_vs_sequential_correlation(
961            &mut self.rng,
962            self.config.mindatasize,
963            self.config.maxdatasize,
964        )
965    }
966
967    fn test_parallel_vs_sequentialbootstrap(&mut self) -> StatsResult<Vec<PropertyTestResult>> {
968        crate::property_based_tests_v2_impl::parallel_vs_sequential_bootstrap(
969            self.config.tolerance,
970            &mut self.rng,
971            self.config.mindatasize,
972            self.config.maxdatasize,
973        )
974    }
975
976    fn test_extreme_values_stability(&mut self) -> StatsResult<Vec<PropertyTestResult>> {
977        crate::property_based_tests_v2_impl::extreme_values_stability()
978    }
979
980    fn test_near_zero_stability(&mut self) -> StatsResult<Vec<PropertyTestResult>> {
981        crate::property_based_tests_v2_impl::near_zero_stability(self.config.tolerance)
982    }
983
984    fn test_large_values_stability(&mut self) -> StatsResult<Vec<PropertyTestResult>> {
985        crate::property_based_tests_v2_impl::large_values_stability()
986    }
987
988    fn test_ill_conditioned_stability(&mut self) -> StatsResult<Vec<PropertyTestResult>> {
989        crate::property_based_tests_v2_impl::ill_conditioned_stability()
990    }
991
992    // Helper methods
993
994    fn generate_random_array(&mut self) -> StatsResult<Array1<f64>> {
995        let size = self
996            .rng
997            .random_range(self.config.mindatasize..self.config.maxdatasize + 1);
998        let data: Vec<f64> = (0..size)
999            .map(|_| self.rng.random_range(-100.0..100.0))
1000            .collect();
1001        Ok(Array1::from_vec(data))
1002    }
1003
1004    fn compile_test_results(
1005        &self,
1006        results: Vec<PropertyTestResult>,
1007        total_duration: std::time::Duration,
1008    ) -> TestSuiteResult {
1009        let total_tests = results.len();
1010        let passed_tests = results
1011            .iter()
1012            .filter(|r| r.status == TestStatus::Pass)
1013            .count();
1014        let failed_tests = results
1015            .iter()
1016            .filter(|r| matches!(r.status, TestStatus::Fail(_)))
1017            .count();
1018        let timeout_tests = results
1019            .iter()
1020            .filter(|r| r.status == TestStatus::Timeout)
1021            .count();
1022        let error_tests = results
1023            .iter()
1024            .filter(|r| matches!(r.status, TestStatus::Error(_)))
1025            .count();
1026
1027        let success_rate = if total_tests > 0 {
1028            passed_tests as f64 / total_tests as f64
1029        } else {
1030            0.0
1031        };
1032
1033        let execution_times: Vec<u64> = results.iter().map(|r| r.execution_time_us).collect();
1034        let avg_execution_time_us = if !execution_times.is_empty() {
1035            execution_times.iter().sum::<u64>() as f64 / execution_times.len() as f64
1036        } else {
1037            0.0
1038        };
1039        let max_execution_time_us = execution_times.iter().copied().max().unwrap_or(0);
1040        let min_execution_time_us = execution_times.iter().copied().min().unwrap_or(0);
1041
1042        let mut properties_tested = Vec::new();
1043        let mut failure_reasons = HashMap::new();
1044
1045        for result in &results {
1046            if !properties_tested.contains(&result.property_name) {
1047                properties_tested.push(result.property_name.clone());
1048            }
1049
1050            if let TestStatus::Fail(reason) = &result.status {
1051                *failure_reasons.entry(reason.clone()).or_insert(0) += 1;
1052            }
1053        }
1054
1055        let summary = TestSummary {
1056            success_rate,
1057            avg_execution_time_us,
1058            max_execution_time_us,
1059            min_execution_time_us,
1060            properties_tested,
1061            failure_reasons,
1062        };
1063
1064        TestSuiteResult {
1065            total_tests,
1066            passed_tests,
1067            failed_tests,
1068            timeout_tests,
1069            error_tests,
1070            test_results: results,
1071            summary,
1072        }
1073    }
1074}
1075
1076/// Convenience functions for property-based testing
1077#[allow(dead_code)]
1078pub fn test_basic_statistics_properties() -> StatsResult<TestSuiteResult> {
1079    let config = PropertyTestConfig::default();
1080    let mut framework = PropertyBasedTestFramework::<f64>::new(config);
1081    framework.test_descriptive_statistics_invariants()
1082}
1083
1084#[allow(dead_code)]
1085pub fn test_correlation_properties() -> StatsResult<TestSuiteResult> {
1086    let config = PropertyTestConfig::default();
1087    let mut framework = PropertyBasedTestFramework::<f64>::new(config);
1088    framework.test_correlation_invariants()
1089}
1090
1091#[allow(dead_code)]
1092pub fn test_all_mathematical_invariants() -> StatsResult<Vec<TestSuiteResult>> {
1093    let config = PropertyTestConfig::default();
1094    let mut framework = PropertyBasedTestFramework::<f64>::new(config);
1095
1096    let mut all_results = Vec::new();
1097
1098    all_results.push(framework.test_descriptive_statistics_invariants()?);
1099    all_results.push(framework.test_correlation_invariants()?);
1100    all_results.push(framework.test_regression_invariants()?);
1101    all_results.push(framework.test_statistical_test_invariants()?);
1102    all_results.push(framework.test_simd_scalar_consistency()?);
1103    all_results.push(framework.test_parallel_sequential_consistency()?);
1104    all_results.push(framework.test_numerical_stability()?);
1105
1106    Ok(all_results)
1107}
1108
1109#[cfg(test)]
1110mod tests {
1111    use super::*;
1112
1113    #[test]
1114    fn test_property_test_framework() {
1115        let config = PropertyTestConfig {
1116            num_test_cases: 10,
1117            ..Default::default()
1118        };
1119
1120        let mut framework = PropertyBasedTestFramework::<f64>::new(config);
1121        let result = framework.test_descriptive_statistics_invariants();
1122
1123        assert!(result.is_ok());
1124        let suite_result = result.expect("Operation failed");
1125        assert!(suite_result.total_tests > 0);
1126        assert!(
1127            suite_result.summary.success_rate >= 0.0 && suite_result.summary.success_rate <= 1.0
1128        );
1129    }
1130
1131    #[test]
1132    fn test_mean_properties() {
1133        let result = test_basic_statistics_properties();
1134        assert!(result.is_ok());
1135
1136        let suite_result = result.expect("Operation failed");
1137        assert!(suite_result.total_tests > 0);
1138    }
1139
1140    #[test]
1141    fn test_correlation_properties_basic() {
1142        let result = test_correlation_properties();
1143        assert!(result.is_ok());
1144
1145        let suite_result = result.expect("Operation failed");
1146        let _ = suite_result.total_tests; // May be 0 if simplified implementations
1147    }
1148
1149    #[test]
1150    fn test_test_input_creation() {
1151        let input = TestInput {
1152            arrays: vec![Array1::from_vec(vec![1.0, 2.0, 3.0])],
1153            matrices: vec![Array2::eye(3)],
1154            scalars: vec![42.0],
1155            flags: vec![true, false],
1156            strings: vec!["test".to_string()],
1157        };
1158
1159        assert_eq!(input.arrays.len(), 1);
1160        assert_eq!(input.matrices.len(), 1);
1161        assert_eq!(input.scalars.len(), 1);
1162        assert_eq!(input.flags.len(), 2);
1163        assert_eq!(input.strings.len(), 1);
1164    }
1165
1166    #[test]
1167    fn test_config_validation() {
1168        let config = PropertyTestConfig {
1169            num_test_cases: 0,
1170            mindatasize: 10,
1171            maxdatasize: 5, // Invalid: max < min
1172            ..Default::default()
1173        };
1174
1175        // The framework should handle invalid configurations gracefully
1176        let framework = PropertyBasedTestFramework::<f64>::new(config);
1177        assert!(framework.config.num_test_cases == 0);
1178    }
1179}