1use crate::error::{StatsError, StatsResult};
15use scirs2_core::ndarray::Array1;
16use serde::{Deserialize, Serialize};
17use std::collections::HashMap;
18use std::fmt::Debug;
19
20#[derive(Debug)]
22pub struct PropertyBasedValidator {
23 config: PropertyTestConfig,
24 test_results: HashMap<String, PropertyTestResult>,
25}
26
27#[derive(Debug, Clone)]
29pub struct PropertyTestConfig {
30 pub test_cases_per_property: usize,
32 pub seed: u64,
34 pub tolerance: f64,
36 pub test_edge_cases: bool,
38 pub test_cross_platform: bool,
40 pub test_numerical_stability: bool,
42}
43
44#[derive(Debug, Clone, Serialize, Deserialize)]
46pub struct PropertyTestResult {
47 pub property_name: String,
49 pub function_name: String,
51 pub test_cases_run: usize,
53 pub test_cases_passed: usize,
55 pub test_cases_failed: usize,
57 pub failures: Vec<PropertyTestFailure>,
59 pub status: PropertyTestStatus,
61 pub statistical_significance: Option<f64>,
63}
64
65#[derive(Debug, Clone, Serialize, Deserialize)]
67pub struct PropertyTestFailure {
68 pub test_case: String,
70 pub expected: String,
72 pub actual: String,
74 pub error_magnitude: f64,
76 pub inputdata: Vec<f64>,
78}
79
80#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
82pub enum PropertyTestStatus {
83 Pass,
85 Warning,
87 Fail,
89 Error,
91}
92
93pub trait MathematicalProperty<T> {
95 fn name(&self) -> &str;
97
98 fn test(&self, input: &T) -> PropertyTestResult;
100
101 fn generate_test_cases(&self, config: &PropertyTestConfig) -> Vec<T>;
103}
104
105impl Default for PropertyTestConfig {
106 fn default() -> Self {
107 Self {
108 test_cases_per_property: 1000,
109 seed: 42,
110 tolerance: 1e-12,
111 test_edge_cases: true,
112 test_cross_platform: true,
113 test_numerical_stability: true,
114 }
115 }
116}
117
118impl PropertyBasedValidator {
119 pub fn new(config: PropertyTestConfig) -> Self {
121 Self {
122 config,
123 test_results: HashMap::new(),
124 }
125 }
126
127 pub fn default() -> Self {
129 Self::new(PropertyTestConfig::default())
130 }
131
132 pub fn test_property<T, P>(&mut self, property: P) -> StatsResult<PropertyTestResult>
134 where
135 P: MathematicalProperty<T>,
136 {
137 let test_cases = property.generate_test_cases(&self.config);
138 let mut passed = 0;
139 let mut failed = 0;
140 let mut failures = Vec::new();
141
142 for (i, test_case) in test_cases.iter().enumerate() {
143 let result = property.test(test_case);
144
145 if result.status == PropertyTestStatus::Pass {
146 passed += 1;
147 } else {
148 failed += 1;
149 failures.push(PropertyTestFailure {
151 test_case: format!("test_case_{}", i),
152 expected: "property_holds".to_string(),
153 actual: "property_violated".to_string(),
154 error_magnitude: 0.0, inputdata: vec![], });
157 }
158 }
159
160 let total_cases = test_cases.len();
161 let status = if failed == 0 {
162 PropertyTestStatus::Pass
163 } else if (failed as f64 / total_cases as f64) < 0.05 {
164 PropertyTestStatus::Warning
165 } else {
166 PropertyTestStatus::Fail
167 };
168
169 let result = PropertyTestResult {
170 property_name: property.name().to_string(),
171 function_name: "unknown".to_string(), test_cases_run: total_cases,
173 test_cases_passed: passed,
174 test_cases_failed: failed,
175 failures,
176 status,
177 statistical_significance: self.calculate_statistical_significance(passed, failed),
178 };
179
180 self.test_results
181 .insert(property.name().to_string(), result.clone());
182 Ok(result)
183 }
184
185 fn calculate_statistical_significance(&self, passed: usize, failed: usize) -> Option<f64> {
187 let total = passed + failed;
188 if total == 0 {
189 return None;
190 }
191
192 let success_rate = passed as f64 / total as f64;
193
194 if success_rate >= 0.99 {
197 Some(0.001) } else if success_rate >= 0.95 {
199 Some(0.05) } else {
201 Some(0.1) }
203 }
204
205 pub fn generate_validation_report(&self) -> ValidationReport {
207 let results: Vec<_> = self.test_results.values().cloned().collect();
208
209 let total_properties = results.len();
210 let passed_properties = results
211 .iter()
212 .filter(|r| r.status == PropertyTestStatus::Pass)
213 .count();
214 let failed_properties = results
215 .iter()
216 .filter(|r| r.status == PropertyTestStatus::Fail)
217 .count();
218 let warning_properties = results
219 .iter()
220 .filter(|r| r.status == PropertyTestStatus::Warning)
221 .count();
222
223 ValidationReport {
224 total_properties,
225 passed_properties,
226 failed_properties,
227 warning_properties,
228 overall_status: if failed_properties == 0 {
229 if warning_properties == 0 {
230 PropertyTestStatus::Pass
231 } else {
232 PropertyTestStatus::Warning
233 }
234 } else {
235 PropertyTestStatus::Fail
236 },
237 property_results: results,
238 generated_at: chrono::Utc::now(),
239 }
240 }
241}
242
243#[derive(Debug, Clone, Serialize, Deserialize)]
245pub struct ValidationReport {
246 pub total_properties: usize,
248 pub passed_properties: usize,
250 pub failed_properties: usize,
252 pub warning_properties: usize,
254 pub overall_status: PropertyTestStatus,
256 pub property_results: Vec<PropertyTestResult>,
258 pub generated_at: chrono::DateTime<chrono::Utc>,
260}
261
262pub struct MeanTranslationInvariance;
266
267impl MathematicalProperty<Array1<f64>> for MeanTranslationInvariance {
268 fn name(&self) -> &str {
269 "mean_translation_invariance"
270 }
271
272 fn test(&self, input: &Array1<f64>) -> PropertyTestResult {
273 use crate::descriptive::mean;
274
275 let original_mean = mean(&input.view());
276 let translation = 100.0;
277 let translated = input.mapv(|x| x + translation);
278 let translated_mean = mean(&translated.view());
279
280 let property_holds = match (original_mean, translated_mean) {
281 (Ok(orig), Ok(trans)) => {
282 let diff = (trans - orig - translation).abs();
283 let scale = 1.0 + orig.abs() + translation.abs();
284 diff < 1e-9 * scale
285 }
286 _ => false,
287 };
288
289 PropertyTestResult {
290 property_name: self.name().to_string(),
291 function_name: "mean".to_string(),
292 test_cases_run: 1,
293 test_cases_passed: if property_holds { 1 } else { 0 },
294 test_cases_failed: if property_holds { 0 } else { 1 },
295 failures: if property_holds {
296 vec![]
297 } else {
298 vec![PropertyTestFailure {
299 test_case: "translation_test".to_string(),
300 expected: "mean(x + c) = mean(x) + c".to_string(),
301 actual: "property_violated".to_string(),
302 error_magnitude: 0.0,
303 inputdata: input.to_vec(),
304 }]
305 },
306 status: if property_holds {
307 PropertyTestStatus::Pass
308 } else {
309 PropertyTestStatus::Fail
310 },
311 statistical_significance: Some(if property_holds { 0.001 } else { 0.1 }),
312 }
313 }
314
315 fn generate_test_cases(&self, config: &PropertyTestConfig) -> Vec<Array1<f64>> {
316 use scirs2_core::random::prelude::*;
317 use scirs2_core::random::{Distribution, Normal};
318
319 let mut rng = StdRng::seed_from_u64(config.seed);
320 let normal = Normal::new(0.0, 1.0).expect("Operation failed");
321 let mut test_cases = Vec::new();
322
323 for _ in 0..config.test_cases_per_property {
324 let size = rng.random_range(10..1000);
325 let mut data = Array1::zeros(size);
326
327 for val in data.iter_mut() {
328 *val = normal.sample(&mut rng);
329 }
330
331 test_cases.push(data);
332 }
333
334 if config.test_edge_cases {
338 test_cases.push(Array1::from_vec(vec![0.0]));
339 test_cases.push(Array1::from_vec(vec![-1e6_f64, 1e6_f64]));
340 test_cases.push(Array1::from_vec(vec![-1.0, 1.0]));
341 }
342
343 test_cases
344 }
345}
346
347pub struct VarianceTranslationInvariance;
349
350impl MathematicalProperty<Array1<f64>> for VarianceTranslationInvariance {
351 fn name(&self) -> &str {
352 "variance_translation_invariance"
353 }
354
355 fn test(&self, input: &Array1<f64>) -> PropertyTestResult {
356 use crate::descriptive::var;
357
358 let original_var = var(&input.view(), 1, None);
359 let translation = 50.0;
360 let translated = input.mapv(|x| x + translation);
361 let translated_var = var(&translated.view(), 1, None);
362
363 let property_holds = match (original_var, translated_var) {
364 (Ok(orig), Ok(trans)) => {
365 let diff = (trans - orig).abs();
366 let scale = 1.0 + orig.abs();
367 diff < 1e-9 * scale
368 }
369 (Err(_), Err(_)) => true,
372 _ => false,
374 };
375
376 PropertyTestResult {
377 property_name: self.name().to_string(),
378 function_name: "variance".to_string(),
379 test_cases_run: 1,
380 test_cases_passed: if property_holds { 1 } else { 0 },
381 test_cases_failed: if property_holds { 0 } else { 1 },
382 failures: if property_holds {
383 vec![]
384 } else {
385 vec![PropertyTestFailure {
386 test_case: "translation_test".to_string(),
387 expected: "var(x + c) = var(x)".to_string(),
388 actual: "property_violated".to_string(),
389 error_magnitude: 0.0,
390 inputdata: input.to_vec(),
391 }]
392 },
393 status: if property_holds {
394 PropertyTestStatus::Pass
395 } else {
396 PropertyTestStatus::Fail
397 },
398 statistical_significance: Some(if property_holds { 0.001 } else { 0.1 }),
399 }
400 }
401
402 fn generate_test_cases(&self, config: &PropertyTestConfig) -> Vec<Array1<f64>> {
403 let mean_prop = MeanTranslationInvariance;
405 mean_prop.generate_test_cases(config)
406 }
407}
408
409pub struct CorrelationBounds;
411
412impl MathematicalProperty<(Array1<f64>, Array1<f64>)> for CorrelationBounds {
413 fn name(&self) -> &str {
414 "correlation_bounds"
415 }
416
417 fn test(&self, input: &(Array1<f64>, Array1<f64>)) -> PropertyTestResult {
418 use crate::correlation::pearson_r;
419
420 let (x, y) = input;
421
422 if x.len() != y.len() {
424 return PropertyTestResult {
425 property_name: self.name().to_string(),
426 function_name: "pearson_r".to_string(),
427 test_cases_run: 1,
428 test_cases_passed: 0,
429 test_cases_failed: 1,
430 failures: vec![],
431 status: PropertyTestStatus::Error,
432 statistical_significance: None,
433 };
434 }
435
436 let correlation = pearson_r(&x.view(), &y.view());
437
438 let property_holds = match correlation {
439 Ok(r) => r >= -1.0 && r <= 1.0 && r.is_finite(),
440 Err(_) => false,
441 };
442
443 PropertyTestResult {
444 property_name: self.name().to_string(),
445 function_name: "pearson_r".to_string(),
446 test_cases_run: 1,
447 test_cases_passed: if property_holds { 1 } else { 0 },
448 test_cases_failed: if property_holds { 0 } else { 1 },
449 failures: if property_holds {
450 vec![]
451 } else {
452 vec![PropertyTestFailure {
453 test_case: "bounds_test".to_string(),
454 expected: "-1 <= correlation <= 1".to_string(),
455 actual: format!("correlation = {:?}", correlation),
456 error_magnitude: 0.0,
457 inputdata: vec![],
458 }]
459 },
460 status: if property_holds {
461 PropertyTestStatus::Pass
462 } else {
463 PropertyTestStatus::Fail
464 },
465 statistical_significance: Some(if property_holds { 0.001 } else { 0.1 }),
466 }
467 }
468
469 fn generate_test_cases(&self, config: &PropertyTestConfig) -> Vec<(Array1<f64>, Array1<f64>)> {
470 use scirs2_core::random::prelude::*;
471 use scirs2_core::random::{Distribution, Normal};
472
473 let mut rng = StdRng::seed_from_u64(config.seed);
474 let normal = Normal::new(0.0, 1.0).expect("Operation failed");
475 let mut test_cases = Vec::new();
476
477 for _ in 0..config.test_cases_per_property {
478 let size = rng.random_range(10..1000);
479 let mut x = Array1::zeros(size);
480 let mut y = Array1::zeros(size);
481
482 for i in 0..size {
483 x[i] = normal.sample(&mut rng);
484 y[i] = normal.sample(&mut rng);
485 }
486
487 test_cases.push((x, y));
488 }
489
490 if config.test_edge_cases {
492 let x1 = Array1::from_vec(vec![1.0, 2.0, 3.0, 4.0, 5.0]);
494 let y1 = Array1::from_vec(vec![1.0, 2.0, 3.0, 4.0, 5.0]);
495 test_cases.push((x1, y1));
496
497 let x2 = Array1::from_vec(vec![1.0, 2.0, 3.0, 4.0, 5.0]);
499 let y2 = Array1::from_vec(vec![5.0, 4.0, 3.0, 2.0, 1.0]);
500 test_cases.push((x2, y2));
501
502 let x3 = Array1::from_vec(vec![1.0, 2.0, 3.0, 4.0, 5.0]);
504 let y3 = Array1::from_vec(vec![2.0, 1.0, 4.0, 3.0, 5.0]);
505 test_cases.push((x3, y3));
506 }
507
508 test_cases
509 }
510}
511
512#[derive(Debug)]
514pub struct ComprehensivePropertyTestSuite {
515 validator: PropertyBasedValidator,
516}
517
518impl ComprehensivePropertyTestSuite {
519 pub fn new(config: PropertyTestConfig) -> Self {
521 Self {
522 validator: PropertyBasedValidator::new(config),
523 }
524 }
525
526 pub fn run_all_tests(&mut self) -> StatsResult<ValidationReport> {
528 self.validator.test_property(MeanTranslationInvariance)?;
530
531 self.validator
533 .test_property(VarianceTranslationInvariance)?;
534
535 self.validator.test_property(CorrelationBounds)?;
537
538 Ok(self.validator.generate_validation_report())
539 }
540
541 pub fn test_function(&mut self, functionname: &str) -> StatsResult<Vec<PropertyTestResult>> {
543 let mut results = Vec::new();
544
545 match functionname {
546 "mean" => {
547 results.push(self.validator.test_property(MeanTranslationInvariance)?);
548 }
549 "variance" => {
550 results.push(
551 self.validator
552 .test_property(VarianceTranslationInvariance)?,
553 );
554 }
555 "correlation" => {
556 results.push(self.validator.test_property(CorrelationBounds)?);
557 }
558 "standard_deviation" => {
559 results.push(self.validator.test_property(StandardDeviationScale)?);
560 results.push(
561 self.validator
562 .test_property(StandardDeviationNonNegativity)?,
563 );
564 }
565 "quantile" => {
566 results.push(self.validator.test_property(QuantileMonotonicity)?);
567 results.push(self.validator.test_property(QuantileBounds)?);
568 }
569 _ => {
570 return Err(StatsError::InvalidInput(format!(
571 "Unknown function: {}",
572 functionname
573 )));
574 }
575 }
576
577 Ok(results)
578 }
579
580 pub fn run_enhanced_tests(&mut self) -> StatsResult<ValidationReport> {
582 self.validator.test_property(MeanTranslationInvariance)?;
584
585 self.validator
587 .test_property(VarianceTranslationInvariance)?;
588
589 self.validator.test_property(CorrelationBounds)?;
591
592 self.validator.test_property(StandardDeviationScale)?;
594 self.validator
595 .test_property(StandardDeviationNonNegativity)?;
596
597 self.validator.test_property(QuantileMonotonicity)?;
599 self.validator.test_property(QuantileBounds)?;
600
601 self.validator.test_property(MeanLinearity)?;
603
604 self.validator.test_property(CorrelationSymmetry)?;
606
607 Ok(self.validator.generate_validation_report())
608 }
609}
610
611pub struct StandardDeviationScale;
613
614impl MathematicalProperty<Array1<f64>> for StandardDeviationScale {
615 fn name(&self) -> &str {
616 "standard_deviation_scale"
617 }
618
619 fn test(&self, input: &Array1<f64>) -> PropertyTestResult {
620 use crate::descriptive::std;
621
622 let original_std = std(&input.view(), 1, None);
623 let scale_factor = 2.0;
624 let scaled = input.mapv(|x| x * scale_factor);
625 let scaled_std = std(&scaled.view(), 1, None);
626
627 let property_holds = match (original_std, scaled_std) {
628 (Ok(orig), Ok(scaled)) => {
629 let expected = orig * scale_factor;
630 (scaled - expected).abs() < 1e-12
631 }
632 _ => false,
633 };
634
635 PropertyTestResult {
636 property_name: self.name().to_string(),
637 function_name: "standard_deviation".to_string(),
638 test_cases_run: 1,
639 test_cases_passed: if property_holds { 1 } else { 0 },
640 test_cases_failed: if property_holds { 0 } else { 1 },
641 failures: if property_holds {
642 vec![]
643 } else {
644 vec![PropertyTestFailure {
645 test_case: "scale_test".to_string(),
646 expected: "std(a*x) = |a| * std(x)".to_string(),
647 actual: "property_violated".to_string(),
648 error_magnitude: 0.0,
649 inputdata: input.to_vec(),
650 }]
651 },
652 status: if property_holds {
653 PropertyTestStatus::Pass
654 } else {
655 PropertyTestStatus::Fail
656 },
657 statistical_significance: Some(if property_holds { 0.001 } else { 0.1 }),
658 }
659 }
660
661 fn generate_test_cases(&self, config: &PropertyTestConfig) -> Vec<Array1<f64>> {
662 let mean_prop = MeanTranslationInvariance;
663 mean_prop.generate_test_cases(config)
664 }
665}
666
667pub struct StandardDeviationNonNegativity;
669
670impl MathematicalProperty<Array1<f64>> for StandardDeviationNonNegativity {
671 fn name(&self) -> &str {
672 "standard_deviation_non_negativity"
673 }
674
675 fn test(&self, input: &Array1<f64>) -> PropertyTestResult {
676 use crate::descriptive::std;
677
678 let result = std(&input.view(), 1, None);
679
680 let property_holds = match result {
681 Ok(std_val) => std_val >= 0.0 && std_val.is_finite(),
682 Err(_) => false,
683 };
684
685 PropertyTestResult {
686 property_name: self.name().to_string(),
687 function_name: "standard_deviation".to_string(),
688 test_cases_run: 1,
689 test_cases_passed: if property_holds { 1 } else { 0 },
690 test_cases_failed: if property_holds { 0 } else { 1 },
691 failures: if property_holds {
692 vec![]
693 } else {
694 vec![PropertyTestFailure {
695 test_case: "non_negativity_test".to_string(),
696 expected: "std(x) >= 0".to_string(),
697 actual: format!("std(x) = {:?}", result),
698 error_magnitude: 0.0,
699 inputdata: input.to_vec(),
700 }]
701 },
702 status: if property_holds {
703 PropertyTestStatus::Pass
704 } else {
705 PropertyTestStatus::Fail
706 },
707 statistical_significance: Some(if property_holds { 0.001 } else { 0.1 }),
708 }
709 }
710
711 fn generate_test_cases(&self, config: &PropertyTestConfig) -> Vec<Array1<f64>> {
712 let mean_prop = MeanTranslationInvariance;
713 mean_prop.generate_test_cases(config)
714 }
715}
716
717pub struct QuantileMonotonicity;
719
720impl MathematicalProperty<Array1<f64>> for QuantileMonotonicity {
721 fn name(&self) -> &str {
722 "quantile_monotonicity"
723 }
724
725 fn test(&self, input: &Array1<f64>) -> PropertyTestResult {
726 use crate::quantile::quantile;
727
728 if input.len() < 2 {
729 return PropertyTestResult {
730 property_name: self.name().to_string(),
731 function_name: "quantile".to_string(),
732 test_cases_run: 1,
733 test_cases_passed: 1,
734 test_cases_failed: 0,
735 failures: vec![],
736 status: PropertyTestStatus::Pass,
737 statistical_significance: Some(0.001),
738 };
739 }
740
741 let q25 = quantile(
742 &input.view(),
743 0.25,
744 crate::quantile::QuantileInterpolation::Linear,
745 );
746 let q50 = quantile(
747 &input.view(),
748 0.50,
749 crate::quantile::QuantileInterpolation::Linear,
750 );
751 let q75 = quantile(
752 &input.view(),
753 0.75,
754 crate::quantile::QuantileInterpolation::Linear,
755 );
756
757 let property_holds = match (q25.clone(), q50.clone(), q75.clone()) {
758 (Ok(q25_val), Ok(q50_val), Ok(q75_val)) => q25_val <= q50_val && q50_val <= q75_val,
759 _ => false,
760 };
761
762 PropertyTestResult {
763 property_name: self.name().to_string(),
764 function_name: "quantile".to_string(),
765 test_cases_run: 1,
766 test_cases_passed: if property_holds { 1 } else { 0 },
767 test_cases_failed: if property_holds { 0 } else { 1 },
768 failures: if property_holds {
769 vec![]
770 } else {
771 vec![PropertyTestFailure {
772 test_case: "monotonicity_test".to_string(),
773 expected: "Q25 <= Q50 <= Q75".to_string(),
774 actual: format!("Q25={:?}, Q50={:?}, Q75={:?}", q25, q50, q75),
775 error_magnitude: 0.0,
776 inputdata: input.to_vec(),
777 }]
778 },
779 status: if property_holds {
780 PropertyTestStatus::Pass
781 } else {
782 PropertyTestStatus::Fail
783 },
784 statistical_significance: Some(if property_holds { 0.001 } else { 0.1 }),
785 }
786 }
787
788 fn generate_test_cases(&self, config: &PropertyTestConfig) -> Vec<Array1<f64>> {
789 let mean_prop = MeanTranslationInvariance;
790 mean_prop.generate_test_cases(config)
791 }
792}
793
794pub struct QuantileBounds;
796
797impl MathematicalProperty<Array1<f64>> for QuantileBounds {
798 fn name(&self) -> &str {
799 "quantile_bounds"
800 }
801
802 fn test(&self, input: &Array1<f64>) -> PropertyTestResult {
803 use crate::quantile::quantile;
804
805 if input.is_empty() {
806 return PropertyTestResult {
807 property_name: self.name().to_string(),
808 function_name: "quantile".to_string(),
809 test_cases_run: 1,
810 test_cases_passed: 0,
811 test_cases_failed: 1,
812 failures: vec![],
813 status: PropertyTestStatus::Error,
814 statistical_significance: None,
815 };
816 }
817
818 let min_val = input.iter().fold(f64::INFINITY, |a, &b| a.min(b));
819 let max_val = input.iter().fold(f64::NEG_INFINITY, |a, &b| a.max(b));
820
821 let q25 = quantile(
822 &input.view(),
823 0.25,
824 crate::quantile::QuantileInterpolation::Linear,
825 );
826 let q75 = quantile(
827 &input.view(),
828 0.75,
829 crate::quantile::QuantileInterpolation::Linear,
830 );
831
832 let property_holds = match (q25.clone(), q75.clone()) {
833 (Ok(q25_val), Ok(q75_val)) => {
834 q25_val >= min_val && q25_val <= max_val && q75_val >= min_val && q75_val <= max_val
835 }
836 _ => false,
837 };
838
839 PropertyTestResult {
840 property_name: self.name().to_string(),
841 function_name: "quantile".to_string(),
842 test_cases_run: 1,
843 test_cases_passed: if property_holds { 1 } else { 0 },
844 test_cases_failed: if property_holds { 0 } else { 1 },
845 failures: if property_holds {
846 vec![]
847 } else {
848 vec![PropertyTestFailure {
849 test_case: "bounds_test".to_string(),
850 expected: "min <= quantile <= max".to_string(),
851 actual: format!(
852 "min={}, max={}, Q25={:?}, Q75={:?}",
853 min_val, max_val, q25, q75
854 ),
855 error_magnitude: 0.0,
856 inputdata: input.to_vec(),
857 }]
858 },
859 status: if property_holds {
860 PropertyTestStatus::Pass
861 } else {
862 PropertyTestStatus::Fail
863 },
864 statistical_significance: Some(if property_holds { 0.001 } else { 0.1 }),
865 }
866 }
867
868 fn generate_test_cases(&self, config: &PropertyTestConfig) -> Vec<Array1<f64>> {
869 let mean_prop = MeanTranslationInvariance;
870 mean_prop.generate_test_cases(config)
871 }
872}
873
874pub struct MeanLinearity;
876
877impl MathematicalProperty<(Array1<f64>, Array1<f64>)> for MeanLinearity {
878 fn name(&self) -> &str {
879 "mean_linearity"
880 }
881
882 fn test(&self, input: &(Array1<f64>, Array1<f64>)) -> PropertyTestResult {
883 use crate::descriptive::mean;
884
885 let (x, y) = input;
886
887 if x.len() != y.len() {
888 return PropertyTestResult {
889 property_name: self.name().to_string(),
890 function_name: "mean".to_string(),
891 test_cases_run: 1,
892 test_cases_passed: 0,
893 test_cases_failed: 1,
894 failures: vec![],
895 status: PropertyTestStatus::Error,
896 statistical_significance: None,
897 };
898 }
899
900 let a = 2.0;
901 let b = 3.0;
902 let combined = x.mapv(|x_val| a * x_val) + &y.mapv(|y_val| b * y_val);
903
904 let mean_combined = mean(&combined.view());
905 let mean_x = mean(&x.view());
906 let mean_y = mean(&y.view());
907
908 let property_holds = match (mean_combined, mean_x, mean_y) {
909 (Ok(combined_val), Ok(x_val), Ok(y_val)) => {
910 let expected = a * x_val + b * y_val;
911 (combined_val - expected).abs() < 1e-12
912 }
913 _ => false,
914 };
915
916 PropertyTestResult {
917 property_name: self.name().to_string(),
918 function_name: "mean".to_string(),
919 test_cases_run: 1,
920 test_cases_passed: if property_holds { 1 } else { 0 },
921 test_cases_failed: if property_holds { 0 } else { 1 },
922 failures: if property_holds {
923 vec![]
924 } else {
925 vec![PropertyTestFailure {
926 test_case: "linearity_test".to_string(),
927 expected: "mean(a*x + b*y) = a*mean(x) + b*mean(y)".to_string(),
928 actual: "linearity_violated".to_string(),
929 error_magnitude: 0.0,
930 inputdata: vec![],
931 }]
932 },
933 status: if property_holds {
934 PropertyTestStatus::Pass
935 } else {
936 PropertyTestStatus::Fail
937 },
938 statistical_significance: Some(if property_holds { 0.001 } else { 0.1 }),
939 }
940 }
941
942 fn generate_test_cases(&self, config: &PropertyTestConfig) -> Vec<(Array1<f64>, Array1<f64>)> {
943 let corr_prop = CorrelationBounds;
944 corr_prop.generate_test_cases(config)
945 }
946}
947
948pub struct CorrelationSymmetry;
950
951impl MathematicalProperty<(Array1<f64>, Array1<f64>)> for CorrelationSymmetry {
952 fn name(&self) -> &str {
953 "correlation_symmetry"
954 }
955
956 fn test(&self, input: &(Array1<f64>, Array1<f64>)) -> PropertyTestResult {
957 use crate::correlation::pearson_r;
958
959 let (x, y) = input;
960
961 if x.len() != y.len() {
962 return PropertyTestResult {
963 property_name: self.name().to_string(),
964 function_name: "correlation".to_string(),
965 test_cases_run: 1,
966 test_cases_passed: 0,
967 test_cases_failed: 1,
968 failures: vec![],
969 status: PropertyTestStatus::Error,
970 statistical_significance: None,
971 };
972 }
973
974 let corr_xy = pearson_r(&x.view(), &y.view());
975 let corr_yx = pearson_r(&y.view(), &x.view());
976
977 let property_holds = match (corr_xy.clone(), corr_yx.clone()) {
978 (Ok(xy), Ok(yx)) => (xy - yx).abs() < 1e-12,
979 _ => false,
980 };
981
982 PropertyTestResult {
983 property_name: self.name().to_string(),
984 function_name: "correlation".to_string(),
985 test_cases_run: 1,
986 test_cases_passed: if property_holds { 1 } else { 0 },
987 test_cases_failed: if property_holds { 0 } else { 1 },
988 failures: if property_holds {
989 vec![]
990 } else {
991 vec![PropertyTestFailure {
992 test_case: "symmetry_test".to_string(),
993 expected: "corr(x, y) = corr(y, x)".to_string(),
994 actual: format!("corr(x,y)={:?}, corr(y,x)={:?}", corr_xy, corr_yx),
995 error_magnitude: 0.0,
996 inputdata: vec![],
997 }]
998 },
999 status: if property_holds {
1000 PropertyTestStatus::Pass
1001 } else {
1002 PropertyTestStatus::Fail
1003 },
1004 statistical_significance: Some(if property_holds { 0.001 } else { 0.1 }),
1005 }
1006 }
1007
1008 fn generate_test_cases(&self, config: &PropertyTestConfig) -> Vec<(Array1<f64>, Array1<f64>)> {
1009 let corr_prop = CorrelationBounds;
1010 corr_prop.generate_test_cases(config)
1011 }
1012}
1013
1014#[allow(dead_code)]
1016pub fn run_comprehensive_property_validation() -> StatsResult<ValidationReport> {
1017 let config = PropertyTestConfig {
1018 test_cases_per_property: 500, seed: 42,
1020 tolerance: 1e-12,
1021 test_edge_cases: true,
1022 test_cross_platform: true,
1023 test_numerical_stability: true,
1024 };
1025
1026 let mut suite = ComprehensivePropertyTestSuite::new(config);
1027 suite.run_enhanced_tests()
1028}
1029
1030#[allow(dead_code)]
1032pub fn run_quick_property_validation() -> StatsResult<ValidationReport> {
1033 let config = PropertyTestConfig {
1034 test_cases_per_property: 100, seed: 42,
1036 tolerance: 1e-10,
1037 test_edge_cases: true,
1038 test_cross_platform: false,
1039 test_numerical_stability: false,
1040 };
1041
1042 let mut suite = ComprehensivePropertyTestSuite::new(config);
1043 suite.run_enhanced_tests()
1044}
1045
1046#[cfg(test)]
1047mod tests {
1048 use super::*;
1049
1050 #[test]
1051 fn test_property_validator_creation() {
1052 let validator = PropertyBasedValidator::default();
1053 assert_eq!(validator.config.test_cases_per_property, 1000);
1054 assert_eq!(validator.config.tolerance, 1e-12);
1055 }
1056
1057 #[test]
1058 fn test_mean_translation_invariance() {
1059 let property = MeanTranslationInvariance;
1060 let testdata = Array1::from_vec(vec![1.0, 2.0, 3.0, 4.0, 5.0]);
1061 let result = property.test(&testdata);
1062
1063 assert_eq!(result.property_name, "mean_translation_invariance");
1064 assert_eq!(result.status, PropertyTestStatus::Pass);
1065 }
1066
1067 #[test]
1068 fn test_variance_translation_invariance() {
1069 let property = VarianceTranslationInvariance;
1070 let testdata = Array1::from_vec(vec![1.0, 2.0, 3.0, 4.0, 5.0]);
1071 let result = property.test(&testdata);
1072
1073 assert_eq!(result.property_name, "variance_translation_invariance");
1074 assert_eq!(result.status, PropertyTestStatus::Pass);
1075 }
1076
1077 #[test]
1078 fn test_correlation_bounds() {
1079 let property = CorrelationBounds;
1080 let x = Array1::from_vec(vec![1.0, 2.0, 3.0, 4.0, 5.0]);
1081 let y = Array1::from_vec(vec![1.0, 2.0, 3.0, 4.0, 5.0]);
1082 let result = property.test(&(x, y));
1083
1084 assert_eq!(result.property_name, "correlation_bounds");
1085 assert_eq!(result.status, PropertyTestStatus::Pass);
1086 }
1087
1088 #[test]
1089 fn test_comprehensive_test_suite() {
1090 let config = PropertyTestConfig {
1091 test_cases_per_property: 10, ..Default::default()
1093 };
1094 let mut suite = ComprehensivePropertyTestSuite::new(config);
1095 let report = suite.run_all_tests().expect("Operation failed");
1096
1097 assert!(report.total_properties > 0);
1098 assert_eq!(report.overall_status, PropertyTestStatus::Pass);
1099 }
1100}