use num_traits::Float;
use rand::distributions::{Distribution, Uniform};
use rand::Rng;
use std::fmt::Debug;
use crate::math::optimization::{ObjectiveFunction, OptimizationConfig, OptimizationResult};
#[derive(Debug, Clone)]
pub struct GeneticConfig<T>
where
T: Float + Debug,
{
pub population_size: usize,
pub mutation_rate: T,
pub crossover_rate: T,
pub tournament_size: usize,
pub lower_bounds: Vec<T>,
pub upper_bounds: Vec<T>,
}
impl<T> Default for GeneticConfig<T>
where
T: Float + Debug,
{
fn default() -> Self {
Self {
population_size: 100,
mutation_rate: T::from(0.1).unwrap(),
crossover_rate: T::from(0.8).unwrap(),
tournament_size: 3,
lower_bounds: vec![T::from(-10.0).unwrap()],
upper_bounds: vec![T::from(10.0).unwrap()],
}
}
}
pub fn minimize<T, F>(
f: &F,
initial_point: &[T],
config: &OptimizationConfig<T>,
ga_config: &GeneticConfig<T>,
) -> OptimizationResult<T>
where
T: Float + Debug,
F: ObjectiveFunction<T>,
{
let n = initial_point.len();
let mut rng = rand::thread_rng();
let mut population = initialize_population(n, ga_config);
let mut fitness = evaluate_population(f, &population);
let mut best_individual = population[0].clone();
let mut best_fitness = fitness[0];
let mut best_history = Vec::new();
let mut iterations = 0;
let mut converged = false;
let convergence_window = 20;
while iterations < config.max_iterations {
let mut new_population = Vec::with_capacity(ga_config.population_size);
let mut new_fitness = Vec::with_capacity(ga_config.population_size);
new_population.push(best_individual.clone());
new_fitness.push(best_fitness);
while new_population.len() < ga_config.population_size {
let parent1 =
tournament_select(&population, &fitness, ga_config.tournament_size, &mut rng);
let parent2 =
tournament_select(&population, &fitness, ga_config.tournament_size, &mut rng);
let (mut child1, mut child2) =
if rng.gen::<f64>() < ga_config.crossover_rate.to_f64().unwrap() {
crossover(&parent1, &parent2, &mut rng)
} else {
(parent1.clone(), parent2.clone())
};
mutate(&mut child1, ga_config, &mut rng);
mutate(&mut child2, ga_config, &mut rng);
let fitness1 = f.evaluate(&child1);
let fitness2 = f.evaluate(&child2);
if fitness1 < best_fitness {
best_fitness = fitness1;
best_individual = child1.clone();
}
if fitness2 < best_fitness {
best_fitness = fitness2;
best_individual = child2.clone();
}
new_population.push(child1);
new_fitness.push(fitness1);
if new_population.len() < ga_config.population_size {
new_population.push(child2);
new_fitness.push(fitness2);
}
}
best_history.push(best_fitness);
let convergence_metric = compute_convergence_metric(&best_history, convergence_window);
if convergence_metric < config.tolerance {
converged = true;
break;
}
population = new_population;
fitness = new_fitness;
iterations += 1;
}
OptimizationResult {
optimal_point: best_individual,
optimal_value: best_fitness,
iterations,
converged,
}
}
fn initialize_population<T>(n: usize, config: &GeneticConfig<T>) -> Vec<Vec<T>>
where
T: Float + Debug,
{
let mut rng = rand::thread_rng();
let mut population = Vec::with_capacity(config.population_size);
for _ in 0..config.population_size {
let mut individual = Vec::with_capacity(n);
for i in 0..n {
let lower = config.lower_bounds[i.min(config.lower_bounds.len() - 1)]
.to_f64()
.unwrap();
let upper = config.upper_bounds[i.min(config.upper_bounds.len() - 1)]
.to_f64()
.unwrap();
let dist = Uniform::new(lower, upper);
individual.push(T::from(dist.sample(&mut rng)).unwrap());
}
population.push(individual);
}
population
}
fn evaluate_population<T, F>(f: &F, population: &[Vec<T>]) -> Vec<T>
where
T: Float + Debug,
F: ObjectiveFunction<T>,
{
population.iter().map(|x| f.evaluate(x)).collect()
}
fn tournament_select<T, R: Rng>(
population: &[Vec<T>],
fitness: &[T],
tournament_size: usize,
rng: &mut R,
) -> Vec<T>
where
T: Float + Debug,
{
let mut best_idx = rng.gen_range(0..population.len());
let mut best_fitness = fitness[best_idx];
for _ in 1..tournament_size {
let idx = rng.gen_range(0..population.len());
if fitness[idx] < best_fitness {
best_idx = idx;
best_fitness = fitness[idx];
}
}
population[best_idx].clone()
}
fn crossover<T, R: Rng>(parent1: &[T], parent2: &[T], rng: &mut R) -> (Vec<T>, Vec<T>)
where
T: Float + Debug,
{
let n = parent1.len();
let mut child1 = Vec::with_capacity(n);
let mut child2 = Vec::with_capacity(n);
for i in 0..n {
let beta = if rng.gen::<bool>() {
T::from(0.25).unwrap()
} else {
T::from(1.75).unwrap()
};
let x1 = parent1[i];
let x2 = parent2[i];
child1.push(beta * x1 + (T::one() - beta) * x2);
child2.push(beta * x2 + (T::one() - beta) * x1);
}
(child1, child2)
}
fn mutate<T, R: Rng>(individual: &mut [T], config: &GeneticConfig<T>, rng: &mut R)
where
T: Float + Debug,
{
individual.iter_mut().enumerate().for_each(|(i, gene)| {
if rng.gen::<f64>() < config.mutation_rate.to_f64().unwrap() {
let lower = config.lower_bounds[i.min(config.lower_bounds.len() - 1)]
.to_f64()
.unwrap();
let upper = config.upper_bounds[i.min(config.upper_bounds.len() - 1)]
.to_f64()
.unwrap();
let range = upper - lower;
let current = gene.to_f64().unwrap();
let step_size = range * 0.1; let gaussian = rand_distr::Normal::new(0.0, step_size).unwrap();
let mutation = gaussian.sample(rng);
let new_value = (current + mutation).max(lower).min(upper);
*gene = T::from(new_value).unwrap();
}
});
}
fn compute_convergence_metric<T>(best_history: &[T], window_size: usize) -> T
where
T: Float + Debug,
{
if best_history.len() < window_size {
return T::max_value();
}
let window = &best_history[best_history.len() - window_size..];
let min_fitness = *window
.iter()
.min_by(|a, b| a.partial_cmp(b).unwrap())
.unwrap();
let max_fitness = *window
.iter()
.max_by(|a, b| a.partial_cmp(b).unwrap())
.unwrap();
max_fitness - min_fitness
}
#[cfg(test)]
mod tests {
use super::*;
struct Quadratic;
impl ObjectiveFunction<f64> for Quadratic {
fn evaluate(&self, point: &[f64]) -> f64 {
point.iter().map(|x| x * x).sum()
}
}
#[test]
fn test_genetic_quadratic() {
let f = Quadratic;
let initial_point = vec![1.0, 1.0];
let config = OptimizationConfig {
max_iterations: 200,
tolerance: 1e-4, learning_rate: 1.0,
};
let ga_config = GeneticConfig {
population_size: 100, mutation_rate: 0.2, crossover_rate: 0.8,
tournament_size: 3,
lower_bounds: vec![-10.0, -10.0],
upper_bounds: vec![10.0, 10.0],
};
let result = minimize(&f, &initial_point, &config, &ga_config);
assert!(result.converged);
assert!(result.optimal_value < 1e-3); for x in result.optimal_point {
assert!(x.abs() < 1e-1); }
}
struct QuadraticWithMinimum;
impl ObjectiveFunction<f64> for QuadraticWithMinimum {
fn evaluate(&self, point: &[f64]) -> f64 {
let x = point[0];
(x - 2.0).powi(2)
}
}
#[test]
fn test_genetic_quadratic_with_minimum() {
let f = QuadraticWithMinimum;
let initial_point = vec![0.0];
let config = OptimizationConfig {
max_iterations: 200,
tolerance: 1e-4, learning_rate: 1.0,
};
let ga_config = GeneticConfig {
population_size: 100, mutation_rate: 0.2, crossover_rate: 0.8,
tournament_size: 3,
lower_bounds: vec![-10.0],
upper_bounds: vec![10.0],
};
let result = minimize(&f, &initial_point, &config, &ga_config);
assert!(result.converged);
assert!((result.optimal_point[0] - 2.0).abs() < 1e-1); assert!(result.optimal_value < 1e-3); }
struct Rosenbrock;
impl ObjectiveFunction<f64> for Rosenbrock {
fn evaluate(&self, point: &[f64]) -> f64 {
let x = point[0];
let y = point[1];
(x - 1.0).powi(2) + 100.0 * (y - x.powi(2)).powi(2)
}
}
#[test]
fn test_genetic_rosenbrock() {
let f = Rosenbrock;
let initial_point = vec![0.0, 0.0];
let config = OptimizationConfig {
max_iterations: 500, tolerance: 1e-4, learning_rate: 1.0,
};
let ga_config = GeneticConfig {
population_size: 200, mutation_rate: 0.2, crossover_rate: 0.8,
tournament_size: 5, lower_bounds: vec![-10.0, -10.0],
upper_bounds: vec![10.0, 10.0],
};
let result = minimize(&f, &initial_point, &config, &ga_config);
assert!(result.converged);
assert!((result.optimal_point[0] - 1.0).abs() < 2e-1); assert!((result.optimal_point[1] - 1.0).abs() < 2e-1); }
}