use super::{HostData, ValidationResult};
const REPORTED_MISMATCHES: usize = 8;
pub fn assert_equals_approx(
actual: &HostData,
expected: &HostData,
epsilon: f32,
) -> ValidationResult {
if actual.shape != expected.shape {
return ValidationResult::Fail(format!(
"Shape mismatch: got {:?}, expected {:?}",
actual.shape, expected.shape,
));
}
let mut mismatches = Mismatches::default();
compare(
actual,
expected,
epsilon as f64,
&mut Vec::new(),
&mut mismatches,
);
if mismatches.count == 0 {
ValidationResult::Pass
} else {
ValidationResult::Fail(mismatches.report(&actual.shape))
}
}
#[derive(Default)]
struct Mismatches {
compared: usize,
count: usize,
worst: Option<(Vec<usize>, f64)>,
listed: Vec<(Vec<usize>, String)>,
}
impl Mismatches {
fn record(&mut self, index: &[usize], delta: f64, reason: String) {
self.count += 1;
if self.worst.as_ref().is_none_or(|(_, worst)| delta > *worst) {
self.worst = Some((index.to_vec(), delta));
}
if self.listed.len() < REPORTED_MISMATCHES {
self.listed.push((index.to_vec(), reason));
}
}
fn report(&self, shape: &[usize]) -> String {
let mut out = format!(
"Got incorrect results: {}/{} elements mismatched, shape={shape:?}",
self.count, self.compared
);
if let Some((index, delta)) = &self.worst {
out.push_str(&format!(", max |Δ|={delta:.6} at {index:?}"));
}
out.push_str("\nFirst mismatches:");
for (index, reason) in &self.listed {
out.push_str(&format!("\n {index:?}: {reason}"));
}
if self.count > self.listed.len() {
out.push_str(&format!(
"\n ... and {} more",
self.count - self.listed.len()
));
}
out
}
}
fn compare(
actual: &HostData,
expected: &HostData,
epsilon: f64,
index: &mut Vec<usize>,
mismatches: &mut Mismatches,
) {
let dim = index.len();
if dim < actual.shape.len() {
for i in 0..actual.shape[dim] {
index.push(i);
compare(actual, expected, epsilon, index, mismatches);
index.pop();
}
return;
}
mismatches.compared += 1;
let got = actual.get_f64(index);
let want = expected.get_f64(index);
let epsilon = (epsilon * want).abs().max(epsilon);
if got.is_nan() || want.is_nan() {
if got.is_nan() != want.is_nan() {
mismatches.record(index, f64::INFINITY, format!("got {got}, expected {want}"));
}
} else if got.is_infinite() && want.is_infinite() {
if got.signum() != want.signum() {
mismatches.record(index, f64::INFINITY, format!("got {got}, expected {want}"));
}
} else {
let delta = (got - want).abs();
if delta > epsilon {
mismatches.record(
index,
delta,
format!("got {got}, expected {want}, |Δ|={delta} > ε={epsilon}"),
);
}
}
}