~ruther/ctu-fee-eoa

0ec0006ac16173ca3c5218f11fdce165b489db12 — Rutherther a month ago 92bd061
feat: add edge recombination crossover
1 files changed, 263 insertions(+), 5 deletions(-)

M codes/tsp_hw01/src/tsp.rs
M codes/tsp_hw01/src/tsp.rs => codes/tsp_hw01/src/tsp.rs +263 -5
@@ 1,10 1,12 @@
use std::{convert::Infallible, marker::PhantomData};

use eoa_lib::{fitness::FitnessFunction, initializer::Initializer, perturbation::PerturbationOperator};
use eoa_lib::{crossover::Crossover, fitness::FitnessFunction, initializer::Initializer, perturbation::PerturbationOperator, replacement::Population};
use itertools::Itertools;
use nalgebra::{allocator::Allocator, distance, Const, DefaultAllocator, Dim, Dyn, OMatrix, OVector, Point, U1};
use plotters::prelude::*;
use rand::{seq::SliceRandom, Rng, RngCore};
use rand::{seq::{IteratorRandom, SliceRandom}, Rng, RngCore};

use crate::graph::Edge;

#[derive(PartialEq, Clone, Debug)]
pub struct TSPCity {


@@ 91,7 93,7 @@ where
        self.distances.shape_generic().0
    }

    pub fn verify_solution(&self, solution: &NodePermutation<D>) -> bool {
    pub fn verify_solution(solution: &NodePermutation<D>) -> bool {
        let mut seen_vertices = OVector::from_element_generic(
            solution.permutation.shape_generic().0,
            solution.permutation.shape_generic().1,


@@ 100,7 102,7 @@ where

        for &vertex in solution.permutation.iter() {
            // This vertex index is out of bounds
            if vertex >= self.cities.len() {
            if vertex >= solution.permutation.len() {
                return false;
            }



@@ 119,10 121,14 @@ where
        solution.permutation
            .iter()
            .circular_tuple_windows()
            .map(|(&node1, &node2): (&usize, &usize)| self.distances[(node1, node2)])
            .map(|(&node1, &node2): (&usize, &usize)| self.distances(node1, node2))
            .sum()
    }

    pub fn distances(&self, city_a: usize, city_b: usize) -> f64 {
        self.distances[(city_a, city_b)]
    }

    fn plot_internal(&self, solution: Option<&NodePermutation<D>>, filename: &str) -> Result<(), Box<dyn std::error::Error>> {
        let root = BitMapBackend::new(filename, (800, 600)).into_drawing_area();
        root.fill(&WHITE)?;


@@ 263,3 269,255 @@ where
        chromosome.permutation.swap_rows(first, second);
    }
}

pub struct ReverseSubsequencePerturbation<D> {
    _phantom: PhantomData<D>
}

impl<D> ReverseSubsequencePerturbation<D> {
    pub fn new() -> Self {
        Self { _phantom: PhantomData }
    }
}

impl<D> PerturbationOperator for ReverseSubsequencePerturbation<D>
where
    D: Dim,
    DefaultAllocator: Allocator<D, D>,
    DefaultAllocator: Allocator<D>,
{
    type Chromosome = NodePermutation<D>;

    fn perturb(&self, chromosome: &mut Self::Chromosome, rng: &mut dyn RngCore) {
        let first = rng.random_range(0..chromosome.permutation.len());
        let second = rng.random_range(0..chromosome.permutation.len());

        let start = first.min(second);
        let end = first.max(second);

        // Only to half of the interval to swap
        let end = (end + start) / 2;

        for (i, j) in (start..=end).zip(end..=start) {
            chromosome.permutation.swap_rows(i, j);
        }

    }
}

pub struct EdgeRecombinationCrossover<D> {
    _phantom: PhantomData<D>
}

impl<D> EdgeRecombinationCrossover<D> {
    pub fn new() -> Self {
        Self { _phantom: PhantomData }
    }
}

impl<D> Crossover<2> for EdgeRecombinationCrossover<D>
where
    D: Dim,
    DefaultAllocator: Allocator<D, D>,
    DefaultAllocator: Allocator<D>,
    DefaultAllocator: nalgebra::allocator::Allocator<D, Const<4>>
{
    type Chromosome = NodePermutation<D>;
    type Out = f64;

    fn crossover(
        &self,
        parents: &eoa_lib::replacement::EvaluatedPopulation<Self::Chromosome, Self::Out>,
        pairs: impl Iterator<Item = eoa_lib::pairing::ParentPairing<2>>,
        rng: &mut dyn RngCore
    ) -> eoa_lib::replacement::Population<Self::Chromosome> {
        let mut offsprings = vec![];

        let permutation = &parents.population[0].chromosome.permutation;
        let len = permutation.len();
        let mut adjacency_lists = OMatrix::from_element_generic(
            permutation.shape_generic().0,
            Const::<4>,
            None);
        let mut used_nodes = OVector::from_element_generic(
            permutation.shape_generic().0,
            Const::<1>,
            false
        );

        let mut neighbors_count = OVector::from_element_generic(
            permutation.shape_generic().0,
            Const::<1>,
            2usize
        );

        for pair in pairs {
            let parent1 = &parents.population[pair.x].chromosome;
            let parent2 = &parents.population[pair.y].chromosome;

            used_nodes.apply(|n| *n = false);

            // 1. Populate adjacency lists
            for (&c1, &n, &c2) in parent1.permutation.iter().circular_tuple_windows() {
                adjacency_lists[(n, 0)] = Some(c1);
                adjacency_lists[(n, 1)] = Some(c2);
                neighbors_count[n] = 2;
            }

            for (&c1, &n, &c2) in parent2.permutation.iter().circular_tuple_windows() {
                // Not duplicit?
                if adjacency_lists[(n, 0)].unwrap() != c1 && adjacency_lists[(n, 1)].unwrap() != c1 {
                    neighbors_count[n] += 1;
                    adjacency_lists[(n, 2)] = Some(c1);
                } else { // Duplicit
                    adjacency_lists[(n, 2)] = None;
                }

                // Not duplicit
                if adjacency_lists[(n, 0)].unwrap() != c2 && adjacency_lists[(n, 1)].unwrap() != c2 {
                    neighbors_count[n] += 1;
                    adjacency_lists[(n, 3)] = Some(c2);
                } else { // Duplicit
                    adjacency_lists[(n, 3)] = None;
                }
            }

            let chosen_parent = if rng.random_bool(0.5) {
                &parent1
            } else {
                &parent2
            };

            let mut offspring = OVector::from_element_generic(permutation.shape_generic().0, Const::<1>, 0);

            let mut current_node = chosen_parent.permutation[0];

            for i in 0..len-1 {
                offspring[i] = current_node;
                used_nodes[current_node] = true;

                for neighbor in adjacency_lists.row(current_node) {
                    if let Some(neighbor) = neighbor {
                        neighbors_count[*neighbor] -= 1;
                    }
                }

                let min_neighbors = adjacency_lists.row(current_node)
                    .iter()
                    .flatten()
                    .filter(|&&neighbor| !used_nodes[neighbor])
                    .map(|&neighbor| neighbors_count[neighbor])
                    .min();

                let neighbor = if let Some(min_neighbors) = min_neighbors {
                    adjacency_lists.row(current_node)
                        .iter()
                        .flatten()
                        .copied()
                        .filter(|&neighbor| !used_nodes[neighbor] && neighbors_count[neighbor] == min_neighbors)
                        .choose(rng)
                } else {
                    None
                };

                current_node = if let Some(neighbor) = neighbor {
                    neighbor
                } else {
                    (0..len).filter(|&node| !used_nodes[node])
                    .choose(rng)
                    .unwrap()
                };
            }

            offspring[len - 1] = current_node;

            offsprings.push(NodePermutation { permutation: offspring });
        }

        Population::from_vec(offsprings)
    }
}

#[cfg(test)]
mod tests {
    use std::convert::Infallible;

    use eoa_lib::{crossover::Crossover, fitness::FitnessFunction, initializer::Initializer, pairing::{AdjacentPairing, Pairing}, replacement::Population};
    use nalgebra::{Const, SVector, U2, U6};
    use rand::{rngs::StdRng, RngCore, SeedableRng};

    use crate::tsp::TSPInstance;

    use super::{EdgeRecombinationCrossover, NodePermutation, TSPRandomInitializer};

    struct MockRng;
    impl RngCore for MockRng {
        fn next_u32(&mut self) -> u32 {
            0
        }

        fn next_u64(&mut self) -> u64 {
            0
        }

        fn fill_bytes(&mut self, _: &mut [u8]) {
            panic!()
        }
    }

    struct ZeroFitness<const LEN: usize>;
    impl<const LEN: usize> FitnessFunction for ZeroFitness<LEN> {
        type In = NodePermutation<Const<LEN>>;
        type Out = f64;
        type Err = Infallible;

        fn fit(self: &Self, _: &Self::In) -> Result<Self::Out, Self::Err> {
            Ok(0.0)
        }
    }

    #[test]
    fn test_edge_recombination_properties() {
        let crossover = EdgeRecombinationCrossover::<Const<10>>::new();
        let initializer = TSPRandomInitializer::<Const<10>>::new();
        let adjacency_pairing = AdjacentPairing::new();

        let mut rng = StdRng::seed_from_u64(0);
        for _ in 0..100 {
            let parents = Population::from_vec(initializer.initialize(Const::<10>, 10, &mut rng));
            let parents = parents.evaluate(&ZeroFitness).unwrap();

            let pairs = adjacency_pairing.pair(&parents, 0..10);
            let result = crossover.crossover(&parents, pairs, &mut rng);

            // Test invariants that should always hold:
            for chromosome in result.into_iter() {
                assert!(TSPInstance::verify_solution(&chromosome));
            }
        }
    }

    #[test]
    fn test_edge_recombination_specific_case() {
        let parent1: Vec<usize> = vec![0, 1, 2, 4, 5, 3];
        let parent2: Vec<usize> = vec![2, 0, 1, 3, 4, 5];

        let parent1 = NodePermutation::<U6> { permutation: SVector::<usize, 6>::from_vec(parent1) };
        let parent2 = NodePermutation::<U6> { permutation: SVector::<usize, 6>::from_vec(parent2) };

        let pairing = SVector::<usize, 2>::new(0, 1);
        let pairings = vec![pairing].into_iter();

        let parents = Population::from_vec(vec![parent1, parent2]).evaluate(&ZeroFitness).unwrap();

        let crossover = EdgeRecombinationCrossover::<U6>::new();

        let offsprings = crossover.crossover(&parents, pairings, &mut MockRng);
        let offspring = offsprings.into_iter().next().unwrap();

        eprintln!("{:?}", offspring);

        assert_eq!(vec![0usize, 1, 3, 4, 5, 2], offspring.permutation.into_iter().copied().collect::<Vec<_>>())
    }

}