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 BrkgaConfig {
pub population_size: usize,
pub max_generations: u32,
pub elite_fraction: f64,
pub mutant_fraction: f64,
pub elite_bias: f64,
pub time_limit: Option<Duration>,
pub target_fitness: Option<f64>,
pub stagnation_limit: Option<u32>,
}
impl Default for BrkgaConfig {
fn default() -> Self {
Self {
population_size: 100,
max_generations: 500,
elite_fraction: 0.2, mutant_fraction: 0.15, elite_bias: 0.7, time_limit: None,
target_fitness: None,
stagnation_limit: Some(50),
}
}
}
impl BrkgaConfig {
pub fn new() -> Self {
Self::default()
}
pub fn with_population_size(mut self, size: usize) -> Self {
self.population_size = size.max(4);
self
}
pub fn with_max_generations(mut self, gen: u32) -> Self {
self.max_generations = gen;
self
}
pub fn with_elite_fraction(mut self, fraction: f64) -> Self {
self.elite_fraction = fraction.clamp(0.01, 0.5);
self
}
pub fn with_mutant_fraction(mut self, fraction: f64) -> Self {
self.mutant_fraction = fraction.clamp(0.0, 0.5);
self
}
pub fn with_elite_bias(mut self, bias: f64) -> Self {
self.elite_bias = bias.clamp(0.5, 1.0);
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 fn with_stagnation_limit(mut self, limit: u32) -> Self {
self.stagnation_limit = Some(limit);
self
}
pub fn elite_count(&self) -> usize {
((self.population_size as f64) * self.elite_fraction).ceil() as usize
}
pub fn mutant_count(&self) -> usize {
((self.population_size as f64) * self.mutant_fraction).ceil() as usize
}
}
#[derive(Debug, Clone)]
pub struct RandomKeyChromosome {
pub keys: Vec<f64>,
fitness: f64,
}
impl RandomKeyChromosome {
pub fn new(num_keys: usize) -> Self {
Self {
keys: vec![0.0; num_keys],
fitness: f64::NEG_INFINITY,
}
}
pub fn random<R: Rng>(num_keys: usize, rng: &mut R) -> Self {
let keys: Vec<f64> = (0..num_keys).map(|_| rng.random::<f64>()).collect();
Self {
keys,
fitness: f64::NEG_INFINITY,
}
}
pub fn len(&self) -> usize {
self.keys.len()
}
pub fn is_empty(&self) -> bool {
self.keys.is_empty()
}
pub fn fitness(&self) -> f64 {
self.fitness
}
pub fn set_fitness(&mut self, fitness: f64) {
self.fitness = fitness;
}
pub fn biased_crossover<R: Rng>(&self, other: &Self, elite_bias: f64, rng: &mut R) -> Self {
let keys: Vec<f64> = self
.keys
.iter()
.zip(&other.keys)
.map(|(&elite_key, &non_elite_key)| {
if rng.random::<f64>() < elite_bias {
elite_key
} else {
non_elite_key
}
})
.collect();
Self {
keys,
fitness: f64::NEG_INFINITY,
}
}
pub fn decode_as_permutation(&self) -> Vec<usize> {
let mut indices: Vec<usize> = (0..self.keys.len()).collect();
indices.sort_by(|&a, &b| {
self.keys[a]
.partial_cmp(&self.keys[b])
.unwrap_or(std::cmp::Ordering::Equal)
});
indices
}
pub fn decode_as_discrete(&self, key_idx: usize, num_options: usize) -> usize {
if key_idx >= self.keys.len() || num_options == 0 {
return 0;
}
let key = self.keys[key_idx].clamp(0.0, 0.9999999);
(key * num_options as f64) as usize
}
}
pub trait BrkgaProblem: Send + Sync {
fn num_keys(&self) -> usize;
fn evaluate(&self, chromosome: &mut RandomKeyChromosome);
fn evaluate_parallel(&self, chromosomes: &mut [RandomKeyChromosome]) {
#[cfg(feature = "parallel")]
chromosomes.par_iter_mut().for_each(|c| {
self.evaluate(c);
});
#[cfg(not(feature = "parallel"))]
for c in chromosomes.iter_mut() {
self.evaluate(c);
}
}
fn on_generation(
&self,
_generation: u32,
_best: &RandomKeyChromosome,
_population: &[RandomKeyChromosome],
) {
}
}
#[derive(Debug, Clone)]
pub struct BrkgaResult {
pub best: RandomKeyChromosome,
pub generations: u32,
pub elapsed: Duration,
pub target_reached: bool,
pub history: Vec<f64>,
}
#[derive(Debug, Clone)]
pub struct BrkgaProgress {
pub generation: u32,
pub max_generations: u32,
pub best_fitness: f64,
pub avg_fitness: f64,
pub elapsed: Duration,
pub running: bool,
}
pub struct BrkgaRunner<P: BrkgaProblem> {
config: BrkgaConfig,
problem: P,
cancelled: Arc<AtomicBool>,
}
impl<P: BrkgaProblem> BrkgaRunner<P> {
pub fn new(config: BrkgaConfig, problem: P) -> Self {
Self {
config,
problem,
cancelled: Arc::new(AtomicBool::new(false)),
}
}
pub fn with_cancellation(config: BrkgaConfig, problem: P, cancelled: Arc<AtomicBool>) -> Self {
Self {
config,
problem,
cancelled,
}
}
pub fn cancel_handle(&self) -> Arc<AtomicBool> {
self.cancelled.clone()
}
pub fn run(&self) -> BrkgaResult {
self.run_with_rng(&mut rand::rng())
}
pub fn run_with_progress<F>(&self, progress_callback: F) -> BrkgaResult
where
F: Fn(BrkgaProgress),
{
self.run_with_rng_and_progress(&mut rand::rng(), Some(progress_callback))
}
pub fn run_with_rng<R: Rng>(&self, rng: &mut R) -> BrkgaResult {
self.run_with_rng_and_progress::<R, fn(BrkgaProgress)>(rng, None)
}
pub fn run_with_rng_and_progress<R: Rng, F>(
&self,
rng: &mut R,
progress_callback: Option<F>,
) -> BrkgaResult
where
F: Fn(BrkgaProgress),
{
let start = Timer::now();
let mut history = Vec::new();
let num_keys = self.problem.num_keys();
let mut population: Vec<RandomKeyChromosome> = (0..self.config.population_size)
.map(|_| RandomKeyChromosome::random(num_keys, rng))
.collect();
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 = best.fitness();
let mut stagnation_count = 0u32;
let mut generation = 0u32;
let mut target_reached = false;
let elite_count = self.config.elite_count();
let mutant_count = self.config.mutant_count();
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 elite in population.iter().take(elite_count) {
new_population.push(elite.clone());
}
let mut mutants: Vec<RandomKeyChromosome> = (0..mutant_count)
.map(|_| RandomKeyChromosome::random(num_keys, rng))
.collect();
let crossover_count = self.config.population_size - elite_count - mutant_count;
let mut children: Vec<RandomKeyChromosome> = (0..crossover_count)
.map(|_| {
let elite_idx = rng.random_range(0..elite_count);
let elite_parent = &population[elite_idx];
let non_elite_idx = rng.random_range(elite_count..population.len());
let non_elite_parent = &population[non_elite_idx];
elite_parent.biased_crossover(non_elite_parent, self.config.elite_bias, rng)
})
.collect();
self.problem.evaluate_parallel(&mut mutants);
self.problem.evaluate_parallel(&mut children);
new_population.extend(mutants);
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 = new_population[0].fitness();
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(|c| c.fitness()).sum::<f64>()
/ new_population.len() as f64;
callback(BrkgaProgress {
generation,
max_generations: self.config.max_generations,
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(|c| c.fitness()).sum::<f64>()
/ population.len().max(1) as f64;
callback(BrkgaProgress {
generation,
max_generations: self.config.max_generations,
best_fitness,
avg_fitness,
elapsed: start.elapsed(),
running: false,
});
}
BrkgaResult {
best,
generations: generation,
elapsed: start.elapsed(),
target_reached,
history,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
struct MaxSumProblem {
num_keys: usize,
}
impl BrkgaProblem for MaxSumProblem {
fn num_keys(&self) -> usize {
self.num_keys
}
fn evaluate(&self, chromosome: &mut RandomKeyChromosome) {
let sum: f64 = chromosome.keys.iter().sum();
chromosome.set_fitness(sum);
}
}
#[test]
fn test_brkga_basic() {
let config = BrkgaConfig::default()
.with_population_size(50)
.with_max_generations(50);
let problem = MaxSumProblem { num_keys: 10 };
let runner = BrkgaRunner::new(config, problem);
let result = runner.run();
assert!(result.best.fitness() > 5.0);
}
#[test]
fn test_random_key_chromosome() {
let mut rng = rand::rng();
let chromosome = RandomKeyChromosome::random(10, &mut rng);
assert_eq!(chromosome.len(), 10);
for &key in &chromosome.keys {
assert!((0.0..1.0).contains(&key));
}
}
#[test]
fn test_biased_crossover() {
let mut rng = rand::rng();
let elite = RandomKeyChromosome::random(10, &mut rng);
let non_elite = RandomKeyChromosome::random(10, &mut rng);
let child = elite.biased_crossover(&non_elite, 0.7, &mut rng);
assert_eq!(child.len(), 10);
for &key in &child.keys {
assert!((0.0..1.0).contains(&key));
}
}
#[test]
fn test_decode_as_permutation() {
let mut chromosome = RandomKeyChromosome::new(5);
chromosome.keys = vec![0.3, 0.1, 0.9, 0.5, 0.2];
let perm = chromosome.decode_as_permutation();
assert_eq!(perm, vec![1, 4, 0, 3, 2]);
}
#[test]
fn test_decode_as_discrete() {
let mut chromosome = RandomKeyChromosome::new(3);
chromosome.keys = vec![0.0, 0.5, 0.99];
assert_eq!(chromosome.decode_as_discrete(0, 6), 0);
assert_eq!(chromosome.decode_as_discrete(1, 6), 3);
assert_eq!(chromosome.decode_as_discrete(2, 6), 5);
}
}