use crate::error::Result;
use serde::Serialize;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
pub struct Threshold {
pub flips_at: u64,
pub low_success: bool,
pub high_success: bool,
pub evaluations: u32,
}
pub(crate) fn search_threshold<F>(lo: u64, hi: u64, ok: F) -> Result<Option<Threshold>>
where
F: Fn(u64) -> Result<bool>,
{
assert!(lo <= hi, "search bounds inverted");
let mut evaluations = 0u32;
let mut eval = |v: u64| -> Result<bool> {
evaluations += 1;
ok(v)
};
let low_success = eval(lo)?;
let high_success = eval(hi)?;
if low_success == high_success {
return Ok(None);
}
let (mut lo, mut hi) = (lo, hi);
while hi - lo > 1 {
let mid = lo + (hi - lo) / 2;
if eval(mid)? == low_success {
lo = mid;
} else {
hi = mid;
}
}
Ok(Some(Threshold {
flips_at: hi,
low_success,
high_success,
evaluations,
}))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn finds_the_exact_boundary_of_a_monotonic_flip() {
let th = search_threshold(0, 1000, |v| Ok(v <= 40))
.unwrap()
.expect("a flip exists in range");
assert_eq!(th.flips_at, 41);
assert!(th.low_success);
assert!(!th.high_success);
assert!(th.evaluations < 16, "took {} evals", th.evaluations);
}
#[test]
fn no_flip_when_both_bounds_agree() {
assert!(search_threshold(0, 100, |_| Ok(true)).unwrap().is_none());
assert!(search_threshold(0, 100, |_| Ok(false)).unwrap().is_none());
}
#[test]
fn finds_a_boundary_that_flips_the_other_direction() {
let th = search_threshold(0, 4096, |v| Ok(v >= 500))
.unwrap()
.unwrap();
assert_eq!(th.flips_at, 500);
assert!(!th.low_success);
assert!(th.high_success);
}
}