Skip to main content

somatize_study/
goal.rs

1//! Which way is better.
2//!
3//! A loss goes down and an accuracy goes up, and nothing in a number says which,
4//! so everything at this level that compares two results has to be told. It
5//! lives on the piece that compares rather than being passed to every call, so a
6//! pruner without a direction is a state that cannot be written down.
7
8use std::fmt;
9use std::str::FromStr;
10
11/// Which way is better.
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
13pub enum Goal {
14    /// Smaller is better: a loss, an error, a runtime.
15    Minimize,
16    /// Larger is better: an accuracy, an F1, a reward.
17    Maximize,
18}
19
20impl Goal {
21    /// Whether `one` is better than `than`. Strictly: equal is not better, so a
22    /// trial that ties is never pruned for tying.
23    pub fn better(&self, one: f64, than: f64) -> bool {
24        match self {
25            Self::Minimize => one < than,
26            Self::Maximize => one > than,
27        }
28    }
29
30    /// The best of them, or `None` if there are none. Values that are not
31    /// numbers are skipped: they are not comparable to anything.
32    pub fn best_of(&self, values: &[f64]) -> Option<f64> {
33        values
34            .iter()
35            .copied()
36            .filter(|v| !v.is_nan())
37            .reduce(|best, v| if self.better(v, best) { v } else { best })
38    }
39}
40
41impl FromStr for Goal {
42    type Err = GoalError;
43
44    /// `min` or `max`, the way it is written down. A typo is caught where it
45    /// was typed rather than becoming a search that optimised backwards.
46    fn from_str(s: &str) -> Result<Self, Self::Err> {
47        match s {
48            "min" => Ok(Self::Minimize),
49            "max" => Ok(Self::Maximize),
50            _ => Err(GoalError::Unknown(s.to_string())),
51        }
52    }
53}
54
55/// Why that does not say which way is better.
56#[derive(Debug, Clone, PartialEq, Eq)]
57pub enum GoalError {
58    /// We do not know that direction.
59    Unknown(String),
60}
61
62impl fmt::Display for GoalError {
63    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
64        match self {
65            Self::Unknown(said) => write!(
66                f,
67                "`{said}` does not say which way is better: write `min` for a loss                  or `max` for an accuracy"
68            ),
69        }
70    }
71}
72
73impl std::error::Error for GoalError {}
74
75impl fmt::Display for Goal {
76    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
77        match self {
78            Self::Minimize => f.write_str("min"),
79            Self::Maximize => f.write_str("max"),
80        }
81    }
82}