optimum/analysis/
batch.rs

1//! Run and analyze multiple executions of a solver.
2
3mod statistics;
4
5pub use statistics::{Gap, Statistics};
6
7use std::{
8    fmt::Debug,
9    time::{Duration, Instant},
10};
11
12use typed_builder::TypedBuilder;
13
14use crate::core::{solver, Evaluation, Problem, Solver, StopCriterion};
15
16/// A batch is a sequence of multiple executions of a stochastic solver which is often used to
17/// compare the solver's performance across different seed numbers.
18#[derive(Debug, TypedBuilder)]
19pub struct Batch<P, B, S, SC, H>
20where
21    P: Problem,
22    SC: StopCriterion<P> + Clone,
23    B: FnMut(usize, usize) -> S,
24    S: Solver<SC, H, P = P>,
25    H: solver::IterHook<P> + Clone,
26{
27    base_seed: usize,
28    executions: usize,
29    solver: B,
30    stop_criterion: SC,
31    hook: H,
32}
33
34impl<P, B, S, SC, H> Batch<P, B, S, SC, H>
35where
36    P: Problem,
37    SC: StopCriterion<P> + Clone,
38    B: FnMut(usize, usize) -> S,
39    S: Solver<SC, H, P = P>,
40    H: solver::IterHook<P> + Clone,
41{
42    /// Runs a new `Batch`.
43    ///
44    /// ## Example
45    ///
46    /// ```ignore
47    /// let stop_criterion = IterCriterion::new(100);
48    /// let batch = Batch::builder()
49    ///     .base_seed(1)
50    ///     .executions(10)
51    ///     .solver(build_solver)
52    ///     .stop_criterion(&stop_criterion)
53    ///     .hook(hook::Empty)
54    ///     .build()
55    ///     .run()
56    ///     .unwrap();
57    /// ```
58    pub fn run(mut self) -> Option<BatchResult<P, H>> {
59        let executions = (1..=self.executions as usize)
60            .flat_map(|exec_number| {
61                let start = Instant::now();
62
63                let mut hook = self.hook.clone();
64                let evaluation = {
65                    let mut solver = (self.solver)(self.base_seed, exec_number);
66                    solver.solve(&mut self.stop_criterion.clone(), &mut hook)?
67                };
68
69                let duration = start.elapsed();
70
71                Some(Execution {
72                    number: exec_number,
73                    evaluation,
74                    duration,
75                    hook,
76                })
77            })
78            .collect::<Vec<_>>();
79
80        if executions.is_empty() {
81            None
82        } else {
83            Some(BatchResult {
84                executions,
85                base_seed: self.base_seed,
86            })
87        }
88    }
89}
90
91/// The results obtained after running a [Batch].
92pub struct BatchResult<P: Problem, H> {
93    base_seed: usize,
94    executions: Vec<Execution<P, H>>,
95}
96
97/// an `Execution` contains information about a full execution on a batch,
98/// such as best evaluation obtained, its number and duration. It's also possible
99/// to obtain e.g. metadata from the [solver::IterHook] used during that execution.
100pub struct Execution<P: Problem, H> {
101    number: usize,
102    evaluation: Evaluation<P>,
103    duration: Duration,
104    hook: H,
105}
106
107impl<P: Problem, H> Execution<P, H> {
108    /// Get a reference to the execution's number.
109    pub fn number(&self) -> usize {
110        self.number
111    }
112
113    /// Get a reference to the execution's evaluation.
114    pub fn evaluation(&self) -> &Evaluation<P> {
115        &self.evaluation
116    }
117
118    /// Get a reference to the execution's duration.
119    pub fn duration(&self) -> Duration {
120        self.duration
121    }
122
123    /// Get a reference to the execution's hook.
124    pub fn hook(&self) -> &H {
125        &self.hook
126    }
127}
128
129impl<P, H> Debug for Execution<P, H>
130where
131    P::Value: Debug,
132    P: Problem,
133{
134    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
135        f.debug_struct("Execution")
136            .field("number", &self.number)
137            .field("solution value", &self.evaluation.value())
138            .field("duration", &self.duration)
139            .finish()
140    }
141}
142
143impl<P: Problem, H> BatchResult<P, H> {
144    /// Get a reference to the batch's evaluations, which are the best solutions for each execution.
145    pub fn executions(&self) -> &[Execution<P, H>] {
146        &self.executions
147    }
148
149    /// Get the base seed used by the executions.
150    pub fn base_seed(&self) -> usize {
151        self.base_seed
152    }
153}
154
155impl<P, H> Debug for BatchResult<P, H>
156where
157    P: Problem + Debug,
158    P::Solution: Debug,
159    P::Value: Debug,
160    H: Debug,
161{
162    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
163        f.debug_struct("BatchResult")
164            .field("base seed", &self.base_seed)
165            .field("evaluations", &self.executions)
166            .finish()
167    }
168}