genetic_algorithms 2.2.0

Library for solving genetic algorithm problems
Documentation
//! Boltzmann selection operator.
//!
//! Uses a temperature parameter inspired by statistical mechanics to
//! control selective pressure. High temperatures yield nearly uniform
//! selection (exploration); low temperatures strongly favor the fittest
//! (exploitation). Lowering the temperature over generations produces a
//! simulated-annealing effect.

use crate::traits::ChromosomeT;
use log::{debug, trace};
use rand::Rng;

/// Boltzmann selection: uses a temperature parameter to control selective pressure,
/// inspired by the Boltzmann probability distribution from statistical mechanics.
///
/// Each individual's selection probability is proportional to `exp(fitness / temperature)`.
/// - **High temperature** flattens the distribution, making selection nearly uniform
///   (exploration).
/// - **Low temperature** sharpens the distribution, strongly favoring high-fitness
///   individuals (exploitation).
///
/// This allows the algorithm to start with broad exploration and gradually increase
/// selective pressure by lowering the temperature (simulated-annealing style).
///
/// # Arguments
///
/// * `chromosomes` - Population to select from.
/// * `couples` - Number of parent pairs to produce.
/// * `temperature` - Controls selective pressure. If `<= 0.0`, defaults to `1.0`.
///
/// # Returns
///
/// A vector of `(usize, usize)` parent index pairs. Returns an empty vector if the
/// population has fewer than 2 individuals.
pub fn boltzmann_selection<U: ChromosomeT>(
    chromosomes: &[U],
    couples: usize,
    temperature: f64,
) -> Vec<(usize, usize)> {
    debug!(target="selection_events", method="boltzmann"; "Starting Boltzmann selection");

    let n = chromosomes.len();
    if n < 2 {
        debug!(target="selection_events", method="boltzmann"; "Population too small ({}), returning empty", n);
        return Vec::new();
    }

    let temp = if temperature <= 0.0 {
        debug!(target="selection_events", method="boltzmann"; "Temperature {} <= 0.0, using fallback 1.0", temperature);
        1.0
    } else {
        temperature
    };

    // Compute raw Boltzmann weights: exp(fitness_i / temperature).
    // To avoid overflow we subtract the maximum exponent before exponentiating.
    let fitnesses: Vec<f64> = chromosomes.iter().map(|c| c.fitness()).collect();
    let max_fitness = fitnesses.iter().cloned().fold(f64::NEG_INFINITY, f64::max);

    let weights: Vec<f64> = fitnesses
        .iter()
        .map(|&f| ((f - max_fitness) / temp).exp())
        .collect();

    let total_weight: f64 = weights.iter().sum();

    // Build cumulative probability distribution
    let mut cumulative = Vec::with_capacity(n);
    let mut cum = 0.0;
    for (i, &w) in weights.iter().enumerate() {
        let prob = if total_weight > 0.0 {
            w / total_weight
        } else {
            // All weights are zero (e.g. all -inf fitness); fall back to uniform
            1.0 / n as f64
        };
        cum += prob;
        cumulative.push(cum);
        trace!(target="selection_events", method="boltzmann"; "Index {} fitness {} weight {} cum_prob {}", i, fitnesses[i], w, cum);
    }

    // Correct any floating-point drift so the last entry is exactly 1.0
    if let Some(last) = cumulative.last_mut() {
        *last = 1.0;
    }

    // Select parents via roulette-wheel sampling on Boltzmann probabilities
    let mut rng = crate::rng::make_rng();
    let total_parents = couples * 2;
    let mut selected = Vec::with_capacity(total_parents);

    for _ in 0..total_parents {
        let r: f64 = rng.random_range(0.0..1.0);
        let idx = cumulative.iter().position(|&cp| cp >= r).unwrap_or(n - 1);
        selected.push(idx);
    }

    // Pair selected parents
    let mut mating = Vec::new();
    for chunk in selected.chunks(2) {
        if chunk.len() == 2 {
            mating.push((chunk[0], chunk[1]));
            trace!(target="selection_events", method="boltzmann"; "Mating pair: {} - {}", chunk[0], chunk[1]);
        }
    }

    debug!(target="selection_events", method="boltzmann"; "Boltzmann selection finished with {} pairs", mating.len());
    mating
}