1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
//! Coupled fitness evaluation for co-evolutionary algorithms.
//!
//! In a single-population strategy, fitness is an absolute function of one
//! genome. In co-evolution it is *relational*: an individual's score depends
//! on the individuals in the other population(s) — predator vs. prey, sorter
//! vs. test case, sub-solution vs. complementary sub-solution. The
//! [`CoupledFitness`] trait captures exactly that joint evaluation.
use ;
use ObjectiveSense;
/// Joint fitness evaluation across two or more co-evolving populations.
///
/// `evaluate_coupled` receives every population at once and returns one
/// fitness vector per population, each in the objective's **natural** value
/// space (in the direction declared by [`sense`](Self::sense) — a cost is
/// returned as its natural cost, a reward/score as its natural value, with no
/// hand-negation). The co-evolutionary **algorithm** is the sole
/// canonicalisation chokepoint: it maps natural → canonical (maximise-native)
/// space once per generation, exactly as the
/// [`EvolutionaryHarness`](crate::strategy::EvolutionaryHarness) does for the
/// single-population path (ADR 0023).
///
/// # Uniform-direction constraint
///
/// [`sense`](Self::sense) applies **uniformly to every returned population
/// vector** — all populations must be reported in one common direction. A
/// truly asymmetric / zero-sum objective (population A *maximises* a score
/// while population B *minimises* the same score) has **no** compliant
/// single-`sense()` representation and must be pre-expressed by the
/// implementor: e.g. return A's score and B's **negated** score, both declared
/// [`Maximize`](ObjectiveSense::Maximize). Otherwise one population would be
/// silently anti-optimised (canonicalised in the wrong direction).
///
/// # Invariants
///
/// - The returned `Vec` has the same length as `populations`, and
/// `result[i]` has shape `(pop_size_i,)` matching `populations[i]`'s row
/// count.
/// - Row order is preserved: `result[i][r]` is the fitness of the individual
/// at row `r` of `populations[i]`.
/// - The trait is **N-population-ready**: it accepts a slice so a future
/// `N > 2` extension needs no breaking change. v1 algorithms always pass
/// exactly two populations, and implementors should
/// `debug_assert_eq!(populations.len(), 2)` to catch misuse without a
/// compile-time const-generic barrier.
///
/// # Examples
///
/// A trivial separable implementor scoring each population by its row sums
/// (a natural maximise):
///
/// ```
/// use burn::backend::Flex;
/// use burn::tensor::{Tensor, TensorData, backend::Backend};
/// use rlevo_core::objective::ObjectiveSense;
/// use rlevo_evolution::coevolution::CoupledFitness;
///
/// struct RowSum;
/// impl<B: Backend> CoupledFitness<B> for RowSum {
/// fn evaluate_coupled(&self, populations: &[Tensor<B, 2>]) -> Vec<Tensor<B, 1>> {
/// debug_assert_eq!(populations.len(), 2);
/// populations
/// .iter()
/// .map(|p| p.clone().sum_dim(1).squeeze_dim::<1>(1))
/// .collect()
/// }
/// fn sense(&self) -> ObjectiveSense {
/// ObjectiveSense::Maximize
/// }
/// }
///
/// let device = Default::default();
/// let a = Tensor::<Flex, 2>::from_data(TensorData::new(vec![1.0_f32, 2.0, 3.0, 4.0], [2, 2]), &device);
/// let b = Tensor::<Flex, 2>::from_data(TensorData::new(vec![0.0_f32, 0.0, 1.0, 1.0], [2, 2]), &device);
/// let out = RowSum.evaluate_coupled(&[a, b]);
/// assert_eq!(out.len(), 2);
/// ```