use core::fmt;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum UncheckedReason {
AmbiguousAtFloor,
BelowResolution,
NonSmooth,
LayoutUnreachable,
NumericalInstability,
Partial {
checked: u32,
total: u32,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Stage {
Input,
Forward,
Analytic,
Probe,
Comparator,
Calibration,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum InvalidReason {
NonFinite,
Unrepresentable,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Verdict {
Pass,
Mismatch,
Unchecked(UncheckedReason),
Invalid {
stage: Stage,
reason: InvalidReason,
},
StructuralMismatch,
}
impl Verdict {
fn rank(&self) -> u8 {
match self {
Verdict::Pass => 0,
Verdict::Unchecked(_) => 1,
Verdict::Mismatch => 2,
Verdict::Invalid { .. } => 3,
Verdict::StructuralMismatch => 4,
}
}
pub fn combine(self, other: Verdict) -> Verdict {
if other.rank() > self.rank() {
other
} else {
self
}
}
pub fn is_pass(&self) -> bool {
matches!(self, Verdict::Pass)
}
}
#[derive(Debug, Clone)]
pub struct ComponentRecord {
pub a: f64,
pub n: f64,
pub diff: f64,
pub tol_used: f64,
pub outcome: Verdict,
pub signed_zero_discrepancy: bool,
}
#[derive(Debug, Clone)]
pub struct Report {
pub name: String,
pub backend: &'static str,
pub shape: Vec<usize>,
pub analytic: Vec<f64>,
pub numeric: Vec<f64>,
pub components: Vec<ComponentRecord>,
pub worst_index: usize,
pub worst_rel_error: f64,
pub verdict: Verdict,
pub nondeterministic: bool,
}
impl Report {
pub fn passed(&self) -> bool {
self.verdict.is_pass()
}
pub fn checked_fraction(&self) -> (u32, u32) {
let checked = self
.components
.iter()
.filter(|c| matches!(c.outcome, Verdict::Pass | Verdict::Mismatch))
.count() as u32;
(checked, self.components.len() as u32)
}
pub fn failing_indices(&self) -> Vec<usize> {
let mut idx: Vec<usize> = (0..self.components.len())
.filter(|&i| self.components[i].outcome == Verdict::Mismatch)
.collect();
idx.sort_by(|a, b| {
self.components[*b]
.diff
.partial_cmp(&self.components[*a].diff)
.unwrap_or(core::cmp::Ordering::Equal)
});
idx
}
pub fn unchecked_indices(&self) -> Vec<usize> {
(0..self.components.len())
.filter(|&i| matches!(self.components[i].outcome, Verdict::Unchecked(_)))
.collect()
}
pub fn assert_pass(&self) {
if !self.passed() {
panic!("{self}");
}
}
pub fn assert_no_mismatch(&self) {
match self.verdict {
Verdict::Pass | Verdict::Unchecked(_) => {}
_ => panic!("{self}"),
}
}
}
impl fmt::Display for Report {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let (checked, total) = self.checked_fraction();
write!(
f,
"gradcheck {:?} [{}] {} shape={:?} checked={checked}/{total} worst_rel={:.6e}",
self.verdict, self.backend, self.name, self.shape, self.worst_rel_error,
)?;
if let Some(c) = self.components.get(self.worst_index) {
write!(
f,
" at[{}] analytic={:.9} numeric={:.9} diff={:.3e} tol={:.3e}",
self.worst_index, c.a, c.n, c.diff, c.tol_used
)?;
}
if self.nondeterministic {
write!(f, " NONDETERMINISTIC")?;
}
Ok(())
}
}