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}