1use crate::steps::EngineStep;
2use radiate_core::error::RadiateResult;
3use radiate_core::{
4 Alterer, Chromosome, Ecosystem, MetricSet, Objective, Optimize, Population, Score, Select,
5};
6use radiate_core::{Phenotype, radiate_err};
7use radiate_error::Result;
8use radiate_utils::VersionedCounts;
9use std::sync::Arc;
10
11#[derive(Clone)]
12pub struct SelectConfig<C: Chromosome> {
13 pub(crate) count: usize,
14 pub(crate) selector: Arc<dyn Select<C>>,
15 pub(crate) names: (&'static str, &'static str),
16}
17
18impl<C: Chromosome> SelectConfig<C> {
19 pub fn new(
20 count: usize,
21 selector: Arc<dyn Select<C>>,
22 names: (&'static str, &'static str),
23 ) -> Self {
24 Self {
25 count,
26 selector,
27 names,
28 }
29 }
30}
31
32#[derive(Clone)]
33pub struct SurvivorConfig<C: Chromosome> {
34 pub(crate) select: SelectConfig<C>,
35}
36
37impl<C: Chromosome> SurvivorConfig<C> {
38 pub fn new(select: SelectConfig<C>) -> Self {
39 Self { select }
40 }
41}
42
43#[derive(Clone)]
44pub struct OffspringConfig<C: Chromosome> {
45 pub(crate) select: SelectConfig<C>,
46 pub(crate) alters: Vec<Alterer<C>>,
47}
48
49impl<C: Chromosome> OffspringConfig<C> {
50 pub fn new(select: SelectConfig<C>, alters: Vec<Alterer<C>>) -> Self {
51 Self { select, alters }
52 }
53}
54
55pub struct RecombineStep<C: Chromosome> {
56 pub(crate) survivor: SurvivorConfig<C>,
57 pub(crate) offspring: OffspringConfig<C>,
58 pub(crate) objective: Objective,
59 pub(crate) survivor_counts: VersionedCounts,
60 pub(crate) offspring_counts: VersionedCounts,
61}
62
63impl<C: Chromosome> RecombineStep<C> {
64 pub fn new(
65 survivor: SurvivorConfig<C>,
66 offspring: OffspringConfig<C>,
67 objective: Objective,
68 ) -> Self {
69 Self {
70 survivor,
71 offspring,
72 objective,
73 survivor_counts: VersionedCounts::new(),
74 offspring_counts: VersionedCounts::new(),
75 }
76 }
77}
78
79impl<C> EngineStep<C> for RecombineStep<C>
80where
81 C: Chromosome + PartialEq + Clone,
82{
83 #[inline]
84 fn execute(
85 &mut self,
86 generation: usize,
87 ecosystem: &mut Ecosystem<C>,
88 metrics: &mut MetricSet,
89 ) -> Result<()> {
90 let new_members = if ecosystem.species().is_some() {
91 self.recombine_clustered(generation, ecosystem, metrics)
92 } else {
93 self.recombine_flat(generation, ecosystem, metrics)
94 };
95
96 match new_members {
97 Ok((survivors, offspring)) => {
98 let pop = ecosystem.population_mut();
99
100 pop.clear();
101 pop.extend(survivors);
102 pop.extend(offspring);
103
104 Ok(())
105 }
106 Err(err) => Err(err.context("Recombination Step failed")),
107 }
108 }
109}
110
111impl<C> RecombineStep<C>
112where
113 C: Chromosome + PartialEq + Clone,
114{
115 #[inline]
124 fn recombine_flat(
125 &mut self,
126 generation: usize,
127 ecosystem: &mut Ecosystem<C>,
128 metrics: &mut MetricSet,
129 ) -> RadiateResult<(Population<C>, Population<C>)> {
130 let s_selector = &self.survivor.select;
131 let o_selector = &self.offspring.select;
132
133 let pop_slice = ecosystem.population().as_ref();
134 let pop_len = pop_slice.len();
135
136 let s_indices = self.timed_select(s_selector, pop_slice, metrics);
137 let o_indices = self.timed_select(o_selector, pop_slice, metrics);
138
139 self.offspring_counts.begin(pop_len);
140 for &idx in o_indices.iter() {
141 self.offspring_counts.bump(idx);
142 }
143
144 self.survivor_counts.begin(pop_len);
145 for &idx in s_indices.iter() {
146 self.survivor_counts.bump(idx);
147 }
148
149 let (survivors, mut offspring) = self.unioned_walk(ecosystem);
150
151 self.objective.sort(&mut offspring);
152
153 for alt in &mut self.offspring.alters {
154 alt.alter(offspring.as_mut(), metrics, generation)?;
155 }
156
157 Ok((survivors, offspring))
158 }
159
160 #[inline]
166 fn recombine_clustered(
167 &mut self,
168 generation: usize,
169 ecosystem: &mut Ecosystem<C>,
170 metrics: &mut MetricSet,
171 ) -> RadiateResult<(Population<C>, Population<C>)> {
172 let s_selector = &self.survivor.select;
173 let o_selector = &self.offspring.select;
174
175 let (species, population) = ecosystem.species_population_mut();
176 let species = species.as_ref().ok_or(
177 radiate_err!(Step: self.name(), "Species population is None during recombination"),
178 )?;
179
180 population.sort_by(|a, b| a.species().cmp(&b.species()));
181
182 let mut start = 0;
183 let mut species_groups = Vec::with_capacity(species.len());
184 let slice = population.as_ref();
185 for chunk in slice.chunk_by(|a, b| a.species() == b.species()) {
186 species_groups.push((chunk[0].species(), start..start + chunk.len()));
187 start += chunk.len();
188 }
189
190 let mut species_scores = species
191 .iter()
192 .filter_map(|spec| spec.adj_score())
193 .collect::<Vec<_>>();
194
195 if let Objective::Single(Optimize::Minimize) = &self.objective {
196 species_scores.reverse();
197 }
198
199 let quotas = self.quotas_from_scores(&species_scores);
200
201 self.offspring_counts.begin(population.len());
202 for (species, count) in species.iter().zip(quotas.iter()) {
203 let range = species_groups
204 .binary_search_by(|group| group.0.cmp(&species.id()))
205 .ok()
206 .map(|i| species_groups[i].1.clone())
207 .ok_or(radiate_err!(Step: self.name(), "Failed to find species {:?} in population", species.id()))?;
208
209 let mut sub_pop = &mut population[range.clone()];
210 self.objective.sort(&mut sub_pop);
211
212 let offspring = self.timed_select_count(o_selector, sub_pop, metrics, *count);
213
214 for &idx in offspring.iter() {
215 self.offspring_counts.bump(range.start + idx);
216 }
217 }
218
219 let s_indices = self.timed_select(s_selector, population.as_ref(), metrics);
220
221 self.survivor_counts.begin(population.len());
222 for &idx in s_indices.iter() {
223 self.survivor_counts.bump(idx);
224 }
225
226 let (survivors, mut offspring) = self.unioned_walk(ecosystem);
227
228 let o_slice = offspring.as_mut();
229 for sub_pop in o_slice.chunk_by_mut(|a, b| a.species() == b.species()) {
230 let mut chunk = sub_pop;
231 self.objective.sort(&mut chunk);
232
233 for alt in &mut self.offspring.alters {
234 alt.alter(chunk, metrics, generation)?;
235 }
236 }
237
238 Ok((survivors, offspring))
239 }
240
241 #[inline]
242 fn timed_select(
243 &self,
244 select: &SelectConfig<C>,
245 population: &[Phenotype<C>],
246 metrics: &mut MetricSet,
247 ) -> Vec<usize> {
248 self.timed_select_count(select, population, metrics, select.count)
249 }
250
251 #[inline]
252 fn timed_select_count(
253 &self,
254 select: &SelectConfig<C>,
255 population: &[Phenotype<C>],
256 metrics: &mut MetricSet,
257 count: usize,
258 ) -> Vec<usize> {
259 let timer = std::time::Instant::now();
260 let indices = select.selector.select(population, &self.objective, count);
261 metrics.upsert(select.names.0, indices.len());
262 metrics.upsert(select.names.1, timer.elapsed());
263 indices
264 }
265
266 #[inline]
295 fn unioned_walk(&self, ecosystem: &mut Ecosystem<C>) -> (Population<C>, Population<C>) {
296 let mut survivors = Population::with_capacity(self.survivor.select.count);
297 let mut offspring = Population::with_capacity(self.offspring.select.count);
298
299 let pop = ecosystem.population_mut();
300 let iter = self
301 .survivor_counts
302 .iter_pair_live_rev(&self.offspring_counts);
303
304 for (idx, s, o) in iter {
305 let mut s_left = s;
306 let total = s_left + o;
307
308 for _ in 0..total - 1 {
309 if s_left > 0 {
310 survivors.push(pop[idx].clone());
311 s_left -= 1;
312 } else {
313 offspring.push(pop[idx].clone());
314 }
315 }
316
317 let moved = pop.swap_remove(idx);
318 if s_left > 0 {
319 survivors.push(moved);
320 } else {
321 offspring.push(moved);
322 }
323 }
324
325 (survivors, offspring)
326 }
327
328 #[inline]
329 fn quotas_from_scores(&self, scores: &[&Score]) -> Vec<usize> {
330 let n = scores.len();
331 if n == 0 || self.offspring.select.count == 0 {
332 return vec![0; n];
333 }
334
335 let raw_scores = scores.iter().map(|s| s.as_f32()).collect::<Vec<f32>>();
336 let mut min_score = raw_scores.iter().cloned().fold(f32::INFINITY, f32::min);
337 if !min_score.is_finite() {
338 min_score = 0.0;
339 }
340
341 let shifted = raw_scores
342 .iter()
343 .map(|s| (s - min_score).max(0.0))
344 .collect::<Vec<f32>>();
345
346 let sum = shifted.iter().sum::<f32>();
347
348 if sum <= f32::EPSILON {
349 let base = self.offspring.select.count / n;
350 let mut quotas = vec![base; n];
351 let mut remaining = self.offspring.select.count - base * n;
352 let mut i = 0;
353 while remaining > 0 {
354 quotas[i] += 1;
355 remaining -= 1;
356 i += 1;
357 }
358
359 return quotas;
360 }
361
362 let total = self.offspring.select.count as f32;
363
364 let mut quotas = Vec::with_capacity(n);
365 let mut fracs = Vec::with_capacity(n);
366 let mut assigned = 0;
367
368 for (idx, w) in shifted.iter().enumerate() {
369 let p = *w / sum;
370 let exact = p * total;
371 let base = exact.floor() as usize;
372 let frac = exact - base as f32;
373
374 quotas.push(base);
375 fracs.push((frac, idx));
376 assigned += base;
377 }
378
379 let remaining = self.offspring.select.count.saturating_sub(assigned);
380 fracs.sort_unstable_by(|a, b| b.0.partial_cmp(&a.0).unwrap_or(std::cmp::Ordering::Equal));
381
382 for (_, idx) in fracs.iter().take(remaining) {
383 quotas[*idx] += 1;
384 }
385
386 quotas
387 }
388}