1use 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#[derive(Debug, Clone)]
18pub struct PropertyTestConfig {
19 pub num_test_cases: usize,
21 pub seed: Option<u64>,
23 pub tolerance: f64,
25 pub maxdatasize: usize,
27 pub mindatasize: usize,
29 pub parallel_execution: bool,
31 pub timeout_ms: u64,
33 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#[derive(Debug, Clone, PartialEq)]
54pub enum TestStatus {
55 Pass,
56 Fail(String),
57 Timeout,
58 Error(String),
59}
60
61#[derive(Debug, Clone)]
63pub struct PropertyTestResult {
64 pub property_name: String,
66 pub test_case_id: usize,
68 pub status: TestStatus,
70 pub failing_input: Option<TestInput>,
72 pub comparison: Option<(f64, f64)>,
74 pub execution_time_us: u64,
76}
77
78#[derive(Debug, Clone)]
80pub struct TestInput {
81 pub arrays: Vec<Array1<f64>>,
83 pub matrices: Vec<Array2<f64>>,
85 pub scalars: Vec<f64>,
87 pub flags: Vec<bool>,
89 pub strings: Vec<String>,
91}
92
93#[derive(Debug, Clone)]
95pub struct TestSuiteResult {
96 pub total_tests: usize,
98 pub passed_tests: usize,
100 pub failed_tests: usize,
102 pub timeout_tests: usize,
104 pub error_tests: usize,
106 pub test_results: Vec<PropertyTestResult>,
108 pub summary: TestSummary,
110}
111
112#[derive(Debug, Clone)]
114pub struct TestSummary {
115 pub success_rate: f64,
117 pub avg_execution_time_us: f64,
119 pub max_execution_time_us: u64,
121 pub min_execution_time_us: u64,
123 pub properties_tested: Vec<String>,
125 pub failure_reasons: HashMap<String, usize>,
127}
128
129pub 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 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 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 results.extend(self.test_mean_properties()?);
171
172 results.extend(self.test_variance_properties()?);
174
175 results.extend(self.test_std_properties()?);
177
178 results.extend(self.test_skewness_properties()?);
180
181 results.extend(self.test_kurtosis_properties()?);
183
184 results.extend(self.test_quantile_properties()?);
186
187 Ok(self.compile_test_results(results, start_time.elapsed()))
188 }
189
190 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 results.extend(self.test_pearson_correlation_properties()?);
197
198 results.extend(self.test_spearman_correlation_properties()?);
200
201 results.extend(self.test_kendall_tau_properties()?);
203
204 results.extend(self.test_correlation_matrix_properties()?);
206
207 Ok(self.compile_test_results(results, start_time.elapsed()))
208 }
209
210 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 results.extend(self.test_linear_regression_properties()?);
217
218 results.extend(self.test_polynomial_regression_properties()?);
220
221 results.extend(self.test_robust_regression_properties()?);
223
224 Ok(self.compile_test_results(results, start_time.elapsed()))
225 }
226
227 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 results.extend(self.test_ttest_properties()?);
234
235 results.extend(self.test_anova_properties()?);
237
238 results.extend(self.test_nonparametric_properties()?);
240
241 results.extend(self.test_normality_test_properties()?);
243
244 Ok(self.compile_test_results(results, start_time.elapsed()))
245 }
246
247 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 results.extend(self.test_simd_vs_scalar_mean()?);
254
255 results.extend(self.test_simd_vs_scalar_variance()?);
257
258 results.extend(self.test_simd_vs_scalar_correlation()?);
260
261 Ok(self.compile_test_results(results, start_time.elapsed()))
262 }
263
264 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 results.extend(self.test_parallel_vs_sequential_mean()?);
271
272 results.extend(self.test_parallel_vs_sequential_correlation()?);
274
275 results.extend(self.test_parallel_vs_sequentialbootstrap()?);
277
278 Ok(self.compile_test_results(results, start_time.elapsed()))
279 }
280
281 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 results.extend(self.test_extreme_values_stability()?);
288
289 results.extend(self.test_near_zero_stability()?);
291
292 results.extend(self.test_large_values_stability()?);
294
295 results.extend(self.test_ill_conditioned_stability()?);
297
298 Ok(self.compile_test_results(results, start_time.elapsed()))
299 }
300
301 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 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 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 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 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 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 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 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); }
589
590 if n % 2 == 1 {
591 data.push(0.0); }
593
594 let data_array = Array1::from_vec(data);
595
596 let result = match crate::descriptive::skew(&data_array.view(), false, None) {
598 Ok(skewness) => {
599 if skewness.abs() < self.config.tolerance * 10.0 {
600 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 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 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 let result = match crate::descriptive::kurtosis(&data_array.view(), true, false, None) {
668 Ok(kurtosis_val) => {
669 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 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 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 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 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#[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; }
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, ..Default::default()
1173 };
1174
1175 let framework = PropertyBasedTestFramework::<f64>::new(config);
1177 assert!(framework.config.num_test_cases == 0);
1178 }
1179}