use std::collections::HashMap;
use crate::error::Result;
use crate::utils::combinations;
use super::OA;
#[derive(Debug, Clone)]
pub struct VerificationResult {
pub is_valid: bool,
pub claimed_strength: u32,
pub actual_strength: u32,
pub issues: Vec<VerificationIssue>,
}
#[derive(Debug, Clone)]
pub enum VerificationIssue {
ValueOutOfRange {
row: usize,
col: usize,
value: u32,
max: u32,
},
ImbalancedSubarray {
columns: Vec<usize>,
expected_count: usize,
tuple_counts: HashMap<Vec<u32>, usize>,
},
}
pub fn verify_strength(oa: &OA, strength: u32) -> Result<VerificationResult> {
let mut issues = Vec::new();
let levels = oa.levels_vec();
let runs = oa.runs();
let factors = oa.factors();
for row in 0..runs {
for col in 0..factors {
let val = oa.get(row, col);
if val >= levels[col] {
issues.push(VerificationIssue::ValueOutOfRange {
row,
col,
value: val,
max: levels[col] - 1,
});
}
}
}
if !issues.is_empty() {
return Ok(VerificationResult {
is_valid: false,
claimed_strength: strength,
actual_strength: 0,
issues,
});
}
let mut verified_strength = 0;
for t in 1..=strength {
if t as usize > factors {
break;
}
let mut balanced_at_t = true;
for col_combo in combinations(factors, t as usize) {
let s_to_t: usize = col_combo.iter().map(|&c| levels[c] as usize).product();
if runs % s_to_t != 0 {
balanced_at_t = false;
issues.push(VerificationIssue::ImbalancedSubarray {
columns: col_combo,
expected_count: 0, tuple_counts: HashMap::new(),
});
continue;
}
let expected_count = runs / s_to_t;
let mut tuple_counts: HashMap<Vec<u32>, usize> = HashMap::new();
for row in 0..runs {
let tuple: Vec<u32> = col_combo.iter().map(|&c| oa.get(row, c)).collect();
*tuple_counts.entry(tuple).or_insert(0) += 1;
}
let num_tuples = tuple_counts.len();
let all_equal = tuple_counts.values().all(|&c| c == expected_count);
if num_tuples != s_to_t || !all_equal {
balanced_at_t = false;
issues.push(VerificationIssue::ImbalancedSubarray {
columns: col_combo,
expected_count,
tuple_counts,
});
}
}
if balanced_at_t {
verified_strength = t;
} else {
break;
}
}
let is_valid = issues.is_empty() && verified_strength >= strength;
Ok(VerificationResult {
is_valid,
claimed_strength: strength,
actual_strength: verified_strength,
issues,
})
}
pub fn compute_strength(oa: &OA, max_check: u32) -> Result<u32> {
let max_t = max_check.min(oa.factors() as u32);
for t in (1..=max_t).rev() {
let result = verify_strength(oa, t)?;
if result.is_valid {
return Ok(t);
}
}
Ok(0)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::oa::OAParams;
use ndarray::Array2;
fn make_l4() -> OA {
let params = OAParams::new(4, 3, 2, 2).unwrap();
let data =
Array2::from_shape_vec((4, 3), vec![0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0]).unwrap();
OA::new(data, params)
}
#[test]
fn test_verify_l4() {
let oa = make_l4();
let result = verify_strength(&oa, 2).unwrap();
assert!(result.is_valid);
assert_eq!(result.actual_strength, 2);
assert!(result.issues.is_empty());
}
#[test]
fn test_verify_invalid_values() {
let params = OAParams::new(4, 3, 2, 2).unwrap();
let data = Array2::from_shape_vec(
(4, 3),
vec![
0, 0, 0, 0, 1, 2, 1, 0, 1, 1, 1, 0,
],
)
.unwrap();
let oa = OA::new(data, params);
let result = verify_strength(&oa, 2).unwrap();
assert!(!result.is_valid);
assert!(!result.issues.is_empty());
}
#[test]
fn test_verify_imbalanced() {
let params = OAParams::new(4, 3, 2, 2).unwrap();
let data = Array2::from_shape_vec(
(4, 3),
vec![
0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0,
],
)
.unwrap();
let oa = OA::new(data, params);
let result = verify_strength(&oa, 2).unwrap();
assert!(!result.is_valid);
assert_eq!(result.actual_strength, 0); }
#[test]
fn test_compute_strength() {
let oa = make_l4();
let strength = compute_strength(&oa, 10).unwrap();
assert_eq!(strength, 2);
}
}