Skip to main content

somatize_study/pruner/
threshold.rs

1//! Judged against a constant.
2
3use super::judging::{latest, not_a_number};
4use super::{Pruner, Reason, Verdict};
5use std::fmt;
6
7/// Prune what leaves the bounds you already know are hopeless. The only scheme
8/// that needs **no other trial**, so it works on the very first one — where a
9/// diverged configuration costs most and the other two have nothing to compare
10/// against. With neither bound it still prunes what is not a number, which is
11/// what [`diverged`](Threshold::diverged) is.
12#[derive(Debug, Clone, PartialEq)]
13pub struct Threshold {
14    /// Below this is hopeless. `None` for no floor.
15    pub lower: Option<f64>,
16    /// Above this is hopeless. `None` for no ceiling.
17    pub upper: Option<f64>,
18}
19
20impl Threshold {
21    /// Only what blew up: no bounds, so nothing goes but a value that is not a
22    /// number.
23    pub fn diverged() -> Self {
24        Self {
25            lower: None,
26            upper: None,
27        }
28    }
29
30    /// Continue, or the reason not to. It ignores the other trials on purpose.
31    pub fn verdict(&self, mine: &[f64], _others: &[Vec<f64>]) -> Verdict {
32        let Some((at, value)) = latest(mine) else {
33            return Verdict::Continue;
34        };
35        if let Some(why) = not_a_number(at, value) {
36            return Verdict::Prune(why);
37        }
38        if let Some(bound) = self.lower.filter(|&bound| value < bound) {
39            return Verdict::Prune(Reason::OutOfBounds { value, bound });
40        }
41        if let Some(bound) = self.upper.filter(|&bound| value > bound) {
42            return Verdict::Prune(Reason::OutOfBounds { value, bound });
43        }
44        Verdict::Continue
45    }
46}
47
48impl fmt::Display for Threshold {
49    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
50        write!(
51            f,
52            "threshold:lower:{}:upper:{}",
53            said(self.lower),
54            said(self.upper)
55        )
56    }
57}
58
59/// A bound, or that there is none. Written out either way so the form cannot be
60/// read two ways.
61fn said(bound: Option<f64>) -> String {
62    match bound {
63        None => "none".to_string(),
64        Some(bound) => bound.to_string(),
65    }
66}
67
68impl From<Threshold> for Pruner {
69    fn from(rule: Threshold) -> Self {
70        Self::Threshold(rule)
71    }
72}