use rand::prelude::*;
#[cfg(feature = "parallel")]
use rayon::prelude::*;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::time::Duration;
use crate::timing::Timer;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct GaConfig {
pub population_size: usize,
pub max_generations: u32,
pub crossover_rate: f64,
pub mutation_rate: f64,
pub elite_count: usize,
pub tournament_size: usize,
pub time_limit: Option<Duration>,
pub target_fitness: Option<f64>,
pub stagnation_limit: Option<u32>,
}
impl Default for GaConfig {
fn default() -> Self {
Self {
population_size: 100,
max_generations: 500,
crossover_rate: 0.85,
mutation_rate: 0.05,
elite_count: 5,
tournament_size: 3,
time_limit: None,
target_fitness: None,
stagnation_limit: Some(50),
}
}
}
impl GaConfig {
pub fn new() -> Self {
Self::default()
}
pub fn with_population_size(mut self, size: usize) -> Self {
self.population_size = size.max(2);
self
}
pub fn with_max_generations(mut self, gen: u32) -> Self {
self.max_generations = gen;
self
}
pub fn with_crossover_rate(mut self, rate: f64) -> Self {
self.crossover_rate = rate.clamp(0.0, 1.0);
self
}
pub fn with_mutation_rate(mut self, rate: f64) -> Self {
self.mutation_rate = rate.clamp(0.0, 1.0);
self
}
pub fn with_elite_count(mut self, count: usize) -> Self {
self.elite_count = count;
self
}
pub fn with_time_limit(mut self, duration: Duration) -> Self {
self.time_limit = Some(duration);
self
}
pub fn with_target_fitness(mut self, fitness: f64) -> Self {
self.target_fitness = Some(fitness);
self
}
}
pub trait Individual: Clone + Send + Sync {
type Fitness: PartialOrd + Copy + Send;
fn fitness(&self) -> Self::Fitness;
fn random<R: Rng>(rng: &mut R) -> Self;
fn crossover<R: Rng>(&self, other: &Self, rng: &mut R) -> Self;
fn mutate<R: Rng>(&mut self, rng: &mut R);
}
pub trait GaProblem: Send + Sync {
type Individual: Individual;
fn evaluate(&self, individual: &mut Self::Individual);
fn evaluate_parallel(&self, individuals: &mut [Self::Individual]) {
#[cfg(feature = "parallel")]
individuals.par_iter_mut().for_each(|ind| {
self.evaluate(ind);
});
#[cfg(not(feature = "parallel"))]
for ind in individuals.iter_mut() {
self.evaluate(ind);
}
}
fn initialize_population<R: Rng>(&self, size: usize, rng: &mut R) -> Vec<Self::Individual> {
(0..size).map(|_| Self::Individual::random(rng)).collect()
}
fn on_generation(
&self,
_generation: u32,
_best: &Self::Individual,
_population: &[Self::Individual],
) {
}
}
#[derive(Debug, Clone)]
pub struct GaProgress<F> {
pub generation: u32,
pub max_generations: u32,
pub best_fitness: F,
pub avg_fitness: f64,
pub elapsed: Duration,
pub running: bool,
}
#[derive(Debug, Clone)]
pub struct GaResult<I: Individual> {
pub best: I,
pub generations: u32,
pub elapsed: Duration,
pub target_reached: bool,
pub history: Vec<f64>,
}
pub struct GaRunner<P: GaProblem> {
config: GaConfig,
problem: P,
cancelled: Arc<AtomicBool>,
}
impl<P: GaProblem> GaRunner<P>
where
<P::Individual as Individual>::Fitness: Into<f64>,
{
pub fn new(config: GaConfig, problem: P) -> Self {
Self {
config,
problem,
cancelled: Arc::new(AtomicBool::new(false)),
}
}
pub fn cancel_handle(&self) -> Arc<AtomicBool> {
self.cancelled.clone()
}
pub fn run(&self) -> GaResult<P::Individual> {
self.run_with_rng(&mut rand::rng())
}
pub fn run_with_progress<F>(&self, progress_callback: F) -> GaResult<P::Individual>
where
F: Fn(GaProgress<<P::Individual as Individual>::Fitness>),
{
self.run_with_rng_and_progress(&mut rand::rng(), Some(progress_callback))
}
pub fn run_with_rng<R: Rng>(&self, rng: &mut R) -> GaResult<P::Individual> {
self.run_with_rng_and_progress::<R, fn(GaProgress<<P::Individual as Individual>::Fitness>)>(
rng, None,
)
}
pub fn run_with_rng_and_progress<R: Rng, F>(
&self,
rng: &mut R,
progress_callback: Option<F>,
) -> GaResult<P::Individual>
where
F: Fn(GaProgress<<P::Individual as Individual>::Fitness>),
{
let start = Timer::now();
let mut history = Vec::new();
let mut population = self
.problem
.initialize_population(self.config.population_size, rng);
self.problem.evaluate_parallel(&mut population);
population.sort_by(|a, b| {
b.fitness()
.partial_cmp(&a.fitness())
.unwrap_or(std::cmp::Ordering::Equal)
});
let mut best = population[0].clone();
let mut best_fitness: f64 = best.fitness().into();
let mut stagnation_count = 0u32;
let mut generation = 0u32;
let mut target_reached = false;
while generation < self.config.max_generations {
if self.cancelled.load(Ordering::Relaxed) {
break;
}
if let Some(limit) = self.config.time_limit {
if start.elapsed() > limit {
break;
}
}
if let Some(target) = self.config.target_fitness {
if best_fitness >= target {
target_reached = true;
break;
}
}
history.push(best_fitness);
let mut new_population = Vec::with_capacity(self.config.population_size);
for individual in population
.iter()
.take(self.config.elite_count.min(population.len()))
{
new_population.push(individual.clone());
}
let mut children: Vec<P::Individual> =
Vec::with_capacity(self.config.population_size - new_population.len());
while children.len() < self.config.population_size - new_population.len() {
let parent1 = self.tournament_select(&population, rng);
let parent2 = self.tournament_select(&population, rng);
let mut child = if rng.random::<f64>() < self.config.crossover_rate {
parent1.crossover(parent2, rng)
} else {
parent1.clone()
};
if rng.random::<f64>() < self.config.mutation_rate {
child.mutate(rng);
}
children.push(child);
}
self.problem.evaluate_parallel(&mut children);
new_population.extend(children);
new_population.sort_by(|a, b| {
b.fitness()
.partial_cmp(&a.fitness())
.unwrap_or(std::cmp::Ordering::Equal)
});
let new_best_fitness: f64 = new_population[0].fitness().into();
if new_best_fitness > best_fitness {
best = new_population[0].clone();
best_fitness = new_best_fitness;
stagnation_count = 0;
} else {
stagnation_count += 1;
}
if let Some(limit) = self.config.stagnation_limit {
if stagnation_count >= limit {
break;
}
}
self.problem
.on_generation(generation, &best, &new_population);
if let Some(ref callback) = progress_callback {
let avg_fitness = new_population
.iter()
.map(|ind| ind.fitness().into())
.sum::<f64>()
/ new_population.len() as f64;
callback(GaProgress {
generation,
max_generations: self.config.max_generations,
best_fitness: best.fitness(),
avg_fitness,
elapsed: start.elapsed(),
running: true,
});
}
population = new_population;
generation += 1;
}
history.push(best_fitness);
if let Some(ref callback) = progress_callback {
let avg_fitness = population
.iter()
.map(|ind| ind.fitness().into())
.sum::<f64>()
/ population.len().max(1) as f64;
callback(GaProgress {
generation,
max_generations: self.config.max_generations,
best_fitness: best.fitness(),
avg_fitness,
elapsed: start.elapsed(),
running: false,
});
}
GaResult {
best,
generations: generation,
elapsed: start.elapsed(),
target_reached,
history,
}
}
fn tournament_select<'a, R: Rng>(
&self,
population: &'a [P::Individual],
rng: &mut R,
) -> &'a P::Individual {
let mut best_idx = rng.random_range(0..population.len());
for _ in 1..self.config.tournament_size {
let idx = rng.random_range(0..population.len());
if population[idx].fitness() > population[best_idx].fitness() {
best_idx = idx;
}
}
&population[best_idx]
}
}
#[derive(Debug, Clone)]
pub struct PermutationChromosome {
pub genes: Vec<usize>,
pub rotations: Vec<usize>,
fitness: f64,
}
impl PermutationChromosome {
pub fn new(size: usize, _rotation_options: usize) -> Self {
Self {
genes: (0..size).collect(),
rotations: vec![0; size],
fitness: f64::NEG_INFINITY,
}
}
pub fn random_with_options<R: Rng>(size: usize, rotation_options: usize, rng: &mut R) -> Self {
let mut genes: Vec<usize> = (0..size).collect();
genes.shuffle(rng);
let rotations: Vec<usize> = (0..size)
.map(|_| rng.random_range(0..rotation_options.max(1)))
.collect();
Self {
genes,
rotations,
fitness: f64::NEG_INFINITY,
}
}
pub fn set_fitness(&mut self, fitness: f64) {
self.fitness = fitness;
}
pub fn len(&self) -> usize {
self.genes.len()
}
pub fn is_empty(&self) -> bool {
self.genes.is_empty()
}
pub fn order_crossover<R: Rng>(&self, other: &Self, rng: &mut R) -> Self {
let n = self.genes.len();
if n < 2 {
return self.clone();
}
let (mut p1, mut p2) = (rng.random_range(0..n), rng.random_range(0..n));
if p1 > p2 {
std::mem::swap(&mut p1, &mut p2);
}
let mut child_genes = vec![usize::MAX; n];
let mut used = vec![false; n];
for i in p1..=p2 {
child_genes[i] = self.genes[i];
used[self.genes[i]] = true;
}
let mut j = (p2 + 1) % n;
for i in 0..n {
let idx = (p2 + 1 + i) % n;
if child_genes[idx] == usize::MAX {
while used[other.genes[j]] {
j = (j + 1) % n;
}
child_genes[idx] = other.genes[j];
used[other.genes[j]] = true;
j = (j + 1) % n;
}
}
let rotations: Vec<usize> = self
.rotations
.iter()
.zip(&other.rotations)
.map(|(a, b)| if rng.random() { *a } else { *b })
.collect();
Self {
genes: child_genes,
rotations,
fitness: f64::NEG_INFINITY,
}
}
pub fn swap_mutate<R: Rng>(&mut self, rng: &mut R) {
if self.genes.len() < 2 {
return;
}
let i = rng.random_range(0..self.genes.len());
let j = rng.random_range(0..self.genes.len());
self.genes.swap(i, j);
self.fitness = f64::NEG_INFINITY;
}
pub fn rotation_mutate<R: Rng>(&mut self, rotation_options: usize, rng: &mut R) {
if self.rotations.is_empty() || rotation_options <= 1 {
return;
}
let idx = rng.random_range(0..self.rotations.len());
self.rotations[idx] = rng.random_range(0..rotation_options);
self.fitness = f64::NEG_INFINITY;
}
pub fn inversion_mutate<R: Rng>(&mut self, rng: &mut R) {
let n = self.genes.len();
if n < 2 {
return;
}
let (mut p1, mut p2) = (rng.random_range(0..n), rng.random_range(0..n));
if p1 > p2 {
std::mem::swap(&mut p1, &mut p2);
}
self.genes[p1..=p2].reverse();
self.fitness = f64::NEG_INFINITY;
}
}
impl Individual for PermutationChromosome {
type Fitness = f64;
fn fitness(&self) -> f64 {
self.fitness
}
fn random<R: Rng>(rng: &mut R) -> Self {
Self::random_with_options(0, 1, rng)
}
fn crossover<R: Rng>(&self, other: &Self, rng: &mut R) -> Self {
self.order_crossover(other, rng)
}
fn mutate<R: Rng>(&mut self, rng: &mut R) {
if rng.random::<f64>() < 0.7 {
self.swap_mutate(rng);
} else {
self.inversion_mutate(rng);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[derive(Clone)]
struct SimpleIndividual {
value: f64,
}
impl Individual for SimpleIndividual {
type Fitness = f64;
fn fitness(&self) -> f64 {
-self.value * self.value
}
fn random<R: Rng>(rng: &mut R) -> Self {
Self {
value: rng.random_range(-100.0..100.0),
}
}
fn crossover<R: Rng>(&self, other: &Self, rng: &mut R) -> Self {
Self {
value: if rng.random() {
self.value
} else {
other.value
},
}
}
fn mutate<R: Rng>(&mut self, rng: &mut R) {
self.value += rng.random_range(-10.0..10.0);
}
}
struct SimpleProblem;
impl GaProblem for SimpleProblem {
type Individual = SimpleIndividual;
fn evaluate(&self, _individual: &mut Self::Individual) {
}
}
#[test]
fn test_ga_basic() {
let config = GaConfig::default()
.with_population_size(50)
.with_max_generations(100)
.with_target_fitness(-0.01);
let runner = GaRunner::new(config, SimpleProblem);
let result = runner.run();
assert!(result.best.value.abs() < 5.0);
}
#[test]
fn test_permutation_crossover() {
let mut rng = rand::rng();
let parent1 = PermutationChromosome::random_with_options(10, 4, &mut rng);
let parent2 = PermutationChromosome::random_with_options(10, 4, &mut rng);
let child = parent1.order_crossover(&parent2, &mut rng);
assert_eq!(child.genes.len(), 10);
let mut sorted = child.genes.clone();
sorted.sort();
assert_eq!(sorted, (0..10).collect::<Vec<_>>());
}
#[test]
fn test_permutation_mutation() {
let mut rng = rand::rng();
let mut chromosome = PermutationChromosome::random_with_options(10, 4, &mut rng);
chromosome.swap_mutate(&mut rng);
let mut sorted = chromosome.genes.clone();
sorted.sort();
assert_eq!(sorted, (0..10).collect::<Vec<_>>());
}
}