1use rand::{rngs::StdRng, Rng, SeedableRng};
11use std::cmp::Ordering;
12
13#[derive(Clone, Copy, Debug, Eq, PartialEq)]
15pub enum Optimize {
16 Maximize,
17 Minimize,
18}
19
20#[derive(Clone, Debug)]
22pub struct Evaluated<G, F> {
23 pub genome: G,
24 pub fitness: F,
25}
26
27pub trait Problem {
41 type Genome: Clone + Send + Sync + 'static;
42 type Fitness: PartialOrd + Copy + Send + Sync + 'static;
43
44 fn random_genome<R: Rng + ?Sized>(&mut self, rng: &mut R) -> Self::Genome;
45
46 fn fitness(&self, genome: &Self::Genome) -> Self::Fitness;
47
48 fn mutate<R: Rng + ?Sized>(&mut self, _genome: &mut Self::Genome, _rng: &mut R) {
49 }
51
52 fn crossover<R: Rng + ?Sized>(
53 &mut self,
54 a: &Self::Genome,
55 _b: &Self::Genome,
56 _rng: &mut R,
57 ) -> Self::Genome {
58 a.clone()
60 }
61
62 fn repair(&mut self, _genome: &mut Self::Genome) {
63 }
65}
66
67pub trait Selection<P: Problem> {
71 fn select<'a, R: Rng + ?Sized>(
72 &self,
73 rng: &mut R,
74 population: &'a [Evaluated<P::Genome, P::Fitness>],
75 optimize: Optimize,
76 ) -> (&'a P::Genome, &'a P::Genome);
77}
78
79#[derive(Clone, Copy, Debug)]
81pub struct TournamentSelection {
82 pub k: usize,
83}
84
85impl Default for TournamentSelection {
86 fn default() -> Self {
87 Self { k: 3 }
88 }
89}
90
91impl<P: Problem> Selection<P> for TournamentSelection {
92 fn select<'a, R: Rng + ?Sized>(
93 &self,
94 rng: &mut R,
95 population: &'a [Evaluated<P::Genome, P::Fitness>],
96 optimize: Optimize,
97 ) -> (&'a P::Genome, &'a P::Genome) {
98 debug_assert!(population.len() >= 2, "population must have at least 2 individuals");
99 let pick_one = |rng: &mut R| {
100 let k = self.k.max(1).min(population.len());
103 let mut best_idx = rng.gen_range(0..population.len());
104 for _ in 1..k {
105 let idx = rng.gen_range(0..population.len());
106 best_idx = better_index(population, best_idx, idx, optimize);
107 }
108 &population[best_idx].genome
109 };
110 (pick_one(rng), pick_one(rng))
111 }
112}
113
114#[derive(Clone, Debug)]
116pub struct Config {
117 pub pop_size: usize,
118 pub optimize: Optimize,
119 pub crossover_rate: f64, pub mutation_rate: f64, pub elitism: usize, pub seed: Option<u64>, }
124
125impl Default for Config {
126 fn default() -> Self {
127 Self {
128 pop_size: 100,
129 optimize: Optimize::Maximize,
130 crossover_rate: 0.9,
131 mutation_rate: 0.1,
132 elitism: 1,
133 seed: None,
134 }
135 }
136}
137
138pub struct Builder<P: Problem, S: Selection<P> = TournamentSelection> {
140 problem: P,
141 config: Config,
142 selection: S,
143}
144
145impl<P: Problem> Builder<P, TournamentSelection> {
146 pub fn new(problem: P) -> Self {
147 Self {
148 problem,
149 config: Config::default(),
150 selection: TournamentSelection::default(),
151 }
152 }
153}
154
155impl<P: Problem, S: Selection<P>> Builder<P, S> {
156 pub fn pop_size(mut self, n: usize) -> Self {
157 self.config.pop_size = n.max(2);
158 self
159 }
160 pub fn optimize(mut self, opt: Optimize) -> Self {
161 self.config.optimize = opt;
162 self
163 }
164 pub fn mutation_rate(mut self, p: f64) -> Self {
165 self.config.mutation_rate = clamp01(p);
166 self
167 }
168 pub fn crossover_rate(mut self, p: f64) -> Self {
169 self.config.crossover_rate = clamp01(p);
170 self
171 }
172 pub fn elitism(mut self, k: usize) -> Self {
173 self.config.elitism = k;
174 self
175 }
176 pub fn seed(mut self, seed: u64) -> Self {
177 self.config.seed = Some(seed);
178 self
179 }
180 pub fn selection<S2: Selection<P>>(self, selection: S2) -> Builder<P, S2> {
181 Builder {
182 problem: self.problem,
183 config: self.config,
184 selection,
185 }
186 }
187 pub fn build(mut self) -> GA<P, S> {
188 let mut rng = match self.config.seed {
189 Some(s) => StdRng::seed_from_u64(s),
190 None => StdRng::from_entropy(),
191 };
192
193 let mut pop = Vec::with_capacity(self.config.pop_size);
195 for _ in 0..self.config.pop_size {
196 let g = self.problem.random_genome(&mut rng);
197 let f = self.problem.fitness(&g);
198 pop.push(Evaluated { genome: g, fitness: f });
199 }
200 GA {
201 problem: self.problem,
202 config: self.config,
203 selection: self.selection,
204 rng,
205 population: pop,
206 generation: 0,
207 }
208 }
209}
210
211pub struct GA<P: Problem, S: Selection<P> = TournamentSelection> {
213 problem: P,
214 config: Config,
215 selection: S,
216 rng: StdRng,
217 population: Vec<Evaluated<P::Genome, P::Fitness>>,
218 generation: usize,
219}
220
221#[derive(Clone, Debug)]
223pub struct RunResult<G, F> {
224 pub generations: usize,
225 pub best: Evaluated<G, F>,
226 pub average_fitness: f64,
227}
228
229impl<P: Problem, S: Selection<P>> GA<P, S> {
230 pub fn builder(problem: P) -> Builder<P, TournamentSelection> {
231 Builder::new(problem)
232 }
233
234 pub fn generation(&self) -> usize {
236 self.generation
237 }
238
239 pub fn best(&self) -> &Evaluated<P::Genome, P::Fitness> {
241 best_of(&self.population, self.config.optimize)
242 .expect("population must be non-empty")
243 }
244
245 pub fn average_fitness(&self) -> f64
247 where
248 P::Fitness: Into<f64>,
249 {
250 let sum: f64 = self.population.iter().map(|e| e.fitness.into()).sum();
251 sum / self.population.len() as f64
252 }
253
254 pub fn step(&mut self) {
256 let n = self.population.len();
257 let elitism = self.config.elitism.min(n);
258
259 let mut next: Vec<Evaluated<P::Genome, P::Fitness>> = if elitism > 0 {
261 let mut sorted = self.population.clone();
262 sorted.sort_by(|a, b| cmp_fit(self.config.optimize, &a.fitness, &b.fitness));
263 sorted.into_iter().rev().take(elitism).collect()
264 } else {
265 Vec::with_capacity(n)
266 };
267
268 while next.len() < n {
270 let (pa, pb) = self.selection.select(&mut self.rng, &self.population, self.config.optimize);
271
272 let mut child = if chance(&mut self.rng, self.config.crossover_rate) {
274 self.problem.crossover(pa, pb, &mut self.rng)
275 } else {
276 pa.clone()
277 };
278
279 if chance(&mut self.rng, self.config.mutation_rate) {
281 self.problem.mutate(&mut child, &mut self.rng);
282 }
283
284 self.problem.repair(&mut child);
286 let fit = self.problem.fitness(&child);
287 next.push(Evaluated { genome: child, fitness: fit });
288 }
289
290 self.population = next;
291 self.generation += 1;
292 }
293
294 pub fn run_for(&mut self, generations: usize) -> RunResult<P::Genome, P::Fitness>
296 where
297 P::Fitness: Into<f64>,
298 {
299 for _ in 0..generations {
300 self.step();
301 }
302 RunResult {
303 generations: self.generation,
304 best: self.best().clone(),
305 average_fitness: self.average_fitness(),
306 }
307 }
308
309 pub fn run_until<F>(
312 &mut self,
313 max_generations: usize,
314 mut stop_when: F,
315 ) -> RunResult<P::Genome, P::Fitness>
316 where
317 F: FnMut(usize, &Evaluated<P::Genome, P::Fitness>) -> bool,
318 P::Fitness: Into<f64>,
319 {
320 for _ in 0..max_generations {
321 if stop_when(self.generation, self.best()) {
322 break;
323 }
324 self.step();
325 }
326 RunResult {
327 generations: self.generation,
328 best: self.best().clone(),
329 average_fitness: self.average_fitness(),
330 }
331 }
332
333 pub fn population(&self) -> &[Evaluated<P::Genome, P::Fitness>] {
335 &self.population
336 }
337
338 pub fn problem_mut(&mut self) -> &mut P {
340 &mut self.problem
341 }
342}
343
344fn clamp01(p: f64) -> f64 {
347 if p.is_nan() {
348 0.0
349 } else if p < 0.0 {
350 0.0
351 } else if p > 1.0 {
352 1.0
353 } else {
354 p
355 }
356}
357
358fn chance<R: Rng + ?Sized>(rng: &mut R, p: f64) -> bool {
359 rng.gen_bool(clamp01(p))
361}
362
363fn cmp_fit<F: PartialOrd>(optimize: Optimize, a: &F, b: &F) -> Ordering {
364 let ord = a.partial_cmp(b).unwrap_or(Ordering::Equal);
366 match optimize {
367 Optimize::Maximize => ord,
368 Optimize::Minimize => ord.reverse(),
369 }
370}
371
372fn best_of<G: Clone, F: PartialOrd>(
373 pop: &[Evaluated<G, F>],
374 optimize: Optimize,
375) -> Option<&Evaluated<G, F>> {
376 pop.iter()
377 .max_by(|a, b| cmp_fit(optimize, &a.fitness, &b.fitness))
378}
379
380fn better_index<G, F: PartialOrd>(
381 pop: &[Evaluated<G, F>],
382 i: usize,
383 j: usize,
384 optimize: Optimize,
385) -> usize {
386 match cmp_fit(optimize, &pop[i].fitness, &pop[j].fitness) {
387 Ordering::Less => j,
388 _ => i,
389 }
390}
391
392pub mod prelude {
394 pub use super::{
395 Builder, Config, Evaluated, GA, Optimize, Problem, Selection, TournamentSelection,
396 };
397}
398