use crate::verification::OracleKind;
#[derive(Clone, Debug, PartialEq)]
pub struct Kat {
pub label: String,
pub expected: f64,
pub tol: f64,
}
#[derive(Clone, Debug)]
pub struct Oracle {
pub name: String,
pub kind: OracleKind,
pub vectors: Vec<Kat>,
}
#[derive(Clone, Debug, PartialEq)]
pub struct OracleReport {
pub passed: bool,
pub per_vector: Vec<(String, bool, f64)>,
}
pub fn check(o: &Oracle, computed: &[(String, f64)]) -> OracleReport {
let mut per_vector = Vec::with_capacity(o.vectors.len());
let mut all_passed = true;
for kat in &o.vectors {
let (within, err) = match computed.iter().find(|(label, _)| *label == kat.label) {
Some((_, value)) => {
let err = (value - kat.expected).abs();
(err <= kat.tol, err)
}
None => (false, f64::INFINITY),
};
all_passed &= within;
per_vector.push((kat.label.clone(), within, err));
}
OracleReport {
passed: all_passed,
per_vector,
}
}
pub fn justifies_validated(o: &Oracle, report: &OracleReport) -> bool {
report.passed && o.kind == OracleKind::ExternalDataset
}
#[cfg(test)]
mod tests {
use super::*;
fn kats() -> Vec<Kat> {
vec![
Kat {
label: "a".to_string(),
expected: 1.0,
tol: 1e-6,
},
Kat {
label: "b".to_string(),
expected: 2.0,
tol: 1e-3,
},
]
}
fn computed_within() -> Vec<(String, f64)> {
vec![("a".to_string(), 1.0 + 1e-9), ("b".to_string(), 2.0 - 1e-4)]
}
#[test]
fn passing_external_dataset_justifies_validated() {
let o = Oracle {
name: "published vectors".to_string(),
kind: OracleKind::ExternalDataset,
vectors: kats(),
};
let report = check(&o, &computed_within());
assert!(report.passed, "all vectors should be within tolerance");
assert!(
justifies_validated(&o, &report),
"a passing ExternalDataset oracle must justify Validated"
);
}
#[test]
fn passing_non_external_oracle_does_not_justify_validated() {
for kind in [
OracleKind::ReferenceImpl,
OracleKind::InternalConsistency,
OracleKind::NoneKind,
] {
let o = Oracle {
name: "self-consistency".to_string(),
kind,
vectors: kats(),
};
let report = check(&o, &computed_within());
assert!(report.passed, "vectors are within tolerance for {kind:?}");
assert!(
!justifies_validated(&o, &report),
"a passing {kind:?} oracle must NOT justify Validated — stays Modelled"
);
}
}
#[test]
fn failing_external_dataset_does_not_justify_validated() {
let o = Oracle {
name: "published vectors".to_string(),
kind: OracleKind::ExternalDataset,
vectors: kats(),
};
let computed = vec![("a".to_string(), 1.0), ("b".to_string(), 9.0)];
let report = check(&o, &computed);
assert!(!report.passed, "an out-of-tolerance vector must fail");
assert!(
!justifies_validated(&o, &report),
"a FAILING ExternalDataset oracle must NOT justify Validated"
);
}
#[test]
fn missing_computed_value_fails_with_infinite_error() {
let o = Oracle {
name: "published vectors".to_string(),
kind: OracleKind::ExternalDataset,
vectors: kats(),
};
let computed = vec![("a".to_string(), 1.0)]; let report = check(&o, &computed);
assert!(!report.passed);
let b = report
.per_vector
.iter()
.find(|(label, _, _)| label == "b")
.expect("b vector reported");
assert!(!b.1, "missing value is not a pass");
assert!(b.2.is_infinite(), "missing value reports infinite error");
assert!(!justifies_validated(&o, &report));
}
}