1use rand::prelude::*;
20#[cfg(feature = "parallel")]
21use rayon::prelude::*;
22use std::sync::atomic::{AtomicBool, Ordering};
23use std::sync::Arc;
24use std::time::Duration;
25
26use crate::error::{check_at_least, check_range, Result};
27use crate::result::StopReason;
28use crate::timing::{evaluate_within, expired, Timer};
29
30#[cfg(feature = "serde")]
31use serde::{Deserialize, Serialize};
32
33#[derive(Debug, Clone)]
35#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
36pub struct GaConfig {
37 pub population_size: usize,
39 pub max_generations: u32,
41 pub crossover_rate: f64,
43 pub mutation_rate: f64,
45 pub elite_count: usize,
47 pub tournament_size: usize,
49 pub time_limit: Option<Duration>,
51 pub target_fitness: Option<f64>,
53 pub stagnation_limit: Option<u32>,
55}
56
57impl Default for GaConfig {
58 fn default() -> Self {
59 Self {
60 population_size: 100,
61 max_generations: 500,
62 crossover_rate: 0.85,
63 mutation_rate: 0.05,
64 elite_count: 5,
65 tournament_size: 3,
66 time_limit: None,
67 target_fitness: None,
68 stagnation_limit: Some(50),
69 }
70 }
71}
72
73impl GaConfig {
74 pub fn new() -> Self {
76 Self::default()
77 }
78
79 pub fn with_population_size(mut self, size: usize) -> Self {
81 self.population_size = size;
82 self
83 }
84
85 pub fn with_max_generations(mut self, gen: u32) -> Self {
87 self.max_generations = gen;
88 self
89 }
90
91 pub fn with_crossover_rate(mut self, rate: f64) -> Self {
93 self.crossover_rate = rate;
94 self
95 }
96
97 pub fn with_mutation_rate(mut self, rate: f64) -> Self {
99 self.mutation_rate = rate;
100 self
101 }
102
103 pub fn with_elite_count(mut self, count: usize) -> Self {
105 self.elite_count = count;
106 self
107 }
108
109 pub fn with_time_limit(mut self, duration: Duration) -> Self {
111 self.time_limit = Some(duration);
112 self
113 }
114
115 pub fn with_target_fitness(mut self, fitness: f64) -> Self {
117 self.target_fitness = Some(fitness);
118 self
119 }
120
121 pub fn validate(&self) -> Result<()> {
128 check_range("crossover_rate", self.crossover_rate, 0.0, 1.0)?;
129 check_range("mutation_rate", self.mutation_rate, 0.0, 1.0)?;
130 check_at_least("population_size", self.population_size as f64, 2.0)?;
131 check_at_least("tournament_size", self.tournament_size as f64, 1.0)?;
132 Ok(())
133 }
134}
135
136pub trait Individual: Clone + Send + Sync {
141 type Fitness: PartialOrd + Copy + Send;
143
144 fn fitness(&self) -> Self::Fitness;
146
147 fn random<R: Rng>(rng: &mut R) -> Self;
149
150 fn crossover<R: Rng>(&self, other: &Self, rng: &mut R) -> Self;
152
153 fn mutate<R: Rng>(&mut self, rng: &mut R);
155}
156
157pub trait GaProblem: Send + Sync {
162 type Individual: Individual;
164
165 fn evaluate(&self, individual: &mut Self::Individual);
167
168 fn evaluate_parallel(&self, individuals: &mut [Self::Individual]) {
171 #[cfg(feature = "parallel")]
172 individuals.par_iter_mut().for_each(|ind| {
173 self.evaluate(ind);
174 });
175 #[cfg(not(feature = "parallel"))]
176 for ind in individuals.iter_mut() {
177 self.evaluate(ind);
178 }
179 }
180
181 fn initialize_population<R: Rng>(&self, size: usize, rng: &mut R) -> Vec<Self::Individual> {
183 (0..size).map(|_| Self::Individual::random(rng)).collect()
184 }
185
186 fn on_generation(
188 &self,
189 _generation: u32,
190 _best: &Self::Individual,
191 _population: &[Self::Individual],
192 ) {
193 }
195}
196
197#[derive(Debug, Clone)]
199pub struct GaProgress<F> {
200 pub generation: u32,
202 pub max_generations: u32,
204 pub best_fitness: F,
206 pub avg_fitness: f64,
208 pub elapsed: Duration,
210 pub running: bool,
212}
213
214#[derive(Debug, Clone)]
216pub struct GaResult<I: Individual> {
217 pub best: I,
219 pub generations: u32,
221 pub elapsed: Duration,
223 pub stop_reason: StopReason,
225 pub history: Vec<f64>,
227}
228
229pub struct GaRunner<P: GaProblem> {
234 config: GaConfig,
235 problem: P,
236 cancelled: Arc<AtomicBool>,
237}
238
239impl<P: GaProblem> GaRunner<P>
240where
241 <P::Individual as Individual>::Fitness: Into<f64>,
242{
243 pub fn new(config: GaConfig, problem: P) -> Result<Self> {
248 Self::with_cancellation(config, problem, Arc::new(AtomicBool::new(false)))
249 }
250
251 pub fn with_cancellation(
257 config: GaConfig,
258 problem: P,
259 cancelled: Arc<AtomicBool>,
260 ) -> Result<Self> {
261 config.validate()?;
262 Ok(Self {
263 config,
264 problem,
265 cancelled,
266 })
267 }
268
269 pub fn cancel_handle(&self) -> Arc<AtomicBool> {
271 self.cancelled.clone()
272 }
273
274 pub fn run(&self) -> GaResult<P::Individual> {
276 self.run_with_rng(&mut rand::rng())
277 }
278
279 pub fn run_with_progress<F>(&self, progress_callback: F) -> GaResult<P::Individual>
281 where
282 F: Fn(GaProgress<<P::Individual as Individual>::Fitness>),
283 {
284 self.run_with_rng_and_progress(&mut rand::rng(), Some(progress_callback))
285 }
286
287 pub fn run_with_rng<R: Rng>(&self, rng: &mut R) -> GaResult<P::Individual> {
289 self.run_with_rng_and_progress::<R, fn(GaProgress<<P::Individual as Individual>::Fitness>)>(
290 rng, None,
291 )
292 }
293
294 pub fn run_with_rng_and_progress<R: Rng, F>(
296 &self,
297 rng: &mut R,
298 progress_callback: Option<F>,
299 ) -> GaResult<P::Individual>
300 where
301 F: Fn(GaProgress<<P::Individual as Individual>::Fitness>),
302 {
303 let start = Timer::now();
304 let mut history = Vec::new();
305
306 let heartbeat = |generation: u32,
311 best: Option<<P::Individual as Individual>::Fitness>,
312 batch: &[P::Individual]| {
313 let best = batch
314 .iter()
315 .map(Individual::fitness)
316 .fold(best, |b, f| match b {
317 Some(b) if b >= f => Some(b),
318 _ => Some(f),
319 });
320 if let (Some(callback), Some(best_fitness)) = (&progress_callback, best) {
321 let avg_fitness = batch.iter().map(|i| i.fitness().into()).sum::<f64>()
322 / batch.len().max(1) as f64;
323 callback(GaProgress {
324 generation,
325 max_generations: self.config.max_generations,
326 best_fitness,
327 avg_fitness,
328 elapsed: start.elapsed(),
329 running: true,
330 });
331 }
332 best
333 };
334
335 let mut population = self
337 .problem
338 .initialize_population(self.config.population_size, rng);
339
340 let mut best_so_far = None;
342 evaluate_within(&mut population, &start, self.config.time_limit, |batch| {
343 self.problem.evaluate_parallel(batch);
344 best_so_far = heartbeat(0, best_so_far, batch);
345 });
346
347 population.sort_by(|a, b| {
349 b.fitness()
350 .partial_cmp(&a.fitness())
351 .unwrap_or(std::cmp::Ordering::Equal)
352 });
353
354 let mut best = population[0].clone();
355 let mut best_fitness: f64 = best.fitness().into();
356 let mut stagnation_count = 0u32;
357 let mut generation = 0u32;
358 let reached = |fitness: f64| self.config.target_fitness.is_some_and(|t| fitness >= t);
359
360 let stop_reason = loop {
364 if reached(best_fitness) {
365 break StopReason::TargetReached;
366 }
367 if generation >= self.config.max_generations {
368 break StopReason::IterationLimit;
369 }
370 if self.cancelled.load(Ordering::Relaxed) {
371 break StopReason::Cancelled;
372 }
373 if expired(&start, self.config.time_limit) {
374 break StopReason::TimeLimit;
375 }
376
377 history.push(best_fitness);
379
380 let mut new_population = Vec::with_capacity(self.config.population_size);
382
383 for individual in population
385 .iter()
386 .take(self.config.elite_count.min(population.len()))
387 {
388 new_population.push(individual.clone());
389 }
390
391 let mut children: Vec<P::Individual> =
393 Vec::with_capacity(self.config.population_size - new_population.len());
394
395 while children.len() < self.config.population_size - new_population.len() {
396 let parent1 = self.tournament_select(&population, rng);
398 let parent2 = self.tournament_select(&population, rng);
399
400 let mut child = if rng.random::<f64>() < self.config.crossover_rate {
402 parent1.crossover(parent2, rng)
403 } else {
404 parent1.clone()
405 };
406
407 if rng.random::<f64>() < self.config.mutation_rate {
409 child.mutate(rng);
410 }
411
412 children.push(child);
413 }
414
415 let best_now = best.fitness();
418 evaluate_within(&mut children, &start, self.config.time_limit, |batch| {
419 self.problem.evaluate_parallel(batch);
420 heartbeat(generation, Some(best_now), batch);
421 });
422
423 new_population.extend(children);
425
426 new_population.sort_by(|a, b| {
428 b.fitness()
429 .partial_cmp(&a.fitness())
430 .unwrap_or(std::cmp::Ordering::Equal)
431 });
432
433 let new_best_fitness: f64 = new_population[0].fitness().into();
435 if new_best_fitness > best_fitness {
436 best = new_population[0].clone();
437 best_fitness = new_best_fitness;
438 stagnation_count = 0;
439 } else {
440 stagnation_count += 1;
441 }
442
443 if let Some(limit) = self.config.stagnation_limit {
445 if stagnation_count >= limit {
446 break StopReason::NoImprovement;
447 }
448 }
449
450 self.problem
452 .on_generation(generation, &best, &new_population);
453
454 if let Some(ref callback) = progress_callback {
456 let avg_fitness = new_population
457 .iter()
458 .map(|ind| ind.fitness().into())
459 .sum::<f64>()
460 / new_population.len() as f64;
461
462 callback(GaProgress {
463 generation,
464 max_generations: self.config.max_generations,
465 best_fitness: best.fitness(),
466 avg_fitness,
467 elapsed: start.elapsed(),
468 running: true,
469 });
470 }
471
472 population = new_population;
473 generation += 1;
474 };
475
476 history.push(best_fitness);
478
479 if let Some(ref callback) = progress_callback {
481 let avg_fitness = population
482 .iter()
483 .map(|ind| ind.fitness().into())
484 .sum::<f64>()
485 / population.len().max(1) as f64;
486
487 callback(GaProgress {
488 generation,
489 max_generations: self.config.max_generations,
490 best_fitness: best.fitness(),
491 avg_fitness,
492 elapsed: start.elapsed(),
493 running: false,
494 });
495 }
496
497 GaResult {
498 best,
499 generations: generation,
500 elapsed: start.elapsed(),
501 stop_reason,
502 history,
503 }
504 }
505
506 fn tournament_select<'a, R: Rng>(
508 &self,
509 population: &'a [P::Individual],
510 rng: &mut R,
511 ) -> &'a P::Individual {
512 let mut best_idx = rng.random_range(0..population.len());
513
514 for _ in 1..self.config.tournament_size {
515 let idx = rng.random_range(0..population.len());
516 if population[idx].fitness() > population[best_idx].fitness() {
517 best_idx = idx;
518 }
519 }
520
521 &population[best_idx]
522 }
523}
524
525#[cfg(test)]
526mod tests {
527 use super::*;
528
529 #[derive(Clone)]
530 struct SimpleIndividual {
531 value: f64,
532 }
533
534 impl Individual for SimpleIndividual {
535 type Fitness = f64;
536
537 fn fitness(&self) -> f64 {
538 -self.value * self.value
540 }
541
542 fn random<R: Rng>(rng: &mut R) -> Self {
543 Self {
544 value: rng.random_range(-100.0..100.0),
545 }
546 }
547
548 fn crossover<R: Rng>(&self, other: &Self, rng: &mut R) -> Self {
549 Self {
550 value: if rng.random() {
551 self.value
552 } else {
553 other.value
554 },
555 }
556 }
557
558 fn mutate<R: Rng>(&mut self, rng: &mut R) {
559 self.value += rng.random_range(-10.0..10.0);
560 }
561 }
562
563 struct SimpleProblem;
564
565 impl GaProblem for SimpleProblem {
566 type Individual = SimpleIndividual;
567
568 fn evaluate(&self, _individual: &mut Self::Individual) {
569 }
571 }
572
573 #[test]
574 fn test_ga_basic() {
575 let config = GaConfig::default()
576 .with_population_size(50)
577 .with_max_generations(100)
578 .with_target_fitness(-0.01);
579
580 let runner = GaRunner::new(config, SimpleProblem).expect("valid config");
581 let result = runner.run();
582
583 assert!(result.best.value.abs() < 5.0);
585 }
586}