Skip to main content

somatize_study/sampler/
mod.rs

1//! Where to look next.
2//!
3//! Each scheme is a type of its own with its own `ask`, and [`Sampler`] is the
4//! family for when the scheme arrives as data — the same shape as
5//! [`Partition`](crate::Partition) and [`Pruner`](crate::Pruner).
6//!
7//! | scheme | looks at | covers | runs out | derives from the index |
8//! |---|---|---|---|---|
9//! | [`Grid`] | **the space's shape** | exactly, where it cut | yes | yes |
10//! | [`Random`] | **nothing** | in expectation | no | yes |
11//! | [`Halton`] | **nothing** | every prefix, thinning with the knobs | no | yes |
12//! | [`Sobol`] | **nothing** | every prefix, up to [`KNOBS`] of them | no | yes |
13//! | [`Tpe`] | **what already happened** | not what it is for | no | no |
14//!
15//! Three look at nothing and are still three schemes, which is why *looks at* is
16//! not the only column: [`Random`] is uniform in expectation, the other two by
17//! construction for every prefix. They pay differently — [`Halton`] is
18//! arithmetic with no ceiling whose cover thins with many knobs, [`Sobol`] has no
19//! seam but carries a table and answers nothing past [`KNOBS`].
20//!
21//! **`ask` is a function of the index**, not of what came before. The original's
22//! took `&mut self` and had a `prepare`; this takes neither, so asking for trial
23//! 7 without having asked for the first six gives the same answer. That is what
24//! lets a study over a shared folder work with no coordinator. [`Tpe`] is the
25//! honest exception and says so.
26//!
27//! A sampler starts nothing: `ask` gives back a [`Point`], or `None` — a
28//! [`Grid`] with no combinations left, which is how a `for` knows to stop.
29
30mod drawing;
31mod grid;
32mod halton;
33mod random;
34mod sobol;
35mod tpe;
36
37pub use grid::Grid;
38pub use halton::Halton;
39pub use random::Random;
40pub use sobol::{KNOBS, Sobol};
41pub use tpe::Tpe;
42
43use crate::{Point, Space};
44use std::fmt;
45
46/// Whichever of the schemes a sampler is.
47#[derive(Debug, Clone, PartialEq)]
48pub enum Sampler {
49    /// [`Grid`]: every combination, then nothing.
50    Grid(Grid),
51    /// [`Random`]: uniform, looking at nothing.
52    Random(Random),
53    /// [`Halton`]: spread on purpose, one prime per knob.
54    Halton(Halton),
55    /// [`Sobol`]: spread on purpose, and without Halton's seam.
56    Sobol(Sobol),
57    /// [`Tpe`]: guided by what already worked.
58    Tpe(Tpe),
59}
60
61impl Sampler {
62    /// Where to look for the `trial`-th time, or `None` when there is nowhere
63    /// left. `seen` is the points somebody has already been to, and **a score
64    /// that is not there means the trial is still running**. Four of the five
65    /// ignore the argument, which is the point of having five.
66    pub fn ask(&self, space: &Space, trial: usize, seen: &[(Point, Option<f64>)]) -> Option<Point> {
67        match self {
68            Self::Grid(how) => how.ask(space, trial, seen),
69            Self::Random(how) => how.ask(space, trial, seen),
70            Self::Halton(how) => how.ask(space, trial, seen),
71            Self::Sobol(how) => how.ask(space, trial, seen),
72            Self::Tpe(how) => how.ask(space, trial, seen),
73        }
74    }
75}
76
77impl fmt::Display for Sampler {
78    /// As text, which is the form that goes into the record of a run.
79    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
80        match self {
81            Self::Grid(how) => write!(f, "grid:{}", how.steps),
82            Self::Random(how) => write!(f, "random:{}", how.seed),
83            Self::Halton(how) => write!(f, "halton:{}", how.seed),
84            Self::Sobol(how) => write!(f, "sobol:{}", how.seed),
85            Self::Tpe(how) => write!(
86                f,
87                "tpe:{}:startup:{}:candidates:{}:quantile:{}:seed:{}",
88                how.goal, how.startup, how.candidates, how.quantile, how.seed
89            ),
90        }
91    }
92}