Skip to main content

somatize_study/sampler/
grid.rs

1//! Every combination, in order, until there are none left.
2
3use super::Sampler;
4use super::drawing::span;
5use crate::{Dimension, Point, Setting, Space};
6
7/// Walk the whole space and stop.
8///
9/// The only scheme that **runs out**: `ask` answers `None` once every
10/// combination is handed out, which is how a study written as a `for` stops
11/// without being told a number. What is continuous is cut by `steps`, and an
12/// `Int` narrower than that is taken whole. **The first dimension varies
13/// fastest**, which is worth knowing when a grid is stopped early.
14#[derive(Debug, Clone, PartialEq, Eq, Hash)]
15pub struct Grid {
16    /// How many values to take from each continuous knob.
17    pub steps: usize,
18}
19
20impl Grid {
21    /// The `trial`-th combination, or `None` when there are no more.
22    ///
23    /// It looks at neither the finished trials nor a seed: a grid is a function
24    /// of the space and the index alone.
25    pub fn ask(
26        &self,
27        space: &Space,
28        trial: usize,
29        _seen: &[(Point, Option<f64>)],
30    ) -> Option<Point> {
31        if space.is_empty() || trial >= self.total(space) {
32            return None;
33        }
34        let mut left = trial;
35        let mut settings = Vec::with_capacity(space.len());
36        for (name, dimension) in space.dimensions() {
37            let many = dimension.grid_of(self.steps);
38            settings.push((name.clone(), nth(dimension, left % many, many)));
39            left /= many;
40        }
41        Some(Point::of(settings))
42    }
43
44    /// How many combinations there are — which is how many trials a grid search
45    /// **is**, and something a caller wants before it starts one.
46    pub fn total(&self, space: &Space) -> usize {
47        if space.is_empty() {
48            return 0;
49        }
50        space
51            .dimensions()
52            .iter()
53            .map(|(_, dimension)| dimension.grid_of(self.steps))
54            .product()
55    }
56}
57
58/// The `which`-th of `many` values of this knob, ends included.
59fn nth(dimension: &Dimension, which: usize, many: usize) -> Setting {
60    if let Dimension::Choice(options) = dimension {
61        return Setting::Choice(options[which.min(options.len() - 1)].clone());
62    }
63    let (from, to) = span(dimension);
64    // With a single value there is no interval to divide, and the bottom is a
65    // less surprising answer than the middle.
66    let place = if many <= 1 {
67        from
68    } else {
69        from + (to - from) * which as f64 / (many - 1) as f64
70    };
71    super::drawing::settle(dimension, place)
72}
73
74impl From<Grid> for Sampler {
75    fn from(how: Grid) -> Self {
76        Self::Grid(how)
77    }
78}