use serde::Deserialize;
use std::fs;
use std::path::Path;
pub const STAT_TOLERANCE: f64 = 0.001;
pub const TIGHT_TOLERANCE: f64 = 1e-9;
pub const HARVEY_COLLIER_TOLERANCE: f64 = 0.1;
pub const DURBIN_WATSON_TOLERANCE: f64 = 0.01;
pub const COOKS_TOLERANCE: f64 = 1e-6;
pub const DFBETAS_TOLERANCE: f64 = 1e-6;
pub const DFFITS_TOLERANCE: f64 = 1e-6;
pub const RESET_TOLERANCE: f64 = 1e-9;
pub const RIDGE_TOLERANCE: f64 = 1e-4;
pub const RIDGE_TOLERANCE_LOOSE: f64 = 1e-3;
pub const LASSO_TOLERANCE: f64 = 1e-2; pub const LASSO_TOLERANCE_SMALL: f64 = 1e-1; pub const LASSO_TOLERANCE_LOOSE: f64 = 2e-2; pub const LASSO_TOLERANCE_SMALL_LOOSE: f64 = 2e-1;
#[derive(Debug, Deserialize)]
pub struct ValidationWrapper {
pub housing_regression: RegressionResult,
}
#[derive(Debug, Deserialize)]
pub struct RegressionResult {
pub coefficients: Vec<f64>,
pub std_errors: Vec<f64>,
pub t_stats: Vec<f64>,
pub p_values: Vec<f64>,
pub r_squared: f64,
pub adj_r_squared: f64,
pub f_statistic: f64,
#[allow(dead_code)]
pub f_p_value: f64,
#[allow(dead_code)]
pub mse: f64,
#[allow(dead_code)]
pub std_error: f64,
pub log_likelihood: f64,
pub aic: f64,
pub bic: f64,
#[allow(dead_code)]
pub conf_int_lower: Vec<f64>,
#[allow(dead_code)]
pub conf_int_upper: Vec<f64>,
#[allow(dead_code)]
pub residuals: Vec<f64>,
#[allow(dead_code)]
pub standardized_residuals: Vec<f64>,
pub vif: Vec<VifEntry>,
pub rainbow: Option<DiagnosticResultJson>,
pub harvey_collier: Option<DiagnosticResultJson>,
pub breusch_pagan: Option<DiagnosticResultJson>,
pub white: Option<DiagnosticResultJson>,
pub jarque_bera: Option<DiagnosticResultJson>,
pub durbin_watson: Option<DiagnosticResultJson>,
pub anderson_darling: Option<DiagnosticResultJson>,
pub shapiro_wilk: Option<DiagnosticResultJson>,
}
#[derive(Debug, Deserialize)]
pub struct VifEntry {
pub variable: String,
pub vif: f64,
#[allow(dead_code)]
pub rsquared: f64,
}
#[derive(Debug, Deserialize)]
pub struct DiagnosticResultJson {
pub statistic: f64,
pub p_value: f64,
#[allow(dead_code)]
pub passed: bool,
}
#[derive(Debug, Deserialize)]
pub struct RDiagnosticResult {
#[allow(dead_code)]
pub test_name: Vec<String>,
#[allow(dead_code)]
pub dataset: Vec<String>,
#[allow(dead_code)]
pub formula: Vec<String>,
pub statistic: Vec<f64>,
pub p_value: Vec<f64>,
#[allow(dead_code)]
pub passed: Vec<bool>,
#[allow(dead_code)]
pub description: Vec<String>,
}
#[derive(Debug, Deserialize)]
pub struct PythonDiagnosticResult {
#[allow(dead_code)]
pub test_name: String,
#[allow(dead_code)]
pub dataset: String,
#[allow(dead_code)]
pub formula: String,
pub statistic: f64,
pub p_value: f64,
#[allow(dead_code)]
pub passed: bool,
#[allow(dead_code)]
pub description: String,
#[serde(default)]
#[allow(dead_code)]
pub f_statistic: Option<f64>,
#[serde(default)]
#[allow(dead_code)]
pub f_p_value: Option<f64>,
#[serde(default)]
#[allow(dead_code)]
pub skipped: bool,
}
#[derive(Debug, Deserialize)]
pub struct RBreuschPaganResult {
#[allow(dead_code)]
pub test_name: Vec<String>,
#[allow(dead_code)]
pub dataset: Vec<String>,
#[allow(dead_code)]
pub formula: Vec<String>,
pub statistic: Vec<f64>,
pub p_value: Vec<f64>,
#[allow(dead_code)]
pub passed: Vec<bool>,
#[allow(dead_code)]
pub description: Vec<String>,
}
#[derive(Debug, Deserialize)]
pub struct PythonBreuschPaganResult {
#[allow(dead_code)]
pub test_name: String,
#[allow(dead_code)]
pub dataset: String,
#[allow(dead_code)]
pub formula: String,
pub statistic: f64,
pub p_value: f64,
#[allow(dead_code)]
pub passed: bool,
#[allow(dead_code)]
pub f_statistic: Option<f64>,
#[allow(dead_code)]
pub f_p_value: Option<f64>,
#[allow(dead_code)]
pub description: String,
}
#[derive(Debug, Deserialize)]
pub struct RShapiroWilkResult {
#[allow(dead_code)]
pub test_name: Vec<String>,
pub statistic: Vec<f64>,
pub p_value: Vec<f64>,
#[allow(dead_code)]
pub passed: Vec<bool>,
#[allow(dead_code)]
pub interpretation: Vec<String>,
#[allow(dead_code)]
pub guidance: Vec<String>,
}
#[derive(Debug, Deserialize)]
pub struct PythonShapiroWilkResult {
#[allow(dead_code)]
pub test_name: String,
pub statistic: f64,
pub p_value: f64,
#[allow(dead_code)]
pub is_passed: bool,
#[allow(dead_code)]
pub interpretation: String,
#[allow(dead_code)]
pub guidance: String,
}
#[derive(Debug, Deserialize)]
pub struct RCooksDistanceResult {
#[allow(dead_code)]
pub test_name: Vec<String>,
#[allow(dead_code)]
pub dataset: Vec<String>,
#[allow(dead_code)]
pub formula: Vec<String>,
pub distances: Vec<f64>,
#[allow(dead_code)]
pub p: Vec<usize>,
#[allow(dead_code)]
pub mse: Vec<f64>,
#[allow(dead_code)]
pub threshold_4_over_n: Vec<f64>,
#[allow(dead_code)]
pub threshold_4_over_df: Vec<f64>,
#[allow(dead_code)]
pub threshold_1: Vec<f64>,
#[allow(dead_code)]
pub influential_4_over_n: Vec<usize>,
#[allow(dead_code)]
pub influential_4_over_df: Vec<usize>,
#[allow(dead_code)]
pub influential_1: Vec<usize>,
pub max_distance: Vec<f64>,
pub max_index: Vec<usize>,
#[allow(dead_code)]
pub description: Vec<String>,
}
#[derive(Debug, Deserialize)]
pub struct PythonCooksDistanceResult {
#[allow(dead_code)]
pub test_name: String,
#[allow(dead_code)]
pub dataset: String,
#[allow(dead_code)]
pub formula: String,
pub distances: Vec<f64>,
#[allow(dead_code)]
pub p: usize,
#[allow(dead_code)]
pub mse: f64,
#[allow(dead_code)]
pub threshold_4_over_n: f64,
#[allow(dead_code)]
pub threshold_4_over_df: f64,
#[allow(dead_code)]
pub threshold_1: f64,
#[allow(dead_code)]
pub influential_4_over_n: Vec<usize>,
#[allow(dead_code)]
pub influential_4_over_df: Vec<usize>,
#[allow(dead_code)]
pub influential_1: Vec<usize>,
pub max_distance: f64,
pub max_index: usize,
#[allow(dead_code)]
pub description: String,
}
#[derive(Debug, Deserialize)]
pub struct RDfbetasResult {
#[allow(dead_code)]
pub test_name: Vec<String>,
#[allow(dead_code)]
pub dataset: Vec<String>,
#[allow(dead_code)]
pub formula: Vec<String>,
pub dfbetas: Vec<Vec<f64>>,
#[allow(dead_code)]
pub n: Vec<usize>,
#[allow(dead_code)]
pub p: Vec<usize>,
#[allow(dead_code)]
pub threshold: Vec<f64>,
pub influential_observations: Vec<usize>,
#[allow(dead_code)]
pub description: Vec<String>,
}
#[derive(Debug, Deserialize)]
pub struct PythonDfbetasResult {
#[allow(dead_code)]
pub test_name: String,
#[allow(dead_code)]
pub dataset: String,
#[allow(dead_code)]
pub formula: String,
pub dfbetas: Vec<Vec<f64>>,
#[allow(dead_code)]
pub n: usize,
#[allow(dead_code)]
pub p: usize,
#[allow(dead_code)]
pub threshold: f64,
pub influential_observations: Vec<usize>,
#[allow(dead_code)]
pub description: String,
}
pub fn load_r_dfbetas_result(json_path: &Path) -> Option<RDfbetasResult> {
if !json_path.exists() {
return None;
}
let content = fs::read_to_string(json_path).ok()?;
serde_json::from_str(&content).ok()
}
pub fn load_python_dfbetas_result(json_path: &Path) -> Option<PythonDfbetasResult> {
if !json_path.exists() {
return None;
}
let content = fs::read_to_string(json_path).ok()?;
serde_json::from_str(&content).ok()
}
#[derive(Debug, Deserialize)]
pub struct RDffitsResult {
#[allow(dead_code)]
pub test_name: Vec<String>,
#[allow(dead_code)]
pub dataset: Vec<String>,
#[allow(dead_code)]
pub formula: Vec<String>,
pub dffits: Vec<f64>,
#[allow(dead_code)]
pub n: Vec<usize>,
#[allow(dead_code)]
pub p: Vec<usize>,
#[allow(dead_code)]
pub threshold: Vec<f64>,
pub influential_observations: Vec<usize>,
#[allow(dead_code)]
pub description: Vec<String>,
}
#[derive(Debug, Deserialize)]
pub struct PythonDffitsResult {
#[allow(dead_code)]
pub test_name: String,
#[allow(dead_code)]
pub dataset: String,
#[allow(dead_code)]
pub formula: String,
pub dffits: Vec<f64>,
#[allow(dead_code)]
pub n: usize,
#[allow(dead_code)]
pub p: usize,
#[allow(dead_code)]
pub threshold: f64,
pub influential_observations: Vec<usize>,
#[allow(dead_code)]
pub description: String,
}
pub fn load_r_dffits_result(json_path: &Path) -> Option<RDffitsResult> {
if !json_path.exists() {
return None;
}
let content = fs::read_to_string(json_path).ok()?;
serde_json::from_str(&content).ok()
}
pub fn load_python_dffits_result(json_path: &Path) -> Option<PythonDffitsResult> {
if !json_path.exists() {
return None;
}
let content = fs::read_to_string(json_path).ok()?;
serde_json::from_str(&content).ok()
}
#[derive(Debug, Deserialize)]
pub struct RBreuschGodfreyResult {
#[allow(dead_code)]
pub test_name: Vec<String>,
#[allow(dead_code)]
pub dataset: Vec<String>,
#[allow(dead_code)]
pub formula: Vec<String>,
pub order: Vec<f64>,
pub statistic: Vec<f64>,
pub p_value: Vec<f64>,
#[allow(dead_code)]
pub test_type: Vec<String>,
#[allow(dead_code)]
pub df: Vec<f64>,
#[serde(default)]
#[allow(dead_code)]
pub f_statistic: Option<Vec<f64>>,
#[serde(default)]
#[allow(dead_code)]
pub f_p_value: Option<Vec<f64>>,
#[serde(default)]
#[allow(dead_code)]
pub f_df: Option<Vec<f64>>,
#[allow(dead_code)]
pub passed: Vec<bool>,
#[allow(dead_code)]
pub interpretation: Vec<String>,
#[allow(dead_code)]
pub description: Vec<String>,
}
#[derive(Debug, Deserialize)]
pub struct PythonBreuschGodfreyResult {
#[allow(dead_code)]
pub test_name: String,
#[allow(dead_code)]
pub dataset: String,
#[allow(dead_code)]
pub formula: String,
pub order: i64,
pub statistic: f64,
pub p_value: f64,
#[allow(dead_code)]
pub test_type: String,
#[allow(dead_code)]
pub df: Vec<f64>,
#[serde(default)]
#[allow(dead_code)]
pub f_statistic: Option<f64>,
#[serde(default)]
#[allow(dead_code)]
pub f_p_value: Option<f64>,
#[serde(default)]
#[allow(dead_code)]
pub f_df: Option<Vec<f64>>,
#[allow(dead_code)]
pub passed: bool,
#[allow(dead_code)]
pub interpretation: String,
#[allow(dead_code)]
pub description: String,
}
#[derive(Debug, Deserialize)]
pub struct RRidgeResult {
#[allow(dead_code)]
pub test: String,
#[allow(dead_code)]
pub method: String,
#[allow(dead_code)]
pub alpha: f64,
pub n: usize,
pub p: usize,
pub lambda_sequence: Vec<f64>,
pub coefficients: Vec<Vec<f64>>,
#[allow(dead_code)]
pub degrees_of_freedom: Vec<f64>,
#[allow(dead_code)]
pub test_lambdas: Vec<f64>,
pub test_predictions: Vec<Vec<f64>>,
#[allow(dead_code)]
pub fitted_values: Vec<f64>,
#[allow(dead_code)]
pub residuals: Vec<f64>,
pub glmnet_version: String,
}
#[derive(Debug, Deserialize)]
pub struct RLassoResult {
#[allow(dead_code)]
pub test: String,
#[allow(dead_code)]
pub method: String,
#[allow(dead_code)]
pub alpha: f64,
pub n: usize,
pub p: usize,
pub lambda_sequence: Vec<f64>,
pub coefficients: Vec<Vec<f64>>,
pub nonzero_counts: Vec<usize>,
#[allow(dead_code)]
pub degrees_of_freedom: Vec<f64>,
#[allow(dead_code)]
pub test_lambdas: Vec<f64>,
pub test_predictions: Vec<Vec<f64>>,
#[allow(dead_code)]
pub fitted_values: Vec<f64>,
#[allow(dead_code)]
pub residuals: Vec<f64>,
pub glmnet_version: String,
}
pub struct Dataset {
#[allow(dead_code)]
pub name: String,
pub y: Vec<f64>,
pub x_vars: Vec<Vec<f64>>,
#[allow(dead_code)]
pub variable_names: Vec<String>,
}
pub fn get_housing_data() -> (Vec<f64>, Vec<Vec<f64>>) {
let y = vec![
245.5, 312.8, 198.4, 425.6, 278.9, 356.2, 189.5, 512.3, 234.7, 298.1, 445.8, 167.9, 367.4,
289.6, 198.2, 478.5, 256.3, 334.7, 178.5, 398.9, 223.4, 312.5, 156.8, 423.7, 267.9,
];
let square_feet = vec![
1200.0, 1800.0, 950.0, 2400.0, 1450.0, 2000.0, 1100.0, 2800.0, 1350.0, 1650.0, 2200.0,
900.0, 1950.0, 1500.0, 1050.0, 2600.0, 1300.0, 1850.0, 1000.0, 2100.0, 1250.0, 1700.0,
850.0, 2350.0, 1400.0,
];
let bedrooms = vec![
3.0, 4.0, 2.0, 4.0, 3.0, 4.0, 2.0, 5.0, 3.0, 3.0, 4.0, 2.0, 4.0, 3.0, 2.0, 5.0, 3.0, 4.0,
2.0, 4.0, 3.0, 3.0, 2.0, 4.0, 3.0,
];
let age = vec![
15.0, 10.0, 25.0, 5.0, 8.0, 12.0, 20.0, 2.0, 18.0, 7.0, 3.0, 30.0, 6.0, 14.0, 22.0, 1.0,
16.0, 9.0, 28.0, 4.0, 19.0, 11.0, 35.0, 3.0, 13.0,
];
(y, vec![square_feet, bedrooms, age])
}
pub fn load_validation_results(json_path: &Path) -> RegressionResult {
if !json_path.exists() {
let script_name = if json_path.to_string_lossy().contains("/r/") {
"verification/scripts/runners/run_all_diagnostics_r.R"
} else {
"verification/scripts/runners/run_all_diagnostics_python.py"
};
panic!(
"Validation file not found: {}\n\
\n\
To generate this file, run:\n\
{}",
json_path.display(),
script_name
);
}
let json_content = fs::read_to_string(json_path).unwrap_or_else(|e| {
panic!(
"Failed to read validation file {:?}: {}\n\
\n\
To generate this file, run the appropriate validation script from verification/scripts/runners/",
json_path, e
)
});
let wrapper: ValidationWrapper = serde_json::from_str(&json_content).unwrap_or_else(|e| {
panic!(
"Failed to parse JSON from {:?}: {}\n\
\n\
The file may be corrupted. Try regenerating it by running the appropriate validation script from verification/scripts/runners/",
json_path, e
)
});
wrapper.housing_regression
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CategoricalEncoding {
ZeroBased,
OneBased,
}
impl CategoricalEncoding {
fn offset(self) -> f64 {
match self {
CategoricalEncoding::ZeroBased => 0.0,
CategoricalEncoding::OneBased => 1.0,
}
}
fn should_sort(self) -> bool {
match self {
CategoricalEncoding::ZeroBased => false, CategoricalEncoding::OneBased => true, }
}
}
pub fn load_dataset(csv_path: &Path) -> Result<Dataset, Box<dyn std::error::Error>> {
load_dataset_with_encoding(csv_path, CategoricalEncoding::ZeroBased)
}
#[derive(Debug)]
pub struct DatasetLoadError {
pub path: String,
pub reason: String,
}
impl std::fmt::Display for DatasetLoadError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"Failed to load dataset from {}: {}\n\
\n\
Dataset files should be in verification/datasets/csv/\n\
\n\
Available datasets: bodyfat, cars_stopping, faithful, iris, lh, longley,\n\
mtcars, prostate, ToothGrowth, synthetic_*.csv",
self.path, self.reason
)
}
}
impl std::error::Error for DatasetLoadError {}
pub fn load_dataset_with_encoding(
csv_path: &Path,
encoding: CategoricalEncoding,
) -> Result<Dataset, Box<dyn std::error::Error>> {
let dataset_name = csv_path
.file_stem()
.unwrap_or_default()
.to_string_lossy()
.to_string();
if !csv_path.exists() {
return Err(Box::new(DatasetLoadError {
path: csv_path.display().to_string(),
reason: "file not found".to_string(),
}));
}
let mut rdr = csv::ReaderBuilder::new()
.has_headers(true)
.from_path(csv_path).map_err(|e| {
Box::new(DatasetLoadError {
path: csv_path.display().to_string(),
reason: format!("IO error: {}", e),
}) as Box<dyn std::error::Error>
})?;
let headers = rdr.headers()?.clone();
let x_names: Vec<String> = headers.iter().skip(1).map(|s| s.to_string()).collect();
let mut raw_y_values: Vec<String> = Vec::new();
let mut raw_x_values: Vec<Vec<String>> = vec![Vec::new(); x_names.len()];
for result in rdr.records() {
let record = result?;
if record.len() < headers.len() {
continue;
}
if let Some(y_str) = record.get(0) {
raw_y_values.push(y_str.to_string());
}
for (i, x_val_str) in record.iter().skip(1).enumerate() {
if i < raw_x_values.len() {
raw_x_values[i].push(x_val_str.to_string());
}
}
}
let mut y_encoding: std::collections::HashMap<String, f64> = std::collections::HashMap::new();
let mut x_encodings: Vec<std::collections::HashMap<String, f64>> =
vec![std::collections::HashMap::new(); x_names.len()];
let offset = encoding.offset();
let start_label = if offset == 0.0 { "0, 1, 2, ..." } else { "1, 2, 3, ..." };
let y_needs_encoding = raw_y_values.iter().any(|v| v.parse::<f64>().is_err());
if y_needs_encoding {
let mut unique_vals: Vec<String> = raw_y_values.iter().map(|s| s.clone()).collect();
unique_vals.dedup();
if encoding.should_sort() {
unique_vals.sort();
}
for (idx, val) in unique_vals.iter().enumerate() {
y_encoding.insert(val.clone(), idx as f64 + offset);
}
eprintln!(
" INFO: y column is categorical, {} categories encoded as {}",
unique_vals.len(),
start_label
);
}
for (col_idx, col_values) in raw_x_values.iter().enumerate() {
let needs_encoding = col_values.iter().any(|v| v.parse::<f64>().is_err());
if needs_encoding {
let mut unique_vals: Vec<String> = col_values.iter().map(|s| s.clone()).collect();
unique_vals.dedup();
if encoding.should_sort() {
unique_vals.sort();
}
for (idx, val) in unique_vals.iter().enumerate() {
x_encodings[col_idx].insert(val.clone(), idx as f64 + offset);
}
eprintln!(
" INFO: {} is categorical, {} categories encoded as {}",
x_names[col_idx],
unique_vals.len(),
start_label
);
}
}
let mut y_data = Vec::new();
let mut x_data: Vec<Vec<f64>> = vec![Vec::new(); x_names.len()];
for (row_idx, y_str) in raw_y_values.iter().enumerate() {
let y_val = if let Some(&encoded) = y_encoding.get(y_str) {
encoded
} else {
y_str.parse::<f64>().unwrap_or(0.0)
};
y_data.push(y_val);
for (col_idx, x_str) in raw_x_values.iter().enumerate() {
if let Some(x_val_str) = x_str.get(row_idx) {
let x_val = if let Some(&encoded) = x_encodings[col_idx].get(x_val_str) {
encoded
} else {
x_val_str.parse::<f64>().unwrap_or(0.0)
};
x_data[col_idx].push(x_val);
}
}
}
let mut variable_names = vec!["Intercept".to_string()];
variable_names.extend(x_names);
Ok(Dataset {
name: dataset_name,
y: y_data,
x_vars: x_data,
variable_names,
})
}
pub fn load_r_diagnostic_result(json_path: &Path) -> Option<RDiagnosticResult> {
if !json_path.exists() {
return None;
}
let content = fs::read_to_string(json_path).ok()?;
serde_json::from_str(&content).ok()
}
pub fn load_python_diagnostic_result(json_path: &Path) -> Option<PythonDiagnosticResult> {
if !json_path.exists() {
return None;
}
let content = fs::read_to_string(json_path).ok()?;
serde_json::from_str(&content).ok()
}
pub fn check_python_result_skipped(json_path: &Path) -> Option<bool> {
if !json_path.exists() {
return None;
}
let content = fs::read_to_string(json_path).ok()?;
#[derive(Deserialize)]
struct SkippedOnly {
#[serde(default)]
skipped: bool,
}
let parsed: Result<SkippedOnly, _> = serde_json::from_str(&content);
match parsed {
Ok(s) => Some(s.skipped),
Err(_) => None, }
}
pub fn load_r_bp_result(json_path: &Path) -> Option<RBreuschPaganResult> {
if !json_path.exists() {
return None;
}
let content = fs::read_to_string(json_path).ok()?;
serde_json::from_str(&content).ok()
}
pub fn load_python_bp_result(json_path: &Path) -> Option<PythonBreuschPaganResult> {
if !json_path.exists() {
return None;
}
let content = fs::read_to_string(json_path).ok()?;
serde_json::from_str(&content).ok()
}
pub fn load_r_shapiro_wilk_result(json_path: &Path) -> Option<RShapiroWilkResult> {
if !json_path.exists() {
return None;
}
let content = fs::read_to_string(json_path).ok()?;
serde_json::from_str(&content).ok()
}
pub fn load_python_shapiro_wilk_result(json_path: &Path) -> Option<PythonShapiroWilkResult> {
if !json_path.exists() {
return None;
}
let content = fs::read_to_string(json_path).ok()?;
serde_json::from_str(&content).ok()
}
pub fn load_r_cooks_result(json_path: &Path) -> Option<RCooksDistanceResult> {
if !json_path.exists() {
return None;
}
let content = fs::read_to_string(json_path).ok()?;
serde_json::from_str(&content).ok()
}
pub fn load_python_cooks_result(json_path: &Path) -> Option<PythonCooksDistanceResult> {
if !json_path.exists() {
return None;
}
let content = fs::read_to_string(json_path).ok()?;
serde_json::from_str(&content).ok()
}
pub fn load_r_bg_result(json_path: &Path) -> Option<RBreuschGodfreyResult> {
if !json_path.exists() {
return None;
}
let content = fs::read_to_string(json_path).ok()?;
serde_json::from_str(&content).ok()
}
pub fn load_python_bg_result(json_path: &Path) -> Option<PythonBreuschGodfreyResult> {
if !json_path.exists() {
return None;
}
let content = fs::read_to_string(json_path).ok()?;
serde_json::from_str(&content).ok()
}
pub fn load_ridge_result(json_path: &Path) -> Option<RRidgeResult> {
if !json_path.exists() {
return None;
}
let content = fs::read_to_string(json_path).ok()?;
serde_json::from_str(&content).ok()
}
pub fn load_lasso_result(json_path: &Path) -> Option<RLassoResult> {
if !json_path.exists() {
return None;
}
let content = fs::read_to_string(json_path).ok()?;
serde_json::from_str(&content).ok()
}
#[derive(Debug, Deserialize)]
pub struct OlsByDatasetResult {
#[allow(dead_code)]
pub test: String,
#[allow(dead_code)]
pub method: String,
#[allow(dead_code)]
pub dataset: String,
#[allow(dead_code)]
pub formula: String,
pub n: usize,
pub k: usize,
pub df_residual: usize,
pub variable_names: Vec<String>,
pub coefficients: Vec<f64>,
pub std_errors: Vec<f64>,
pub t_stats: Vec<f64>,
pub p_values: Vec<f64>,
pub r_squared: f64,
pub adj_r_squared: f64,
pub f_statistic: f64,
pub f_p_value: f64,
pub mse: f64,
pub std_error: f64,
pub log_likelihood: f64,
pub aic: f64,
pub bic: f64,
pub conf_int_lower: Vec<f64>,
pub conf_int_upper: Vec<f64>,
#[allow(dead_code)]
pub residuals: Vec<f64>,
pub vif: Vec<OlsVifEntry>,
}
#[derive(Debug, Deserialize)]
pub struct OlsVifEntry {
pub variable: String,
pub vif: f64,
#[allow(dead_code)]
pub rsquared: f64,
}
pub fn load_ols_by_dataset_result(json_path: &Path) -> Option<OlsByDatasetResult> {
if !json_path.exists() {
return None;
}
let content = fs::read_to_string(json_path).ok()?;
serde_json::from_str(&content).ok()
}
pub fn expect_r_diagnostic_result(json_path: &Path, test_name: &str) -> RDiagnosticResult {
if !json_path.exists() {
panic!(
"R diagnostic result file not found: {}\n\
\n\
Test: {}\n\
\n\
To generate this file, run:\n\
cd verification/scripts/runners && Rscript run_all_diagnostics_r.R\n\
\n\
Or generate individual test results from verification/scripts/r/diagnostics/",
json_path.display(),
test_name
);
}
load_r_diagnostic_result(json_path).unwrap_or_else(|| {
panic!(
"Failed to parse R diagnostic result file: {}\n\
\n\
The file may be corrupted. Try regenerating it by running:\n\
cd verification/scripts/runners && Rscript run_all_diagnostics_r.R",
json_path.display()
)
})
}
pub fn expect_python_diagnostic_result(json_path: &Path, test_name: &str) -> PythonDiagnosticResult {
if !json_path.exists() {
panic!(
"Python diagnostic result file not found: {}\n\
\n\
Test: {}\n\
\n\
To generate this file, run:\n\
cd verification/scripts/runners && python run_all_diagnostics_python.py\n\
\n\
Or generate individual test results from verification/scripts/python/diagnostics/",
json_path.display(),
test_name
);
}
load_python_diagnostic_result(json_path).unwrap_or_else(|| {
panic!(
"Failed to parse Python diagnostic result file: {}\n\
\n\
The file may be corrupted. Try regenerating it by running:\n\
cd verification/scripts/runners && python run_all_diagnostics_python.py",
json_path.display()
)
})
}
pub fn expect_ridge_result(json_path: &Path) -> RRidgeResult {
if !json_path.exists() {
panic!(
"Ridge result file not found: {}\n\
\n\
To generate this file, run:\n\
cd verification/scripts/r/regularized && Rscript test_ridge.R\n\
\n\
Or generate all regularized results:\n\
cd verification/scripts/r/regularized && Rscript generate_all_references.R",
json_path.display()
);
}
load_ridge_result(json_path).unwrap_or_else(|| {
panic!(
"Failed to parse ridge result file: {}\n\
\n\
The file may be corrupted. Try regenerating it.",
json_path.display()
)
})
}
pub fn expect_lasso_result(json_path: &Path) -> RLassoResult {
if !json_path.exists() {
panic!(
"Lasso result file not found: {}\n\
\n\
To generate this file, run:\n\
cd verification/scripts/r/regularized && Rscript test_lasso.R\n\
\n\
Or generate all regularized results:\n\
cd verification/scripts/r/regularized && Rscript generate_all_references.R",
json_path.display()
);
}
load_lasso_result(json_path).unwrap_or_else(|| {
panic!(
"Failed to parse lasso result file: {}\n\
\n\
The file may be corrupted. Try regenerating it.",
json_path.display()
)
})
}
pub fn expect_ols_by_dataset_result(json_path: &Path) -> OlsByDatasetResult {
if !json_path.exists() {
panic!(
"OLS result file not found: {}\n\
\n\
To generate this file, run:\n\
cd verification/scripts/runners && Rscript run_all_diagnostics_r.R",
json_path.display()
);
}
load_ols_by_dataset_result(json_path).unwrap_or_else(|| {
panic!(
"Failed to parse OLS result file: {}\n\
\n\
The file may be corrupted. Try regenerating it.",
json_path.display()
)
})
}
pub fn assert_close_scalar(actual: &f64, expected: &f64, tolerance: f64, context: &str) {
let diff = (actual - expected).abs();
if diff > tolerance {
panic!(
"{} mismatch: actual = {:.10}, expected = {:.10}, diff = {:.2e} (tolerance = {:.2e})",
context, actual, expected, diff, tolerance
);
}
}
pub fn assert_close_array(actual: &[f64], expected: &[f64], tolerance: f64, context: &str) {
assert_eq!(
actual.len(),
expected.len(),
"{} length mismatch: actual = {}, expected = {}",
context,
actual.len(),
expected.len()
);
for (i, (a, e)) in actual.iter().zip(expected.iter()).enumerate() {
let diff = (a - e).abs();
if diff > tolerance {
panic!(
"{}[{}] mismatch: actual = {:.10}, expected = {:.10}, diff = {:.2e} (tolerance = {:.2e})",
context, i, a, e, diff, tolerance
);
}
}
}
pub fn assert_close_to(actual: f64, expected: f64, tolerance: f64, context: &str) {
let diff = (actual - expected).abs();
let (effective_tolerance, use_absolute) = if expected.abs() < 1e-3 {
(1e-4, true) } else if context.contains("lasso") && expected.abs() < 0.5 {
let relaxed_tol = if tolerance == LASSO_TOLERANCE {
LASSO_TOLERANCE_SMALL
} else if tolerance == LASSO_TOLERANCE_LOOSE {
LASSO_TOLERANCE_SMALL_LOOSE
} else {
tolerance
};
(relaxed_tol, false)
} else {
(tolerance, false)
};
let check_value = if use_absolute {
diff
} else {
diff / expected.abs()
};
if check_value > effective_tolerance {
panic!(
"{} mismatch: actual = {:.6}, expected = {:.6}, diff = {:.6} (check = {:.6}, tolerance = {:.6})",
context, actual, expected, diff, check_value, effective_tolerance
);
}
}
pub fn print_comparison_r(label: &str, rust_val: f64, r_val: f64, indent: &str) {
let diff = (rust_val - r_val).abs();
println!("{}{}", indent, label);
println!("{} Rust: {:.15}", indent, rust_val);
println!("{} R: {:.15}", indent, r_val);
println!("{} Diff: {:.2e}", indent, diff);
println!();
}
pub fn print_comparison_python(label: &str, rust_val: f64, py_val: f64, indent: &str) {
let diff = (rust_val - py_val).abs();
println!("{}{}", indent, label);
println!("{} Rust: {:.15}", indent, rust_val);
println!("{} Python: {:.15}", indent, py_val);
println!("{} Diff: {:.2e}", indent, diff);
println!();
}
pub const ALL_DATASETS: &[&str] = &[
"bodyfat",
"cars_stopping",
"faithful",
"iris",
"lh",
"longley",
"mtcars",
"prostate",
"synthetic_simple_linear",
"synthetic_multiple",
"synthetic_collinear",
"synthetic_heteroscedastic",
"synthetic_nonlinear",
"synthetic_nonnormal",
"synthetic_autocorrelated",
"synthetic_high_vif",
"synthetic_outliers",
"synthetic_small",
"synthetic_interaction",
"ToothGrowth",
];
pub const SHAPIRO_WILK_DATASETS: &[&str] = &["bodyfat", "longley", "mtcars", "prostate"];
pub const VIF_TOLERANCE: f64 = 1e-9;
#[derive(Debug, Deserialize)]
pub struct RVifResult {
#[allow(dead_code)]
pub test_name: Vec<String>,
#[allow(dead_code)]
pub dataset: Vec<String>,
#[allow(dead_code)]
pub formula: Vec<String>,
pub vif_results: Vec<VifResultEntry>,
#[allow(dead_code)]
pub vif_numeric: Vec<f64>,
pub max_vif: Vec<f64>,
#[allow(dead_code)]
pub interpretation: Vec<String>,
#[allow(dead_code)]
pub description: Vec<String>,
}
#[derive(Debug, Deserialize)]
pub struct VifResultEntry {
#[allow(dead_code)]
pub vif_result: f64,
pub _row: String,
}
#[derive(Debug, Deserialize)]
pub struct PythonVifResult {
#[allow(dead_code)]
pub test_name: String,
#[allow(dead_code)]
pub dataset: String,
#[allow(dead_code)]
pub formula: String,
pub vif_values: Vec<f64>,
pub variable_names: Vec<String>,
pub max_vif: f64,
pub interpretation: String,
#[allow(dead_code)]
pub description: String,
}
pub fn load_r_vif_result(json_path: &Path) -> Option<RVifResult> {
if !json_path.exists() {
return None;
}
let content = fs::read_to_string(json_path).ok()?;
serde_json::from_str(&content).ok()
}
pub fn load_python_vif_result(json_path: &Path) -> Option<PythonVifResult> {
if !json_path.exists() {
return None;
}
let content = fs::read_to_string(json_path).ok()?;
serde_json::from_str(&content).ok()
}
pub const LOESS_TOLERANCE: f64 = 0.5;
pub const WLS_TOLERANCE: f64 = 1e-8;
#[derive(Debug, Deserialize)]
pub struct RLoessResult {
#[allow(dead_code)]
pub test: String,
#[allow(dead_code)]
pub method: String,
#[allow(dead_code)]
pub dataset: String,
#[allow(dead_code)]
pub formula: String,
pub n: usize,
pub n_predictors: usize,
pub span: f64,
pub degree: usize,
pub surface: String, pub fitted: Vec<f64>,
pub y: Vec<f64>,
pub x: Vec<Vec<f64>>, }
#[derive(Debug, Deserialize)]
pub struct PythonLoessResult {
#[allow(dead_code)]
pub test: String,
#[allow(dead_code)]
pub method: String,
#[allow(dead_code)]
pub dataset: String,
pub n: usize,
pub n_predictors: usize,
pub span: f64,
pub degree: usize,
pub surface: String, pub fitted: Vec<f64>,
pub y: Vec<f64>,
pub x: Vec<f64>,
}
pub fn load_r_loess_result(json_path: &Path) -> Option<RLoessResult> {
if !json_path.exists() {
return None;
}
let content = fs::read_to_string(json_path).ok()?;
serde_json::from_str(&content).ok()
}
pub fn load_python_loess_result(json_path: &Path) -> Option<PythonLoessResult> {
if !json_path.exists() {
return None;
}
let content = fs::read_to_string(json_path).ok()?;
serde_json::from_str(&content).ok()
}
#[derive(Debug, Deserialize)]
pub struct WlsResult {
#[allow(dead_code)]
pub test: String,
#[allow(dead_code)]
pub method: String,
#[allow(dead_code)]
pub dataset: String,
#[allow(dead_code)]
pub formula: String,
pub n: usize,
pub k: usize,
pub df_residual: i64,
pub df_model: i64,
pub variable_names: Vec<String>,
pub coefficients: Vec<f64>,
pub std_errors: Vec<f64>,
pub t_stats: Vec<f64>,
pub p_values: Vec<f64>,
pub r_squared: f64,
pub adj_r_squared: f64,
pub f_statistic: f64,
pub f_p_value: f64,
pub mse: f64,
pub rmse: f64,
pub mae: f64,
pub residual_std_error: f64,
#[allow(dead_code)]
pub log_likelihood: f64,
#[allow(dead_code)]
pub aic: f64,
#[allow(dead_code)]
pub bic: f64,
#[allow(dead_code)]
pub conf_int_lower: Vec<f64>,
#[allow(dead_code)]
pub conf_int_upper: Vec<f64>,
pub fitted_values: Vec<f64>,
pub residuals: Vec<f64>,
#[allow(dead_code)]
pub weights: Vec<f64>,
}
pub fn load_wls_result(json_path: &Path) -> Option<WlsResult> {
if !json_path.exists() {
return None;
}
let content = fs::read_to_string(json_path).ok()?;
serde_json::from_str(&content).ok()
}
pub fn expect_wls_result(json_path: &Path) -> WlsResult {
if !json_path.exists() {
panic!(
"WLS result file not found: {}\n\
\n\
To generate this file, run:\n\
cd verification/scripts/runners && Rscript run_all_diagnostics_r.R\n\
\n\
Or generate individual WLS results:\n\
Rscript verification/scripts/r/core/test_wls.R <csv_path> <output_dir>",
json_path.display()
);
}
load_wls_result(json_path).unwrap_or_else(|| {
panic!(
"Failed to parse WLS result file: {}\n\
\n\
The file may be corrupted. Try regenerating it.",
json_path.display()
)
})
}
pub const CV_TOLERANCE: f64 = 1e-4;
#[derive(Debug, Deserialize)]
pub struct RKfoldCVResult {
#[allow(dead_code)]
pub test: String,
#[allow(dead_code)]
pub method: String,
#[allow(dead_code)]
pub dataset: String,
pub n_folds: usize,
pub n_samples: usize,
pub mean_mse: f64,
pub std_mse: f64,
pub mean_rmse: f64,
pub std_rmse: f64,
pub mean_mae: f64,
pub std_mae: f64,
pub mean_r_squared: f64,
pub std_r_squared: f64,
#[serde(default)]
pub mean_train_r_squared: Option<f64>,
#[allow(dead_code)]
pub fold_results: Vec<CVFoldResult>,
}
#[derive(Debug, Deserialize)]
pub struct CVFoldResult {
pub fold_index: usize,
pub train_size: usize,
pub test_size: usize,
pub mse: f64,
pub rmse: f64,
pub mae: f64,
pub r_squared: f64,
#[serde(default)]
pub train_r_squared: Option<f64>,
}
pub fn load_kfold_cv_result(json_path: &Path) -> Option<RKfoldCVResult> {
if !json_path.exists() {
return None;
}
let content = fs::read_to_string(json_path).ok()?;
serde_json::from_str(&content).ok()
}
pub const POLYNOMIAL_TOLERANCE: f64 = 1e-8;
#[derive(Debug, Deserialize)]
pub struct PolynomialResult {
#[allow(dead_code)]
pub test: String,
#[allow(dead_code)]
pub method: String,
#[allow(dead_code)]
pub dataset: String,
#[allow(dead_code)]
pub formula: String,
pub degree: usize,
pub n: usize,
#[allow(dead_code)]
pub k: usize,
pub df_residual: usize,
#[allow(dead_code)]
pub df_model: usize,
#[allow(dead_code)]
pub variable_names: Vec<String>,
pub coefficients: Vec<f64>,
pub std_errors: Vec<f64>,
pub t_stats: Vec<f64>,
pub p_values: Vec<f64>,
pub r_squared: f64,
pub adj_r_squared: f64,
pub f_statistic: f64,
pub f_p_value: f64,
pub mse: f64,
pub rmse: f64,
pub mae: f64,
#[allow(dead_code)]
pub residual_std_error: f64,
pub log_likelihood: f64,
pub aic: f64,
pub bic: f64,
pub conf_int_lower: Vec<f64>,
pub conf_int_upper: Vec<f64>,
pub fitted_values: Vec<f64>,
pub residuals: Vec<f64>,
}
pub fn load_polynomial_result(json_path: &Path) -> Option<PolynomialResult> {
if !json_path.exists() {
return None;
}
let content = fs::read_to_string(json_path).ok()?;
serde_json::from_str(&content).ok()
}
pub fn expect_polynomial_result(json_path: &Path) -> PolynomialResult {
if !json_path.exists() {
panic!(
"Polynomial result file not found: {}\n\
\n\
To generate this file, run:\n\
cd verification/scripts/runners && Rscript run_all_diagnostics_r.R\n\
\n\
Or generate individual polynomial results:\n\
Rscript verification/scripts/r/core/test_polynomial.R <csv_path> <output_dir> <degree>",
json_path.display()
);
}
load_polynomial_result(json_path).unwrap_or_else(|| {
panic!(
"Failed to parse polynomial result file: {}\n\
\n\
The file may be corrupted. Try regenerating it.",
json_path.display()
)
})
}
pub fn expect_kfold_cv_result(json_path: &Path) -> RKfoldCVResult {
if !json_path.exists() {
panic!(
"K-Fold CV result file not found: {}\n\
\n\
To generate this file, run the R validation script:\n\
Rscript verification/scripts/r/cross_validation/test_kfold_cv.R",
json_path.display()
);
}
load_kfold_cv_result(json_path).unwrap_or_else(|| {
panic!(
"Failed to parse K-Fold CV result file: {}\n\
\n\
The file may be corrupted. Try regenerating it.",
json_path.display()
)
})
}
pub const PI_TOLERANCE: f64 = 1e-4;
#[derive(Debug, Deserialize)]
pub struct PiTrainResult {
pub predicted: Vec<f64>,
pub lower: Vec<f64>,
pub upper: Vec<f64>,
pub se_pred: Vec<f64>,
pub leverage: Vec<f64>,
}
#[derive(Debug, Deserialize)]
pub struct PiExtrapolationResult {
pub new_x: std::collections::HashMap<String, Vec<f64>>,
pub predicted: Vec<f64>,
pub lower: Vec<f64>,
pub upper: Vec<f64>,
pub se_pred: Vec<f64>,
pub leverage: Vec<f64>,
}
#[derive(Debug, Deserialize)]
pub struct PiReferenceResult {
#[allow(dead_code)]
pub dataset: String,
pub alpha: f64,
pub n: usize,
pub p: usize,
pub df_residuals: f64,
pub mse: f64,
pub train: PiTrainResult,
pub extrapolation: PiExtrapolationResult,
}
pub fn load_pi_result(json_path: &Path) -> Option<PiReferenceResult> {
if !json_path.exists() {
return None;
}
let content = fs::read_to_string(json_path)
.unwrap_or_else(|e| panic!("Failed to read PI reference file {:?}: {}", json_path, e));
Some(serde_json::from_str(&content)
.unwrap_or_else(|e| panic!("Failed to parse PI reference JSON {:?}: {}", json_path, e)))
}