Skip to main content

forge_core/
solution.rs

1//! Optimization results shared by every algorithm.
2
3/// The best point an optimizer found, with its (minimized) objective value.
4#[derive(Debug, Clone, PartialEq)]
5pub struct Solution {
6    /// Best decision vector.
7    pub x: Vec<f64>,
8    /// Objective value at `x`, in the **minimizing** sense forge uses
9    /// internally. If the problem was wrapped in [`Maximize`], negate this to
10    /// recover the original value (or use [`Report::best_value_maximized`]).
11    ///
12    /// [`Maximize`]: crate::problem::Maximize
13    pub value: f64,
14}
15
16/// A point in a multi-objective search, with its full objective vector (all in
17/// the minimizing sense).
18#[derive(Debug, Clone, PartialEq)]
19pub struct MultiSolution {
20    /// Decision vector.
21    pub x: Vec<f64>,
22    /// Objective values at `x`, length `n_objectives`.
23    pub objectives: Vec<f64>,
24}
25
26/// The outcome of a multi-objective run: the approximated Pareto front (a set
27/// of mutually non-dominated solutions) and the evaluations spent.
28#[derive(Debug, Clone, PartialEq)]
29pub struct ParetoFront {
30    /// Non-dominated solutions found.
31    pub solutions: Vec<MultiSolution>,
32    /// Objective evaluations performed.
33    pub evaluations: usize,
34}
35
36impl ParetoFront {
37    /// Number of solutions on the front.
38    pub fn len(&self) -> usize {
39        self.solutions.len()
40    }
41
42    /// Whether the front is empty.
43    pub fn is_empty(&self) -> bool {
44        self.solutions.is_empty()
45    }
46
47    /// The objective vectors of every solution on the front.
48    pub fn objective_vectors(&self) -> impl Iterator<Item = &[f64]> {
49        self.solutions.iter().map(|s| s.objectives.as_slice())
50    }
51}
52
53/// Why an optimizer stopped.
54///
55/// Marked `#[non_exhaustive]`: future versions may add reasons (e.g. internal
56/// convergence), so matches need a wildcard arm.
57#[derive(Debug, Clone, Copy, PartialEq, Eq)]
58#[non_exhaustive]
59pub enum StopReason {
60    /// The evaluation budget was exhausted.
61    BudgetExhausted,
62    /// The target objective value was reached.
63    TargetReached,
64    /// Internal convergence criteria fired before the budget was spent (e.g.
65    /// CMA-ES TolFun/TolX/step-size collapse). Only some algorithms emit this.
66    Converged,
67}
68
69/// Full outcome of a run: the best [`Solution`], how it stopped, and the number
70/// of objective evaluations actually spent.
71#[derive(Debug, Clone, PartialEq)]
72pub struct Report {
73    /// Best solution found.
74    pub solution: Solution,
75    /// Why the run terminated.
76    pub stop: StopReason,
77    /// Objective evaluations performed.
78    pub evaluations: usize,
79}
80
81impl Report {
82    /// Best decision vector.
83    pub fn best(&self) -> &[f64] {
84        &self.solution.x
85    }
86
87    /// Best objective in the minimizing sense (as stored).
88    pub fn best_value(&self) -> f64 {
89        self.solution.value
90    }
91
92    /// Best objective in the *maximizing* sense — the negation of
93    /// [`Report::best_value`]. Use this when the problem was wrapped in
94    /// [`Maximize`].
95    ///
96    /// [`Maximize`]: crate::problem::Maximize
97    pub fn best_value_maximized(&self) -> f64 {
98        -self.solution.value
99    }
100}