use crate::wire::{Question, RowOption};
use serde::Serialize;
pub const PROBE_STATE: &str = "This is a self-check of the decision channel. Answer each question with the option that is obviously correct.";
#[derive(Debug, Clone, Copy)]
pub struct Probe {
pub id: &'static str,
pub question: &'static str,
pub options: &'static [(&'static str, &'static str)],
pub expected: &'static str,
}
pub const PROBES: &[Probe] = &[
Probe {
id: "sky-is-blue",
question: "On a clear day, is the sky blue?",
options: &[("true", "Yes"), ("false", "No")],
expected: "true",
},
Probe {
id: "ice-not-hot",
question: "Is ice hotter than boiling water?",
options: &[("true", "Yes"), ("false", "No")],
expected: "false",
},
Probe {
id: "which-is-fruit",
question: "Which of these is a fruit?",
options: &[
("apple", "An apple"),
("granite", "A piece of granite"),
("iron", "An iron bar"),
],
expected: "apple",
},
Probe {
id: "summer-day",
question: "How hot is a summer day in the sun?",
options: &[
("0", "Freezing"),
("1", "Cold"),
("2", "Warm"),
("3", "Hot"),
],
expected: "3",
},
];
pub fn questions() -> Vec<Question> {
PROBES
.iter()
.map(|probe| Question {
id: probe.id.to_string(),
question: probe.question.to_string(),
options: probe
.options
.iter()
.map(|(id, description)| RowOption {
id: (*id).to_string(),
description: (*description).to_string(),
})
.collect(),
})
.collect()
}
#[derive(Debug, Clone, Serialize)]
pub struct ProbeResult {
pub id: String,
pub expected: String,
pub argmax: String,
pub expected_probability: f64,
pub passed: bool,
}
#[derive(Debug, Clone, Serialize)]
pub struct ReadoutCheck {
pub probes: usize,
pub passed: usize,
pub results: Vec<ProbeResult>,
}
impl ReadoutCheck {
pub fn is_ok(&self) -> bool {
self.probes > 0 && self.passed == self.probes
}
pub fn from_results(results: Vec<ProbeResult>) -> Self {
let passed = results.iter().filter(|result| result.passed).count();
Self {
probes: results.len(),
passed,
results,
}
}
}
pub fn outcome(probe: &Probe, option_ids: &[String], probabilities: &[f64]) -> ProbeResult {
let argmax_index = probabilities
.iter()
.enumerate()
.fold(0usize, |best, (index, value)| {
if *value > probabilities[best] {
index
} else {
best
}
});
let argmax = option_ids.get(argmax_index).cloned().unwrap_or_default();
let expected_probability = option_ids
.iter()
.position(|id| *id == probe.expected)
.and_then(|index| probabilities.get(index))
.copied()
.unwrap_or(0.0);
ProbeResult {
id: probe.id.to_string(),
expected: probe.expected.to_string(),
passed: argmax == probe.expected,
argmax,
expected_probability,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn every_probe_declares_its_expected_option() {
for probe in PROBES {
assert!(
probe.options.iter().any(|(id, _)| *id == probe.expected),
"probe {:?}: expected {:?} is not one of its options",
probe.id,
probe.expected
);
assert!(
probe.options.len() >= 2,
"probe {:?} needs at least two options",
probe.id
);
}
let ids: Vec<&str> = PROBES.iter().map(|probe| probe.id).collect();
let mut unique = ids.clone();
unique.sort_unstable();
unique.dedup();
assert_eq!(unique.len(), ids.len(), "probe ids must be unique");
}
#[test]
fn questions_carry_the_probes_verbatim() {
let questions = questions();
assert_eq!(questions.len(), PROBES.len());
assert_eq!(questions[0].id, "sky-is-blue");
assert_eq!(questions[0].options[0].id, "true");
assert_eq!(questions[0].options[1].description, "No");
}
#[test]
fn an_answer_that_names_the_expected_option_passes() {
let probe = &PROBES[0];
let ids = vec!["true".to_string(), "false".to_string()];
let result = outcome(probe, &ids, &[0.9, 0.1]);
assert!(result.passed);
assert_eq!(result.argmax, "true");
assert!((result.expected_probability - 0.9).abs() < 1e-12);
}
#[test]
fn an_answer_that_names_another_option_fails_and_reports_what_it_named() {
let probe = &PROBES[1];
let ids = vec!["true".to_string(), "false".to_string()];
let result = outcome(probe, &ids, &[0.8, 0.2]);
assert!(!result.passed);
assert_eq!(result.argmax, "true");
assert!((result.expected_probability - 0.2).abs() < 1e-12);
}
#[test]
fn ties_resolve_to_the_earlier_slot() {
let probe = &PROBES[0];
let ids = vec!["true".to_string(), "false".to_string()];
assert_eq!(outcome(probe, &ids, &[0.5, 0.5]).argmax, "true");
}
#[test]
fn a_report_counts_its_passes() {
let ids = vec!["true".to_string(), "false".to_string()];
let results = vec![
outcome(&PROBES[0], &ids, &[0.9, 0.1]),
outcome(&PROBES[1], &ids, &[0.8, 0.2]),
];
let check = ReadoutCheck::from_results(results);
assert_eq!(check.probes, 2);
assert_eq!(check.passed, 1);
assert!(!check.is_ok());
let all_pass = ReadoutCheck::from_results(vec![outcome(&PROBES[0], &ids, &[0.9, 0.1])]);
assert!(all_pass.is_ok());
assert!(!ReadoutCheck::from_results(Vec::new()).is_ok());
}
}