use crate::error::{StatsError, StatsResult};
use scirs2_core::ndarray::Array1;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fmt::Debug;
#[derive(Debug)]
pub struct PropertyBasedValidator {
config: PropertyTestConfig,
test_results: HashMap<String, PropertyTestResult>,
}
#[derive(Debug, Clone)]
pub struct PropertyTestConfig {
pub test_cases_per_property: usize,
pub seed: u64,
pub tolerance: f64,
pub test_edge_cases: bool,
pub test_cross_platform: bool,
pub test_numerical_stability: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PropertyTestResult {
pub property_name: String,
pub function_name: String,
pub test_cases_run: usize,
pub test_cases_passed: usize,
pub test_cases_failed: usize,
pub failures: Vec<PropertyTestFailure>,
pub status: PropertyTestStatus,
pub statistical_significance: Option<f64>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PropertyTestFailure {
pub test_case: String,
pub expected: String,
pub actual: String,
pub error_magnitude: f64,
pub inputdata: Vec<f64>,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
pub enum PropertyTestStatus {
Pass,
Warning,
Fail,
Error,
}
pub trait MathematicalProperty<T> {
fn name(&self) -> &str;
fn test(&self, input: &T) -> PropertyTestResult;
fn generate_test_cases(&self, config: &PropertyTestConfig) -> Vec<T>;
}
impl Default for PropertyTestConfig {
fn default() -> Self {
Self {
test_cases_per_property: 1000,
seed: 42,
tolerance: 1e-12,
test_edge_cases: true,
test_cross_platform: true,
test_numerical_stability: true,
}
}
}
impl PropertyBasedValidator {
pub fn new(config: PropertyTestConfig) -> Self {
Self {
config: config,
test_results: HashMap::new(),
}
}
pub fn default() -> Self {
Self::new(PropertyTestConfig::default())
}
pub fn test_property<T, P>(&mut self, property: P) -> StatsResult<PropertyTestResult>
where
P: MathematicalProperty<T>,
{
let test_cases = property.generate_test_cases(&self.config);
let mut passed = 0;
let mut failed = 0;
let mut failures = Vec::new();
for (i, test_case) in test_cases.iter().enumerate() {
let result = property.test(test_case);
if result.status == PropertyTestStatus::Pass {
passed += 1;
} else {
failed += 1;
failures.push(PropertyTestFailure {
test_case: format!("test_case_{}", i),
expected: "property_holds".to_string(),
actual: "property_violated".to_string(),
error_magnitude: 0.0, inputdata: vec![], });
}
}
let total_cases = test_cases.len();
let status = if failed == 0 {
PropertyTestStatus::Pass
} else if (failed as f64 / total_cases as f64) < 0.05 {
PropertyTestStatus::Warning
} else {
PropertyTestStatus::Fail
};
let result = PropertyTestResult {
property_name: property.name().to_string(),
function_name: "unknown".to_string(), test_cases_run: total_cases,
test_cases_passed: passed,
test_cases_failed: failed,
failures,
status,
statistical_significance: self.calculate_statistical_significance(passed, failed),
};
self.test_results
.insert(property.name().to_string(), result.clone());
Ok(result)
}
fn calculate_statistical_significance(&self, passed: usize, failed: usize) -> Option<f64> {
let total = passed + failed;
if total == 0 {
return None;
}
let success_rate = passed as f64 / total as f64;
if success_rate >= 0.99 {
Some(0.001) } else if success_rate >= 0.95 {
Some(0.05) } else {
Some(0.1) }
}
pub fn generate_validation_report(&self) -> ValidationReport {
let results: Vec<_> = self.test_results.values().cloned().collect();
let total_properties = results.len();
let passed_properties = results
.iter()
.filter(|r| r.status == PropertyTestStatus::Pass)
.count();
let failed_properties = results
.iter()
.filter(|r| r.status == PropertyTestStatus::Fail)
.count();
let warning_properties = results
.iter()
.filter(|r| r.status == PropertyTestStatus::Warning)
.count();
ValidationReport {
total_properties,
passed_properties,
failed_properties,
warning_properties,
overall_status: if failed_properties == 0 {
if warning_properties == 0 {
PropertyTestStatus::Pass
} else {
PropertyTestStatus::Warning
}
} else {
PropertyTestStatus::Fail
},
property_results: results,
generated_at: chrono::Utc::now(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ValidationReport {
pub total_properties: usize,
pub passed_properties: usize,
pub failed_properties: usize,
pub warning_properties: usize,
pub overall_status: PropertyTestStatus,
pub property_results: Vec<PropertyTestResult>,
pub generated_at: chrono::DateTime<chrono::Utc>,
}
pub struct MeanTranslationInvariance;
impl MathematicalProperty<Array1<f64>> for MeanTranslationInvariance {
fn name(&self) -> &str {
"mean_translation_invariance"
}
fn test(&self, input: &Array1<f64>) -> PropertyTestResult {
use crate::descriptive::mean;
let original_mean = mean(&input.view());
let translation = 100.0;
let translated = input.mapv(|x| x + translation);
let translated_mean = mean(&translated.view());
let property_holds = match (original_mean, translated_mean) {
(Ok(orig), Ok(trans)) => (trans - orig - translation).abs() < 1e-12,
_ => false,
};
PropertyTestResult {
property_name: self.name().to_string(),
function_name: "mean".to_string(),
test_cases_run: 1,
test_cases_passed: if property_holds { 1 } else { 0 },
test_cases_failed: if property_holds { 0 } else { 1 },
failures: if property_holds {
vec![]
} else {
vec![PropertyTestFailure {
test_case: "translation_test".to_string(),
expected: "mean(x + c) = mean(x) + c".to_string(),
actual: "property_violated".to_string(),
error_magnitude: 0.0,
inputdata: input.to_vec(),
}]
},
status: if property_holds {
PropertyTestStatus::Pass
} else {
PropertyTestStatus::Fail
},
statistical_significance: Some(if property_holds { 0.001 } else { 0.1 }),
}
}
fn generate_test_cases(&self, config: &PropertyTestConfig) -> Vec<Array1<f64>> {
use scirs2_core::random::prelude::*;
use scirs2_core::random::{Distribution, Normal};
let mut rng = StdRng::seed_from_u64(config.seed);
let normal = Normal::new(0.0, 1.0).expect("Operation failed");
let mut test_cases = Vec::new();
for _ in 0..config.test_cases_per_property {
let size = rng.random_range(10..1000);
let mut data = Array1::zeros(size);
for val in data.iter_mut() {
*val = normal.sample(&mut rng);
}
test_cases.push(data);
}
if config.test_edge_cases {
test_cases.push(Array1::from_vec(vec![0.0]));
test_cases.push(Array1::from_vec(vec![f64::MAX, f64::MIN]));
test_cases.push(Array1::from_vec(vec![-1.0, 1.0]));
}
test_cases
}
}
pub struct VarianceTranslationInvariance;
impl MathematicalProperty<Array1<f64>> for VarianceTranslationInvariance {
fn name(&self) -> &str {
"variance_translation_invariance"
}
fn test(&self, input: &Array1<f64>) -> PropertyTestResult {
use crate::descriptive::var;
let original_var = var(&input.view(), 1, None);
let translation = 50.0;
let translated = input.mapv(|x| x + translation);
let translated_var = var(&translated.view(), 1, None);
let property_holds = match (original_var, translated_var) {
(Ok(orig), Ok(trans)) => (trans - orig).abs() < 1e-12,
_ => false,
};
PropertyTestResult {
property_name: self.name().to_string(),
function_name: "variance".to_string(),
test_cases_run: 1,
test_cases_passed: if property_holds { 1 } else { 0 },
test_cases_failed: if property_holds { 0 } else { 1 },
failures: if property_holds {
vec![]
} else {
vec![PropertyTestFailure {
test_case: "translation_test".to_string(),
expected: "var(x + c) = var(x)".to_string(),
actual: "property_violated".to_string(),
error_magnitude: 0.0,
inputdata: input.to_vec(),
}]
},
status: if property_holds {
PropertyTestStatus::Pass
} else {
PropertyTestStatus::Fail
},
statistical_significance: Some(if property_holds { 0.001 } else { 0.1 }),
}
}
fn generate_test_cases(&self, config: &PropertyTestConfig) -> Vec<Array1<f64>> {
let mean_prop = MeanTranslationInvariance;
mean_prop.generate_test_cases(config)
}
}
pub struct CorrelationBounds;
impl MathematicalProperty<(Array1<f64>, Array1<f64>)> for CorrelationBounds {
fn name(&self) -> &str {
"correlation_bounds"
}
fn test(&self, input: &(Array1<f64>, Array1<f64>)) -> PropertyTestResult {
use crate::correlation::pearson_r;
let (x, y) = input;
if x.len() != y.len() {
return PropertyTestResult {
property_name: self.name().to_string(),
function_name: "pearson_r".to_string(),
test_cases_run: 1,
test_cases_passed: 0,
test_cases_failed: 1,
failures: vec![],
status: PropertyTestStatus::Error,
statistical_significance: None,
};
}
let correlation = pearson_r(&x.view(), &y.view());
let property_holds = match correlation {
Ok(r) => r >= -1.0 && r <= 1.0 && r.is_finite(),
Err(_) => false,
};
PropertyTestResult {
property_name: self.name().to_string(),
function_name: "pearson_r".to_string(),
test_cases_run: 1,
test_cases_passed: if property_holds { 1 } else { 0 },
test_cases_failed: if property_holds { 0 } else { 1 },
failures: if property_holds {
vec![]
} else {
vec![PropertyTestFailure {
test_case: "bounds_test".to_string(),
expected: "-1 <= correlation <= 1".to_string(),
actual: format!("correlation = {:?}", correlation),
error_magnitude: 0.0,
inputdata: vec![],
}]
},
status: if property_holds {
PropertyTestStatus::Pass
} else {
PropertyTestStatus::Fail
},
statistical_significance: Some(if property_holds { 0.001 } else { 0.1 }),
}
}
fn generate_test_cases(&self, config: &PropertyTestConfig) -> Vec<(Array1<f64>, Array1<f64>)> {
use scirs2_core::random::prelude::*;
use scirs2_core::random::{Distribution, Normal};
let mut rng = StdRng::seed_from_u64(config.seed);
let normal = Normal::new(0.0, 1.0).expect("Operation failed");
let mut test_cases = Vec::new();
for _ in 0..config.test_cases_per_property {
let size = rng.random_range(10..1000);
let mut x = Array1::zeros(size);
let mut y = Array1::zeros(size);
for i in 0..size {
x[i] = normal.sample(&mut rng);
y[i] = normal.sample(&mut rng);
}
test_cases.push((x, y));
}
if config.test_edge_cases {
let x1 = Array1::from_vec(vec![1.0, 2.0, 3.0, 4.0, 5.0]);
let y1 = Array1::from_vec(vec![1.0, 2.0, 3.0, 4.0, 5.0]);
test_cases.push((x1, y1));
let x2 = Array1::from_vec(vec![1.0, 2.0, 3.0, 4.0, 5.0]);
let y2 = Array1::from_vec(vec![5.0, 4.0, 3.0, 2.0, 1.0]);
test_cases.push((x2, y2));
let x3 = Array1::from_vec(vec![1.0, 2.0, 3.0, 4.0, 5.0]);
let y3 = Array1::from_vec(vec![2.0, 1.0, 4.0, 3.0, 5.0]);
test_cases.push((x3, y3));
}
test_cases
}
}
#[derive(Debug)]
pub struct ComprehensivePropertyTestSuite {
validator: PropertyBasedValidator,
}
impl ComprehensivePropertyTestSuite {
pub fn new(config: PropertyTestConfig) -> Self {
Self {
validator: PropertyBasedValidator::new(config),
}
}
pub fn run_all_tests(&mut self) -> StatsResult<ValidationReport> {
self.validator.test_property(MeanTranslationInvariance)?;
self.validator
.test_property(VarianceTranslationInvariance)?;
self.validator.test_property(CorrelationBounds)?;
Ok(self.validator.generate_validation_report())
}
pub fn test_function(&mut self, functionname: &str) -> StatsResult<Vec<PropertyTestResult>> {
let mut results = Vec::new();
match functionname {
"mean" => {
results.push(self.validator.test_property(MeanTranslationInvariance)?);
}
"variance" => {
results.push(
self.validator
.test_property(VarianceTranslationInvariance)?,
);
}
"correlation" => {
results.push(self.validator.test_property(CorrelationBounds)?);
}
"standard_deviation" => {
results.push(self.validator.test_property(StandardDeviationScale)?);
results.push(
self.validator
.test_property(StandardDeviationNonNegativity)?,
);
}
"quantile" => {
results.push(self.validator.test_property(QuantileMonotonicity)?);
results.push(self.validator.test_property(QuantileBounds)?);
}
_ => {
return Err(StatsError::InvalidInput(format!(
"Unknown function: {}",
functionname
)));
}
}
Ok(results)
}
pub fn run_enhanced_tests(&mut self) -> StatsResult<ValidationReport> {
self.validator.test_property(MeanTranslationInvariance)?;
self.validator
.test_property(VarianceTranslationInvariance)?;
self.validator.test_property(CorrelationBounds)?;
self.validator.test_property(StandardDeviationScale)?;
self.validator
.test_property(StandardDeviationNonNegativity)?;
self.validator.test_property(QuantileMonotonicity)?;
self.validator.test_property(QuantileBounds)?;
self.validator.test_property(MeanLinearity)?;
self.validator.test_property(CorrelationSymmetry)?;
Ok(self.validator.generate_validation_report())
}
}
pub struct StandardDeviationScale;
impl MathematicalProperty<Array1<f64>> for StandardDeviationScale {
fn name(&self) -> &str {
"standard_deviation_scale"
}
fn test(&self, input: &Array1<f64>) -> PropertyTestResult {
use crate::descriptive::std;
let original_std = std(&input.view(), 1, None);
let scale_factor = 2.0;
let scaled = input.mapv(|x| x * scale_factor);
let scaled_std = std(&scaled.view(), 1, None);
let property_holds = match (original_std, scaled_std) {
(Ok(orig), Ok(scaled)) => {
let expected = orig * scale_factor;
(scaled - expected).abs() < 1e-12
}
_ => false,
};
PropertyTestResult {
property_name: self.name().to_string(),
function_name: "standard_deviation".to_string(),
test_cases_run: 1,
test_cases_passed: if property_holds { 1 } else { 0 },
test_cases_failed: if property_holds { 0 } else { 1 },
failures: if property_holds {
vec![]
} else {
vec![PropertyTestFailure {
test_case: "scale_test".to_string(),
expected: "std(a*x) = |a| * std(x)".to_string(),
actual: "property_violated".to_string(),
error_magnitude: 0.0,
inputdata: input.to_vec(),
}]
},
status: if property_holds {
PropertyTestStatus::Pass
} else {
PropertyTestStatus::Fail
},
statistical_significance: Some(if property_holds { 0.001 } else { 0.1 }),
}
}
fn generate_test_cases(&self, config: &PropertyTestConfig) -> Vec<Array1<f64>> {
let mean_prop = MeanTranslationInvariance;
mean_prop.generate_test_cases(config)
}
}
pub struct StandardDeviationNonNegativity;
impl MathematicalProperty<Array1<f64>> for StandardDeviationNonNegativity {
fn name(&self) -> &str {
"standard_deviation_non_negativity"
}
fn test(&self, input: &Array1<f64>) -> PropertyTestResult {
use crate::descriptive::std;
let result = std(&input.view(), 1, None);
let property_holds = match result {
Ok(std_val) => std_val >= 0.0 && std_val.is_finite(),
Err(_) => false,
};
PropertyTestResult {
property_name: self.name().to_string(),
function_name: "standard_deviation".to_string(),
test_cases_run: 1,
test_cases_passed: if property_holds { 1 } else { 0 },
test_cases_failed: if property_holds { 0 } else { 1 },
failures: if property_holds {
vec![]
} else {
vec![PropertyTestFailure {
test_case: "non_negativity_test".to_string(),
expected: "std(x) >= 0".to_string(),
actual: format!("std(x) = {:?}", result),
error_magnitude: 0.0,
inputdata: input.to_vec(),
}]
},
status: if property_holds {
PropertyTestStatus::Pass
} else {
PropertyTestStatus::Fail
},
statistical_significance: Some(if property_holds { 0.001 } else { 0.1 }),
}
}
fn generate_test_cases(&self, config: &PropertyTestConfig) -> Vec<Array1<f64>> {
let mean_prop = MeanTranslationInvariance;
mean_prop.generate_test_cases(config)
}
}
pub struct QuantileMonotonicity;
impl MathematicalProperty<Array1<f64>> for QuantileMonotonicity {
fn name(&self) -> &str {
"quantile_monotonicity"
}
fn test(&self, input: &Array1<f64>) -> PropertyTestResult {
use crate::quantile::quantile;
if input.len() < 2 {
return PropertyTestResult {
property_name: self.name().to_string(),
function_name: "quantile".to_string(),
test_cases_run: 1,
test_cases_passed: 1,
test_cases_failed: 0,
failures: vec![],
status: PropertyTestStatus::Pass,
statistical_significance: Some(0.001),
};
}
let q25 = quantile(
&input.view(),
0.25,
crate::quantile::QuantileInterpolation::Linear,
);
let q50 = quantile(
&input.view(),
0.50,
crate::quantile::QuantileInterpolation::Linear,
);
let q75 = quantile(
&input.view(),
0.75,
crate::quantile::QuantileInterpolation::Linear,
);
let property_holds = match (q25.clone(), q50.clone(), q75.clone()) {
(Ok(q25_val), Ok(q50_val), Ok(q75_val)) => q25_val <= q50_val && q50_val <= q75_val,
_ => false,
};
PropertyTestResult {
property_name: self.name().to_string(),
function_name: "quantile".to_string(),
test_cases_run: 1,
test_cases_passed: if property_holds { 1 } else { 0 },
test_cases_failed: if property_holds { 0 } else { 1 },
failures: if property_holds {
vec![]
} else {
vec![PropertyTestFailure {
test_case: "monotonicity_test".to_string(),
expected: "Q25 <= Q50 <= Q75".to_string(),
actual: format!("Q25={:?}, Q50={:?}, Q75={:?}", q25, q50, q75),
error_magnitude: 0.0,
inputdata: input.to_vec(),
}]
},
status: if property_holds {
PropertyTestStatus::Pass
} else {
PropertyTestStatus::Fail
},
statistical_significance: Some(if property_holds { 0.001 } else { 0.1 }),
}
}
fn generate_test_cases(&self, config: &PropertyTestConfig) -> Vec<Array1<f64>> {
let mean_prop = MeanTranslationInvariance;
mean_prop.generate_test_cases(config)
}
}
pub struct QuantileBounds;
impl MathematicalProperty<Array1<f64>> for QuantileBounds {
fn name(&self) -> &str {
"quantile_bounds"
}
fn test(&self, input: &Array1<f64>) -> PropertyTestResult {
use crate::quantile::quantile;
if input.is_empty() {
return PropertyTestResult {
property_name: self.name().to_string(),
function_name: "quantile".to_string(),
test_cases_run: 1,
test_cases_passed: 0,
test_cases_failed: 1,
failures: vec![],
status: PropertyTestStatus::Error,
statistical_significance: None,
};
}
let min_val = input.iter().fold(f64::INFINITY, |a, &b| a.min(b));
let max_val = input.iter().fold(f64::NEG_INFINITY, |a, &b| a.max(b));
let q25 = quantile(
&input.view(),
0.25,
crate::quantile::QuantileInterpolation::Linear,
);
let q75 = quantile(
&input.view(),
0.75,
crate::quantile::QuantileInterpolation::Linear,
);
let property_holds = match (q25.clone(), q75.clone()) {
(Ok(q25_val), Ok(q75_val)) => {
q25_val >= min_val && q25_val <= max_val && q75_val >= min_val && q75_val <= max_val
}
_ => false,
};
PropertyTestResult {
property_name: self.name().to_string(),
function_name: "quantile".to_string(),
test_cases_run: 1,
test_cases_passed: if property_holds { 1 } else { 0 },
test_cases_failed: if property_holds { 0 } else { 1 },
failures: if property_holds {
vec![]
} else {
vec![PropertyTestFailure {
test_case: "bounds_test".to_string(),
expected: "min <= quantile <= max".to_string(),
actual: format!(
"min={}, max={}, Q25={:?}, Q75={:?}",
min_val, max_val, q25, q75
),
error_magnitude: 0.0,
inputdata: input.to_vec(),
}]
},
status: if property_holds {
PropertyTestStatus::Pass
} else {
PropertyTestStatus::Fail
},
statistical_significance: Some(if property_holds { 0.001 } else { 0.1 }),
}
}
fn generate_test_cases(&self, config: &PropertyTestConfig) -> Vec<Array1<f64>> {
let mean_prop = MeanTranslationInvariance;
mean_prop.generate_test_cases(config)
}
}
pub struct MeanLinearity;
impl MathematicalProperty<(Array1<f64>, Array1<f64>)> for MeanLinearity {
fn name(&self) -> &str {
"mean_linearity"
}
fn test(&self, input: &(Array1<f64>, Array1<f64>)) -> PropertyTestResult {
use crate::descriptive::mean;
let (x, y) = input;
if x.len() != y.len() {
return PropertyTestResult {
property_name: self.name().to_string(),
function_name: "mean".to_string(),
test_cases_run: 1,
test_cases_passed: 0,
test_cases_failed: 1,
failures: vec![],
status: PropertyTestStatus::Error,
statistical_significance: None,
};
}
let a = 2.0;
let b = 3.0;
let combined = x.mapv(|x_val| a * x_val) + &y.mapv(|y_val| b * y_val);
let mean_combined = mean(&combined.view());
let mean_x = mean(&x.view());
let mean_y = mean(&y.view());
let property_holds = match (mean_combined, mean_x, mean_y) {
(Ok(combined_val), Ok(x_val), Ok(y_val)) => {
let expected = a * x_val + b * y_val;
(combined_val - expected).abs() < 1e-12
}
_ => false,
};
PropertyTestResult {
property_name: self.name().to_string(),
function_name: "mean".to_string(),
test_cases_run: 1,
test_cases_passed: if property_holds { 1 } else { 0 },
test_cases_failed: if property_holds { 0 } else { 1 },
failures: if property_holds {
vec![]
} else {
vec![PropertyTestFailure {
test_case: "linearity_test".to_string(),
expected: "mean(a*x + b*y) = a*mean(x) + b*mean(y)".to_string(),
actual: "linearity_violated".to_string(),
error_magnitude: 0.0,
inputdata: vec![],
}]
},
status: if property_holds {
PropertyTestStatus::Pass
} else {
PropertyTestStatus::Fail
},
statistical_significance: Some(if property_holds { 0.001 } else { 0.1 }),
}
}
fn generate_test_cases(&self, config: &PropertyTestConfig) -> Vec<(Array1<f64>, Array1<f64>)> {
let corr_prop = CorrelationBounds;
corr_prop.generate_test_cases(config)
}
}
pub struct CorrelationSymmetry;
impl MathematicalProperty<(Array1<f64>, Array1<f64>)> for CorrelationSymmetry {
fn name(&self) -> &str {
"correlation_symmetry"
}
fn test(&self, input: &(Array1<f64>, Array1<f64>)) -> PropertyTestResult {
use crate::correlation::pearson_r;
let (x, y) = input;
if x.len() != y.len() {
return PropertyTestResult {
property_name: self.name().to_string(),
function_name: "correlation".to_string(),
test_cases_run: 1,
test_cases_passed: 0,
test_cases_failed: 1,
failures: vec![],
status: PropertyTestStatus::Error,
statistical_significance: None,
};
}
let corr_xy = pearson_r(&x.view(), &y.view());
let corr_yx = pearson_r(&y.view(), &x.view());
let property_holds = match (corr_xy.clone(), corr_yx.clone()) {
(Ok(xy), Ok(yx)) => (xy - yx).abs() < 1e-12,
_ => false,
};
PropertyTestResult {
property_name: self.name().to_string(),
function_name: "correlation".to_string(),
test_cases_run: 1,
test_cases_passed: if property_holds { 1 } else { 0 },
test_cases_failed: if property_holds { 0 } else { 1 },
failures: if property_holds {
vec![]
} else {
vec![PropertyTestFailure {
test_case: "symmetry_test".to_string(),
expected: "corr(x, y) = corr(y, x)".to_string(),
actual: format!("corr(x,y)={:?}, corr(y,x)={:?}", corr_xy, corr_yx),
error_magnitude: 0.0,
inputdata: vec![],
}]
},
status: if property_holds {
PropertyTestStatus::Pass
} else {
PropertyTestStatus::Fail
},
statistical_significance: Some(if property_holds { 0.001 } else { 0.1 }),
}
}
fn generate_test_cases(&self, config: &PropertyTestConfig) -> Vec<(Array1<f64>, Array1<f64>)> {
let corr_prop = CorrelationBounds;
corr_prop.generate_test_cases(config)
}
}
#[allow(dead_code)]
pub fn run_comprehensive_property_validation() -> StatsResult<ValidationReport> {
let config = PropertyTestConfig {
test_cases_per_property: 500, seed: 42,
tolerance: 1e-12,
test_edge_cases: true,
test_cross_platform: true,
test_numerical_stability: true,
};
let mut suite = ComprehensivePropertyTestSuite::new(config);
suite.run_enhanced_tests()
}
#[allow(dead_code)]
pub fn run_quick_property_validation() -> StatsResult<ValidationReport> {
let config = PropertyTestConfig {
test_cases_per_property: 100, seed: 42,
tolerance: 1e-10,
test_edge_cases: true,
test_cross_platform: false,
test_numerical_stability: false,
};
let mut suite = ComprehensivePropertyTestSuite::new(config);
suite.run_enhanced_tests()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_property_validator_creation() {
let validator = PropertyBasedValidator::default();
assert_eq!(validator.config.test_cases_per_property, 1000);
assert_eq!(validator.config.tolerance, 1e-12);
}
#[test]
fn test_mean_translation_invariance() {
let property = MeanTranslationInvariance;
let testdata = Array1::from_vec(vec![1.0, 2.0, 3.0, 4.0, 5.0]);
let result = property.test(&testdata);
assert_eq!(result.property_name, "mean_translation_invariance");
assert_eq!(result.status, PropertyTestStatus::Pass);
}
#[test]
fn test_variance_translation_invariance() {
let property = VarianceTranslationInvariance;
let testdata = Array1::from_vec(vec![1.0, 2.0, 3.0, 4.0, 5.0]);
let result = property.test(&testdata);
assert_eq!(result.property_name, "variance_translation_invariance");
assert_eq!(result.status, PropertyTestStatus::Pass);
}
#[test]
fn test_correlation_bounds() {
let property = CorrelationBounds;
let x = Array1::from_vec(vec![1.0, 2.0, 3.0, 4.0, 5.0]);
let y = Array1::from_vec(vec![1.0, 2.0, 3.0, 4.0, 5.0]);
let result = property.test(&(x, y));
assert_eq!(result.property_name, "correlation_bounds");
assert_eq!(result.status, PropertyTestStatus::Pass);
}
#[test]
fn test_comprehensive_test_suite() {
let config = PropertyTestConfig {
test_cases_per_property: 10, ..Default::default()
};
let mut suite = ComprehensivePropertyTestSuite::new(config);
let report = suite.run_all_tests().expect("Operation failed");
assert!(report.total_properties > 0);
assert_eq!(report.overall_status, PropertyTestStatus::Pass);
}
}