Skip to main content

fugue_evo/fitness/
multi_objective.rs

1//! Multi-objective fitness abstraction
2//!
3//! The [`MultiObjectiveFitness`] trait is shared by the classic NSGA-II
4//! algorithm (`algorithms::nsga2`, `classic` feature) and the Pareto-posterior
5//! inference layer (`inference::pareto`, `ppl` feature), so it lives here in
6//! the always-available core rather than in either layer.
7
8/// Multi-objective fitness function trait
9#[cfg(feature = "parallel")]
10pub trait MultiObjectiveFitness<G>: Send + Sync {
11    /// Number of objectives
12    fn num_objectives(&self) -> usize;
13
14    /// Evaluate all objectives (all to be minimized by convention)
15    fn evaluate(&self, genome: &G) -> Vec<f64>;
16}
17
18/// Multi-objective fitness function trait
19#[cfg(not(feature = "parallel"))]
20pub trait MultiObjectiveFitness<G> {
21    /// Number of objectives
22    fn num_objectives(&self) -> usize;
23
24    /// Evaluate all objectives (all to be minimized by convention)
25    fn evaluate(&self, genome: &G) -> Vec<f64>;
26}
27
28/// Adapts a closure into a [`MultiObjectiveFitness`] with an explicit objective
29/// count.
30///
31/// A bare `Fn(&G) -> Vec<f64>` cannot report how many objectives it produces,
32/// so the previous blanket impl hardcoded `num_objectives() == 2`, silently
33/// mis-reporting the count for any 3+ objective problem (EV-85). This wrapper
34/// requires the caller to state the true objective count at construction.
35///
36/// ```
37/// use fugue_evo::fitness::multi_objective::{ClosureMultiObjective, MultiObjectiveFitness};
38/// use fugue_evo::genome::real_vector::RealVector;
39/// use fugue_evo::genome::traits::RealValuedGenome;
40///
41/// let fitness = ClosureMultiObjective::new(3, |g: &RealVector| {
42///     let x = g.genes()[0];
43///     vec![x, x * x, x + 1.0]
44/// });
45/// assert_eq!(fitness.num_objectives(), 3);
46/// ```
47pub struct ClosureMultiObjective<G, F> {
48    num_objectives: usize,
49    f: F,
50    _phantom: std::marker::PhantomData<fn() -> G>,
51}
52
53impl<G, F> ClosureMultiObjective<G, F>
54where
55    F: Fn(&G) -> Vec<f64>,
56{
57    /// Wrap `f`, declaring that it returns `num_objectives` objective values.
58    pub fn new(num_objectives: usize, f: F) -> Self {
59        Self {
60            num_objectives,
61            f,
62            _phantom: std::marker::PhantomData,
63        }
64    }
65}
66
67#[cfg(feature = "parallel")]
68impl<G, F> MultiObjectiveFitness<G> for ClosureMultiObjective<G, F>
69where
70    F: Fn(&G) -> Vec<f64> + Send + Sync,
71{
72    fn num_objectives(&self) -> usize {
73        self.num_objectives
74    }
75
76    fn evaluate(&self, genome: &G) -> Vec<f64> {
77        (self.f)(genome)
78    }
79}
80
81#[cfg(not(feature = "parallel"))]
82impl<G, F> MultiObjectiveFitness<G> for ClosureMultiObjective<G, F>
83where
84    F: Fn(&G) -> Vec<f64>,
85{
86    fn num_objectives(&self) -> usize {
87        self.num_objectives
88    }
89
90    fn evaluate(&self, genome: &G) -> Vec<f64> {
91        (self.f)(genome)
92    }
93}