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}