genetic_algorithms 2.2.0

Library for solving genetic algorithm problems
Documentation
use log::debug;

/// Computes the sharing function value `sh(d)` for a given distance.
///
/// The sharing function is:
/// - `sh(d) = 1 - (d / sigma_share)^alpha` if `d < sigma_share`
/// - `sh(d) = 0` otherwise
///
/// # Arguments
///
/// * `distance` - Distance between two individuals.
/// * `sigma_share` - Sharing radius.
/// * `alpha` - Shape parameter.
///
/// # Returns
///
/// The sharing value in [0, 1].
///
/// # Examples
///
/// ```
/// use genetic_algorithms::niching::sharing::sharing_function;
///
/// let sh = sharing_function(0.5, 1.0, 1.0);
/// assert!((sh - 0.5).abs() < f64::EPSILON);
///
/// let sh = sharing_function(1.5, 1.0, 1.0);
/// assert!((sh - 0.0).abs() < f64::EPSILON);
/// ```
pub fn sharing_function(distance: f64, sigma_share: f64, alpha: f64) -> f64 {
    if distance < sigma_share {
        1.0 - (distance / sigma_share).powf(alpha)
    } else {
        0.0
    }
}

/// Applies fitness sharing to a population's fitness values.
///
/// For each individual `i`, the shared fitness is:
/// `f'(i) = f(i) / niche_count(i)`
///
/// where `niche_count(i) = sum_j(sh(d(i, j)))` over all individuals `j`.
///
/// # Arguments
///
/// * `fitness_values` - Mutable slice of fitness values to be adjusted in-place.
/// * `distances` - A symmetric distance matrix where `distances[i][j]` is the
///   distance between individual `i` and individual `j`.
/// * `sigma_share` - Sharing radius.
/// * `alpha` - Shape parameter for the sharing function.
///
/// # Examples
///
/// ```
/// use genetic_algorithms::niching::sharing::apply_fitness_sharing;
///
/// let mut fitnesses = vec![10.0, 10.0, 10.0];
/// // All individuals are identical (distance 0)
/// let distances = vec![
///     vec![0.0, 0.0, 0.0],
///     vec![0.0, 0.0, 0.0],
///     vec![0.0, 0.0, 0.0],
/// ];
/// apply_fitness_sharing(&mut fitnesses, &distances, 1.0, 1.0);
/// // niche_count for each = 3.0 (sh(0) = 1.0 for each pair)
/// // shared fitness = 10.0 / 3.0
/// for f in &fitnesses {
///     assert!((*f - 10.0 / 3.0).abs() < 1e-10);
/// }
/// ```
pub fn apply_fitness_sharing(
    fitness_values: &mut [f64],
    distances: &[Vec<f64>],
    sigma_share: f64,
    alpha: f64,
) {
    let n = fitness_values.len();
    if n == 0 {
        return;
    }

    let raw_fitnesses: Vec<f64> = fitness_values.to_vec();

    for i in 0..n {
        let mut niche_count = 0.0;
        for j in 0..n {
            let d = if i < distances.len() && j < distances[i].len() {
                distances[i][j]
            } else {
                f64::INFINITY
            };
            niche_count += sharing_function(d, sigma_share, alpha);
        }

        if niche_count > 0.0 {
            fitness_values[i] = raw_fitnesses[i] / niche_count;
        }
    }

    debug!(
        target: "niching_events",
        "Applied fitness sharing to {} individuals with sigma_share={}, alpha={}",
        n,
        sigma_share,
        alpha
    );
}

/// Computes a distance matrix from a slice of chromosomes using a distance function.
///
/// # Arguments
///
/// * `dna_slices` - Slice of DNA slice references.
/// * `distance_fn` - A function that computes distance between two DNA slices.
///
/// # Returns
///
/// A symmetric matrix (Vec of Vec) of distances.
pub fn compute_distance_matrix<G, F>(dna_slices: &[&[G]], distance_fn: F) -> Vec<Vec<f64>>
where
    F: Fn(&[G], &[G]) -> f64,
{
    let n = dna_slices.len();
    let mut matrix = vec![vec![0.0; n]; n];

    for i in 0..n {
        for j in (i + 1)..n {
            let d = distance_fn(dna_slices[i], dna_slices[j]);
            matrix[i][j] = d;
            matrix[j][i] = d;
        }
    }

    matrix
}