use crate::error::{OptimError, Result};
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::path::PathBuf;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ReproducibilityManager {
pub environments: HashMap<String, EnvironmentSnapshot>,
pub reports: Vec<ReproducibilityReport>,
pub verifications: Vec<VerificationResult>,
pub config: ReproducibilityConfig,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EnvironmentSnapshot {
pub id: String,
pub timestamp: DateTime<Utc>,
pub system_info: SystemInfo,
pub dependencies: Vec<Dependency>,
pub environment_variables: HashMap<String, String>,
pub hardware_config: HardwareConfig,
pub random_seeds: Vec<u64>,
pub data_checksums: HashMap<String, String>,
pub config_hashes: HashMap<String, String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SystemInfo {
pub os: String,
pub os_version: String,
pub kernel_version: Option<String>,
pub architecture: String,
pub hostname: String,
pub timezone: String,
pub locale: HashMap<String, String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Dependency {
pub name: String,
pub version: String,
pub source: String,
pub checksum: Option<String>,
pub install_path: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HardwareConfig {
pub cpu: CpuSpec,
pub memory: MemorySpec,
pub gpu: Option<GpuSpec>,
pub storage: Vec<StorageSpec>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CpuSpec {
pub model: String,
pub cores: usize,
pub threads: usize,
pub base_frequency: u32,
pub max_frequency: u32,
pub cache: HashMap<String, String>,
pub flags: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MemorySpec {
pub total_bytes: u64,
pub available_bytes: u64,
pub memory_type: String,
pub speed_mhz: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GpuSpec {
pub model: String,
pub memory_bytes: u64,
pub driver_version: String,
pub cuda_version: Option<String>,
pub compute_capability: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StorageSpec {
pub device: String,
pub storage_type: String,
pub size_bytes: u64,
pub available_bytes: u64,
pub filesystem: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ReproducibilityReport {
pub id: String,
pub experiment_id: String,
pub environment_id: String,
pub reproducibility_score: f64,
pub checklist: ReproducibilityChecklist,
pub issues: Vec<ReproducibilityIssue>,
pub recommendations: Vec<String>,
pub generated_at: DateTime<Utc>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ReproducibilityChecklist {
pub random_seed_documented: bool,
pub dependencies_pinned: bool,
pub environment_captured: bool,
pub data_versioned: bool,
pub code_versioned: bool,
pub hardware_documented: bool,
pub configuration_hashed: bool,
pub results_verified: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ReproducibilityIssue {
pub issue_type: IssueType,
pub severity: IssueSeverity,
pub description: String,
pub component: String,
pub suggested_fix: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub enum IssueType {
MissingRandomSeed,
UnpinnedDependencies,
MissingEnvironment,
DataNotVersioned,
CodeNotVersioned,
HardwareNotDocumented,
ConfigurationNotHashed,
NonDeterministic,
PlatformSpecific,
ExternalDependencies,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
pub enum IssueSeverity {
Critical,
High,
Medium,
Low,
Info,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VerificationResult {
pub id: String,
pub original_experiment_id: String,
pub reproduction_experiment_id: String,
pub status: VerificationStatus,
pub similarity_metrics: SimilarityMetrics,
pub differences: Vec<Difference>,
pub verified_at: DateTime<Utc>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub enum VerificationStatus {
ExactMatch,
CloseMatch,
PartialMatch,
NoMatch,
VerificationFailed,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SimilarityMetrics {
pub overall_similarity: f64,
pub result_similarity: f64,
pub performance_similarity: f64,
pub configuration_similarity: Option<f64>,
pub environment_similarity: Option<f64>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Difference {
pub category: DifferenceCategory,
pub field: String,
pub original_value: String,
pub reproduction_value: String,
pub magnitude: f64,
pub significant: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub enum DifferenceCategory {
Results,
Performance,
Configuration,
Environment,
Dependencies,
Hardware,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ReproducibilityConfig {
pub numerical_tolerance: f64,
pub performance_tolerance: f64,
pub min_reproducibility_score: f64,
pub auto_capture_environment: bool,
pub auto_verify_results: bool,
pub storage: ReproducibilityStorage,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ReproducibilityStorage {
pub base_directory: PathBuf,
pub compress_snapshots: bool,
pub retention_days: u32,
pub max_storage_bytes: u64,
}
impl ReproducibilityManager {
pub fn new(config: ReproducibilityConfig) -> Self {
Self {
environments: HashMap::new(),
reports: Vec::new(),
verifications: Vec::new(),
config,
}
}
pub fn capture_environment(&mut self, random_seeds: &[u64]) -> Result<String> {
let snapshot_id = uuid::Uuid::new_v4().to_string();
let snapshot = EnvironmentSnapshot {
id: snapshot_id.clone(),
timestamp: Utc::now(),
system_info: self.capture_system_info()?,
dependencies: self.capture_dependencies()?,
environment_variables: self.capture_environment_variables(),
hardware_config: self.capture_hardware_config()?,
random_seeds: random_seeds.to_vec(),
data_checksums: HashMap::new(),
config_hashes: HashMap::new(),
};
self.environments.insert(snapshot_id.clone(), snapshot);
Ok(snapshot_id)
}
pub fn generate_report(&mut self, experiment_id: &str, environment_id: &str) -> Result<String> {
let environment = self.environments.get(environment_id).ok_or_else(|| {
OptimError::InvalidConfig("Environment snapshot not found".to_string())
})?;
let checklist = self.evaluate_checklist(environment, experiment_id);
let (score, issues) = self.calculate_reproducibility_score(&checklist, environment);
let recommendations = self.generate_recommendations(&issues);
let report_id = uuid::Uuid::new_v4().to_string();
let report = ReproducibilityReport {
id: report_id.clone(),
experiment_id: experiment_id.to_string(),
environment_id: environment_id.to_string(),
reproducibility_score: score,
checklist,
issues,
recommendations,
generated_at: Utc::now(),
};
self.reports.push(report);
Ok(report_id)
}
pub fn verify_reproducibility(
&mut self,
original_experiment_id: &str,
reproduction_experiment_id: &str,
original_metrics: &HashMap<String, f64>,
reproduction_metrics: &HashMap<String, f64>,
original_environment_id: Option<&str>,
reproduction_environment_id: Option<&str>,
) -> Result<String> {
const PERFORMANCE_MARKERS: &[&str] = &[
"time",
"memory",
"latency",
"throughput",
"duration",
"speed",
];
let mut keys: Vec<&String> = original_metrics
.keys()
.chain(reproduction_metrics.keys())
.collect();
keys.sort();
keys.dedup();
let mut differences = Vec::new();
let mut result_diffs = Vec::new();
let mut performance_diffs = Vec::new();
for key in keys {
let is_performance = PERFORMANCE_MARKERS
.iter()
.any(|marker| key.to_lowercase().contains(marker));
let category = if is_performance {
DifferenceCategory::Performance
} else {
DifferenceCategory::Results
};
let relative = match (original_metrics.get(key), reproduction_metrics.get(key)) {
(Some(&orig), Some(&repro)) => {
let magnitude = orig
.abs()
.max(repro.abs())
.max(self.config.numerical_tolerance);
let relative = ((orig - repro).abs() / magnitude).min(1.0);
if relative > self.config.numerical_tolerance {
differences.push(Difference {
category,
field: key.clone(),
original_value: orig.to_string(),
reproduction_value: repro.to_string(),
magnitude: relative,
significant: relative > self.config.performance_tolerance,
});
}
relative
}
(orig, repro) => {
differences.push(Difference {
category,
field: key.clone(),
original_value: orig
.map(|v| v.to_string())
.unwrap_or_else(|| "<missing>".to_string()),
reproduction_value: repro
.map(|v| v.to_string())
.unwrap_or_else(|| "<missing>".to_string()),
magnitude: 1.0,
significant: true,
});
1.0
}
};
if is_performance {
performance_diffs.push(relative);
} else {
result_diffs.push(relative);
}
}
let mean_similarity = |diffs: &[f64]| -> f64 {
if diffs.is_empty() {
1.0
} else {
1.0 - (diffs.iter().sum::<f64>() / diffs.len() as f64)
}
};
let result_similarity = mean_similarity(&result_diffs);
let performance_similarity = mean_similarity(&performance_diffs);
let environment_similarity = match (original_environment_id, reproduction_environment_id) {
(Some(orig_id), Some(repro_id)) => match (
self.environments.get(orig_id),
self.environments.get(repro_id),
) {
(Some(orig_env), Some(repro_env)) => {
Some(environment_similarity(orig_env, repro_env))
}
_ => None,
},
_ => None,
};
let overall_similarity = {
let mut parts = vec![result_similarity, performance_similarity];
parts.extend(environment_similarity);
parts.iter().sum::<f64>() / parts.len() as f64
};
let status = if differences.is_empty() {
VerificationStatus::ExactMatch
} else if overall_similarity >= 1.0 - self.config.performance_tolerance {
VerificationStatus::CloseMatch
} else if overall_similarity >= self.config.min_reproducibility_score {
VerificationStatus::PartialMatch
} else {
VerificationStatus::NoMatch
};
let verification_id = uuid::Uuid::new_v4().to_string();
let verification = VerificationResult {
id: verification_id.clone(),
original_experiment_id: original_experiment_id.to_string(),
reproduction_experiment_id: reproduction_experiment_id.to_string(),
status,
similarity_metrics: SimilarityMetrics {
overall_similarity,
result_similarity,
performance_similarity,
configuration_similarity: None,
environment_similarity,
},
differences,
verified_at: Utc::now(),
};
self.verifications.push(verification);
Ok(verification_id)
}
fn capture_system_info(&self) -> Result<SystemInfo> {
Ok(SystemInfo {
os: std::env::consts::OS.to_string(),
os_version: "Unknown".to_string(), kernel_version: None,
architecture: std::env::consts::ARCH.to_string(),
hostname: std::env::var("HOSTNAME").unwrap_or_else(|_| "unknown".to_string()),
timezone: "UTC".to_string(), locale: HashMap::new(),
})
}
fn capture_dependencies(&self) -> Result<Vec<Dependency>> {
let Some(lock_path) = find_cargo_lock() else {
return Ok(Vec::new());
};
let content = std::fs::read_to_string(&lock_path).map_err(|e| {
OptimError::InvalidConfig(format!("failed to read {}: {e}", lock_path.display()))
})?;
Ok(parse_cargo_lock_dependencies(&content))
}
fn capture_environment_variables(&self) -> HashMap<String, String> {
const ALLOWED_EXACT: &[&str] = &[
"LANG",
"LC_ALL",
"LC_CTYPE",
"LC_NUMERIC",
"TZ",
"PATH",
"HOSTNAME",
"USER",
"SHELL",
"PWD",
"OS",
"OSTYPE",
"HOSTTYPE",
"RUSTC_VERSION",
"RUSTFLAGS",
"RUST_BACKTRACE",
"RUST_LOG",
"CARGO_HOME",
"RUSTUP_HOME",
"RUSTUP_TOOLCHAIN",
"OMP_NUM_THREADS",
"RAYON_NUM_THREADS",
"MKL_NUM_THREADS",
"OPENBLAS_NUM_THREADS",
"CUDA_VISIBLE_DEVICES",
"HIP_VISIBLE_DEVICES",
"ROCR_VISIBLE_DEVICES",
];
const ALLOWED_PREFIXES: &[&str] = &["CARGO_", "RUSTC_"];
const SECRET_MARKERS: &[&str] = &[
"KEY",
"TOKEN",
"SECRET",
"PASSWORD",
"PASSWD",
"CREDENTIAL",
"AUTH",
"PRIVATE",
"APIKEY",
"ACCESS",
"COOKIE",
"SESSION",
];
std::env::vars()
.filter(|(name, _)| {
let upper = name.to_uppercase();
ALLOWED_EXACT.contains(&upper.as_str())
|| ALLOWED_PREFIXES.iter().any(|p| upper.starts_with(p))
})
.map(|(name, value)| {
let upper = name.to_uppercase();
if SECRET_MARKERS.iter().any(|marker| upper.contains(marker)) {
(name, "<redacted>".to_string())
} else {
(name, value)
}
})
.collect()
}
fn capture_hardware_config(&self) -> Result<HardwareConfig> {
let cores = std::thread::available_parallelism()
.map(|p| p.get())
.unwrap_or(1);
let (model, base_frequency, max_frequency) = detect_cpu_info();
let (total_bytes, available_bytes) = detect_memory_info();
Ok(HardwareConfig {
cpu: CpuSpec {
model,
cores,
threads: cores,
base_frequency,
max_frequency,
cache: HashMap::new(),
flags: Vec::new(),
},
memory: MemorySpec {
total_bytes,
available_bytes,
memory_type: "Unknown".to_string(),
speed_mhz: 0,
},
gpu: None,
storage: Vec::new(),
})
}
fn evaluate_checklist(
&self,
environment: &EnvironmentSnapshot,
experiment_id: &str,
) -> ReproducibilityChecklist {
ReproducibilityChecklist {
random_seed_documented: !environment.random_seeds.is_empty(),
dependencies_pinned: !environment.dependencies.is_empty(),
environment_captured: true, data_versioned: !environment.data_checksums.is_empty(),
code_versioned: is_code_versioned(),
hardware_documented: environment.hardware_config.cpu.model != "Unknown CPU"
|| environment.hardware_config.memory.total_bytes > 0,
configuration_hashed: !environment.config_hashes.is_empty(),
results_verified: self.verifications.iter().any(|v| {
(v.original_experiment_id == experiment_id
|| v.reproduction_experiment_id == experiment_id)
&& v.status != VerificationStatus::VerificationFailed
}),
}
}
fn calculate_reproducibility_score(
&self,
checklist: &ReproducibilityChecklist,
environment: &EnvironmentSnapshot,
) -> (f64, Vec<ReproducibilityIssue>) {
let mut score = 0.0;
let mut issues = Vec::new();
let total_checks = 8.0;
let contradictions: [(bool, IssueType, &str, &str); 5] = [
(
checklist.random_seed_documented && environment.random_seeds.is_empty(),
IssueType::MissingRandomSeed,
"the checklist claims the random seed is documented, but the environment snapshot \
recorded no seeds",
"record every seed in EnvironmentSnapshot::random_seeds",
),
(
checklist.dependencies_pinned
&& environment
.dependencies
.iter()
.any(|dependency| dependency.version.trim().is_empty()),
IssueType::UnpinnedDependencies,
"the checklist claims dependencies are pinned, but the snapshot contains a \
dependency with no version",
"pin every dependency to an exact version",
),
(
checklist.environment_captured
&& environment.dependencies.is_empty()
&& environment.environment_variables.is_empty(),
IssueType::MissingEnvironment,
"the checklist claims the environment is captured, but the snapshot records \
neither dependencies nor environment variables",
"capture the dependency set and the relevant environment variables",
),
(
checklist.data_versioned && environment.data_checksums.is_empty(),
IssueType::DataNotVersioned,
"the checklist claims the data is versioned, but the snapshot records no data \
checksums",
"record a checksum per dataset in EnvironmentSnapshot::data_checksums",
),
(
checklist.configuration_hashed && environment.config_hashes.is_empty(),
IssueType::ConfigurationNotHashed,
"the checklist claims the configuration is hashed, but the snapshot records no \
configuration hashes",
"record a hash per configuration file in EnvironmentSnapshot::config_hashes",
),
];
let mut unsubstantiated = 0.0_f64;
for (contradicted, issue_type, description, fix) in contradictions {
if contradicted {
unsubstantiated += 1.0;
issues.push(ReproducibilityIssue {
issue_type,
severity: IssueSeverity::High,
description: description.to_string(),
component: format!("environment snapshot {}", environment.id),
suggested_fix: Some(fix.to_string()),
});
}
}
if checklist.random_seed_documented {
score += 1.0;
} else {
issues.push(ReproducibilityIssue {
issue_type: IssueType::MissingRandomSeed,
severity: IssueSeverity::High,
description: "Random seed not documented".to_string(),
component: "Random Number Generation".to_string(),
suggested_fix: Some("Set and document random seeds for all RNGs".to_string()),
});
}
if checklist.dependencies_pinned {
score += 1.0;
} else {
issues.push(ReproducibilityIssue {
issue_type: IssueType::UnpinnedDependencies,
severity: IssueSeverity::Critical,
description: "Dependencies not pinned to specific versions".to_string(),
component: "Dependencies".to_string(),
suggested_fix: Some("Pin all dependencies to exact versions".to_string()),
});
}
if checklist.environment_captured {
score += 1.0;
}
if checklist.data_versioned {
score += 1.0;
} else {
issues.push(ReproducibilityIssue {
issue_type: IssueType::DataNotVersioned,
severity: IssueSeverity::High,
description: "Data not versioned or checksummed".to_string(),
component: "Data Management".to_string(),
suggested_fix: Some("Version control data or provide checksums".to_string()),
});
}
if checklist.code_versioned {
score += 1.0;
} else {
issues.push(ReproducibilityIssue {
issue_type: IssueType::CodeNotVersioned,
severity: IssueSeverity::Critical,
description: "Code not under version control".to_string(),
component: "Source Code".to_string(),
suggested_fix: Some("Use Git or other version control system".to_string()),
});
}
if checklist.hardware_documented {
score += 1.0;
}
if checklist.configuration_hashed {
score += 1.0;
} else {
issues.push(ReproducibilityIssue {
issue_type: IssueType::ConfigurationNotHashed,
severity: IssueSeverity::Medium,
description: "Configuration not hashed for integrity".to_string(),
component: "Configuration".to_string(),
suggested_fix: Some("Generate and store configuration hashes".to_string()),
});
}
if checklist.results_verified {
score += 1.0;
}
((score - unsubstantiated).max(0.0) / total_checks, issues)
}
fn generate_recommendations(&self, issues: &[ReproducibilityIssue]) -> Vec<String> {
let mut recommendations = Vec::new();
for issue in issues {
if let Some(fix) = &issue.suggested_fix {
recommendations.push(format!("{}: {}", issue.component, fix));
}
}
if issues
.iter()
.any(|i| i.issue_type == IssueType::MissingRandomSeed)
{
recommendations.push("Use consistent random seeds across all components".to_string());
}
if issues
.iter()
.any(|i| i.issue_type == IssueType::UnpinnedDependencies)
{
recommendations.push("Create a lockfile with exact dependency versions".to_string());
}
recommendations.push("Document the complete experimental procedure".to_string());
recommendations.push("Provide clear instructions for reproduction".to_string());
recommendations
}
}
fn find_cargo_lock() -> Option<PathBuf> {
let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
let mut dir: &std::path::Path = &manifest_dir;
loop {
let candidate = dir.join("Cargo.lock");
if candidate.is_file() {
return Some(candidate);
}
dir = dir.parent()?;
}
}
fn parse_cargo_lock_dependencies(content: &str) -> Vec<Dependency> {
let mut dependencies = Vec::new();
let mut current: Option<(String, String, Option<String>, Option<String>)> = None;
for raw_line in content.lines() {
let line = raw_line.trim();
if line == "[[package]]" {
if let Some((name, version, source, checksum)) = current.take() {
if !name.is_empty() {
dependencies.push(Dependency {
name,
version,
source: source.unwrap_or_else(|| "local".to_string()),
checksum,
install_path: None,
});
}
}
current = Some((String::new(), String::new(), None, None));
continue;
}
let Some(entry) = current.as_mut() else {
continue;
};
if let Some(value) = parse_toml_string_field(line, "name") {
entry.0 = value;
} else if let Some(value) = parse_toml_string_field(line, "version") {
entry.1 = value;
} else if let Some(value) = parse_toml_string_field(line, "source") {
entry.2 = Some(value);
} else if let Some(value) = parse_toml_string_field(line, "checksum") {
entry.3 = Some(value);
}
}
if let Some((name, version, source, checksum)) = current {
if !name.is_empty() {
dependencies.push(Dependency {
name,
version,
source: source.unwrap_or_else(|| "local".to_string()),
checksum,
install_path: None,
});
}
}
dependencies
}
fn parse_toml_string_field(line: &str, key: &str) -> Option<String> {
let rest = line.strip_prefix(key)?;
let rest = rest.trim_start();
let rest = rest.strip_prefix('=')?;
let rest = rest.trim();
let rest = rest.strip_prefix('"')?;
let value = rest.strip_suffix('"')?;
Some(value.to_string())
}
fn detect_cpu_info() -> (String, u32, u32) {
#[cfg(target_os = "macos")]
{
if let Some(brand) = run_system_command("sysctl", &["-n", "machdep.cpu.brand_string"]) {
let brand = brand.trim();
if !brand.is_empty() {
return (brand.to_string(), 0, 0);
}
}
}
#[cfg(target_os = "linux")]
{
if let Ok(cpuinfo) = std::fs::read_to_string("/proc/cpuinfo") {
let model = cpuinfo
.lines()
.find(|line| line.starts_with("model name"))
.and_then(|line| line.split_once(':'))
.map(|(_, value)| value.trim().to_string());
if let Some(model) = model {
if !model.is_empty() {
return (model, 0, 0);
}
}
}
}
#[cfg(target_os = "windows")]
{
if let Some(output) = run_system_command("wmic", &["cpu", "get", "name"]) {
if let Some(model) = output.lines().nth(1).map(str::trim) {
if !model.is_empty() {
return (model.to_string(), 0, 0);
}
}
}
}
("Unknown CPU".to_string(), 0, 0)
}
fn detect_memory_info() -> (u64, u64) {
#[cfg(target_os = "macos")]
{
if let Some(total) = run_system_command("sysctl", &["-n", "hw.memsize"])
.and_then(|s| s.trim().parse::<u64>().ok())
{
return (total, total);
}
}
#[cfg(target_os = "linux")]
{
if let Ok(meminfo) = std::fs::read_to_string("/proc/meminfo") {
let total = parse_meminfo_kb(&meminfo, "MemTotal:");
let available = parse_meminfo_kb(&meminfo, "MemAvailable:");
if total > 0 {
return (total * 1024, available * 1024);
}
}
}
#[cfg(target_os = "windows")]
{
if let Some(output) = run_system_command(
"wmic",
&[
"OS",
"get",
"TotalVisibleMemorySize,FreePhysicalMemory",
"/value",
],
) {
let total = parse_wmic_kb_field(&output, "TotalVisibleMemorySize");
let available = parse_wmic_kb_field(&output, "FreePhysicalMemory");
if total > 0 {
return (total * 1024, available * 1024);
}
}
}
(0, 0)
}
#[cfg(any(target_os = "macos", target_os = "windows"))]
fn run_system_command(program: &str, args: &[&str]) -> Option<String> {
std::process::Command::new(program)
.args(args)
.output()
.ok()
.filter(|output| output.status.success())
.map(|output| String::from_utf8_lossy(&output.stdout).to_string())
}
#[cfg(target_os = "linux")]
fn parse_meminfo_kb(meminfo: &str, key: &str) -> u64 {
meminfo
.lines()
.find(|line| line.starts_with(key))
.and_then(|line| line.split_whitespace().nth(1))
.and_then(|value| value.parse::<u64>().ok())
.unwrap_or(0)
}
#[cfg(target_os = "windows")]
fn parse_wmic_kb_field(output: &str, key: &str) -> u64 {
output
.lines()
.find_map(|line| line.trim().strip_prefix(&format!("{key}=")))
.and_then(|value| value.trim().parse::<u64>().ok())
.unwrap_or(0)
}
fn environment_similarity(a: &EnvironmentSnapshot, b: &EnvironmentSnapshot) -> f64 {
let os_match = if a.system_info.os == b.system_info.os
&& a.system_info.architecture == b.system_info.architecture
{
1.0
} else {
0.0
};
let deps_a: std::collections::HashSet<String> = a
.dependencies
.iter()
.map(|d| format!("{}@{}", d.name, d.version))
.collect();
let deps_b: std::collections::HashSet<String> = b
.dependencies
.iter()
.map(|d| format!("{}@{}", d.name, d.version))
.collect();
let dependency_similarity = if deps_a.is_empty() && deps_b.is_empty() {
1.0
} else {
let intersection = deps_a.intersection(&deps_b).count() as f64;
let union = deps_a.union(&deps_b).count().max(1) as f64;
intersection / union
};
0.5 * os_match + 0.5 * dependency_similarity
}
fn is_code_versioned() -> bool {
std::process::Command::new("git")
.args(["rev-parse", "--is-inside-work-tree"])
.output()
.map(|output| output.status.success())
.unwrap_or(false)
}
impl Default for ReproducibilityConfig {
fn default() -> Self {
Self {
numerical_tolerance: 1e-6,
performance_tolerance: 0.1, min_reproducibility_score: 0.8, auto_capture_environment: true,
auto_verify_results: false,
storage: ReproducibilityStorage {
base_directory: PathBuf::from("./reproducibility"),
compress_snapshots: true,
retention_days: 365,
max_storage_bytes: 10 * 1024 * 1024 * 1024, },
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_reproducibility_manager_creation() {
let config = ReproducibilityConfig::default();
let manager = ReproducibilityManager::new(config);
assert!(manager.environments.is_empty());
assert!(manager.reports.is_empty());
assert!(manager.verifications.is_empty());
}
#[test]
fn test_environment_capture() {
let config = ReproducibilityConfig::default();
let mut manager = ReproducibilityManager::new(config);
let snapshot_id = manager.capture_environment(&[123]).expect("unwrap failed");
assert!(manager.environments.contains_key(&snapshot_id));
let snapshot = &manager.environments[&snapshot_id];
assert_eq!(snapshot.system_info.os, std::env::consts::OS);
assert_eq!(snapshot.random_seeds, vec![123]);
}
#[test]
fn test_reproducibility_report() {
let config = ReproducibilityConfig::default();
let mut manager = ReproducibilityManager::new(config);
let env_id = manager.capture_environment(&[42]).expect("unwrap failed");
let report_id = manager
.generate_report("test_experiment", &env_id)
.expect("unwrap failed");
assert!(!manager.reports.is_empty());
let report = &manager.reports[0];
assert_eq!(report.id, report_id);
assert_eq!(report.experiment_id, "test_experiment");
}
#[test]
fn test_environment_variables_are_allowlisted_and_redacted() {
unsafe {
std::env::set_var("OPTIRS_TEST_SECRET_API_KEY", "super-secret-value");
std::env::set_var("LANG", "en_US.UTF-8");
}
let config = ReproducibilityConfig::default();
let manager = ReproducibilityManager::new(config);
let captured = manager.capture_environment_variables();
assert!(
!captured.contains_key("OPTIRS_TEST_SECRET_API_KEY"),
"a variable outside the allowlist must not be captured at all"
);
unsafe {
std::env::set_var("CARGO_TEST_SECRET_KEY", "another-secret");
}
let captured = manager.capture_environment_variables();
if let Some(value) = captured.get("CARGO_TEST_SECRET_KEY") {
assert_eq!(
value, "<redacted>",
"an allowlisted-by-prefix variable whose name looks secret-shaped must be redacted"
);
}
if let Some(lang) = captured.get("LANG") {
assert_eq!(
lang, "en_US.UTF-8",
"ordinary allowlisted values pass through"
);
}
unsafe {
std::env::remove_var("OPTIRS_TEST_SECRET_API_KEY");
std::env::remove_var("CARGO_TEST_SECRET_KEY");
}
}
#[test]
fn test_capture_dependencies_reads_real_cargo_lock() {
let config = ReproducibilityConfig::default();
let manager = ReproducibilityManager::new(config);
let dependencies = manager
.capture_dependencies()
.expect("dependency capture should not error");
assert!(
dependencies.len() > 1,
"expected real Cargo.lock contents, got {} entries",
dependencies.len()
);
assert!(
dependencies.iter().any(|d| d.name == "serde"),
"expected to find a real, well-known dependency (serde) in the parsed lockfile"
);
assert!(
!dependencies.iter().any(|d| d.name == "scirs2-optim"),
"must not still contain the old fabricated placeholder entry"
);
}
#[test]
fn test_capture_hardware_config_is_not_the_old_fixed_placeholder() {
let config = ReproducibilityConfig::default();
let manager = ReproducibilityManager::new(config);
let hardware = manager
.capture_hardware_config()
.expect("hardware capture should not error");
assert_ne!(hardware.memory.total_bytes, 8 * 1024 * 1024 * 1024);
assert_ne!(hardware.memory.available_bytes, 6 * 1024 * 1024 * 1024);
#[cfg(any(target_os = "macos", target_os = "linux"))]
{
assert!(hardware.memory.total_bytes > 0);
assert_ne!(hardware.cpu.model, "Unknown CPU");
}
}
#[test]
fn test_reproducibility_score_reflects_real_environment_not_a_fixed_constant() {
let config = ReproducibilityConfig::default();
let mut manager = ReproducibilityManager::new(config);
let sparse_env_id = manager.capture_environment(&[]).expect("capture");
let rich_env_id = manager.capture_environment(&[7]).expect("capture");
if let Some(env) = manager.environments.get_mut(&rich_env_id) {
env.data_checksums
.insert("dataset.csv".to_string(), "deadbeef".to_string());
env.config_hashes
.insert("config.json".to_string(), "cafebabe".to_string());
}
let sparse_report_id = manager
.generate_report("exp_sparse", &sparse_env_id)
.expect("report");
let rich_report_id = manager
.generate_report("exp_rich", &rich_env_id)
.expect("report");
let sparse_score = manager
.reports
.iter()
.find(|r| r.id == sparse_report_id)
.unwrap()
.reproducibility_score;
let rich_score = manager
.reports
.iter()
.find(|r| r.id == rich_report_id)
.unwrap()
.reproducibility_score;
assert!(
rich_score > sparse_score,
"richer environment ({rich_score}) should score higher than sparse ({sparse_score})"
);
assert!(is_code_versioned());
}
#[test]
fn test_verify_reproducibility_reflects_real_metric_differences() {
let config = ReproducibilityConfig::default();
let mut manager = ReproducibilityManager::new(config);
let mut identical_metrics = HashMap::new();
identical_metrics.insert("accuracy".to_string(), 0.95);
identical_metrics.insert("execution_time_seconds".to_string(), 12.0);
let exact_id = manager
.verify_reproducibility(
"orig",
"repro_exact",
&identical_metrics,
&identical_metrics.clone(),
None,
None,
)
.expect("verification should succeed");
let exact = manager
.verifications
.iter()
.find(|v| v.id == exact_id)
.unwrap();
assert_eq!(exact.status, VerificationStatus::ExactMatch);
assert_eq!(exact.similarity_metrics.result_similarity, 1.0);
assert!(exact.differences.is_empty());
let exact_overall_similarity = exact.similarity_metrics.overall_similarity;
let mut divergent_metrics = HashMap::new();
divergent_metrics.insert("accuracy".to_string(), 0.10);
divergent_metrics.insert("execution_time_seconds".to_string(), 999.0);
let divergent_id = manager
.verify_reproducibility(
"orig",
"repro_divergent",
&identical_metrics,
&divergent_metrics,
None,
None,
)
.expect("verification should succeed");
let divergent = manager
.verifications
.iter()
.find(|v| v.id == divergent_id)
.unwrap();
assert_ne!(
divergent.status,
VerificationStatus::ExactMatch,
"a run with wildly different metrics must not be reported as an exact match"
);
assert!(!divergent.differences.is_empty());
assert!(
divergent.similarity_metrics.overall_similarity < exact_overall_similarity,
"the divergent run must score lower than the identical run, not a fixed constant"
);
assert!(divergent
.similarity_metrics
.configuration_similarity
.is_none());
}
#[test]
fn test_verify_reproducibility_computes_real_environment_similarity() {
let config = ReproducibilityConfig::default();
let mut manager = ReproducibilityManager::new(config);
let env_a = manager.capture_environment(&[1]).expect("capture");
let env_b = manager.capture_environment(&[2]).expect("capture");
let metrics = HashMap::new();
let verification_id = manager
.verify_reproducibility(
"orig",
"repro",
&metrics,
&metrics,
Some(env_a.as_str()),
Some(env_b.as_str()),
)
.expect("verification should succeed");
let verification = manager
.verifications
.iter()
.find(|v| v.id == verification_id)
.unwrap();
assert_eq!(
verification.similarity_metrics.environment_similarity,
Some(1.0)
);
}
}