From 0ec0006ac16173ca3c5218f11fdce165b489db12 Mon Sep 17 00:00:00 2001 From: Rutherther Date: Tue, 28 Oct 2025 21:27:04 +0100 Subject: [PATCH] feat: add edge recombination crossover --- codes/tsp_hw01/src/tsp.rs | 268 +++++++++++++++++++++++++++++++++++++- 1 file changed, 263 insertions(+), 5 deletions(-) diff --git a/codes/tsp_hw01/src/tsp.rs b/codes/tsp_hw01/src/tsp.rs index c0cf6d86b69a7d264ef2418a2b1a7de79d95fbc1..23c40d4e4cbca7e81d061c89e7c5ee319b35ebf4 100644 --- a/codes/tsp_hw01/src/tsp.rs +++ b/codes/tsp_hw01/src/tsp.rs @@ -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) -> bool { + pub fn verify_solution(solution: &NodePermutation) -> 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>, filename: &str) -> Result<(), Box> { 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 { + _phantom: PhantomData +} + +impl ReverseSubsequencePerturbation { + pub fn new() -> Self { + Self { _phantom: PhantomData } + } +} + +impl PerturbationOperator for ReverseSubsequencePerturbation +where + D: Dim, + DefaultAllocator: Allocator, + DefaultAllocator: Allocator, +{ + type Chromosome = NodePermutation; + + 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 { + _phantom: PhantomData +} + +impl EdgeRecombinationCrossover { + pub fn new() -> Self { + Self { _phantom: PhantomData } + } +} + +impl Crossover<2> for EdgeRecombinationCrossover +where + D: Dim, + DefaultAllocator: Allocator, + DefaultAllocator: Allocator, + DefaultAllocator: nalgebra::allocator::Allocator> +{ + type Chromosome = NodePermutation; + type Out = f64; + + fn crossover( + &self, + parents: &eoa_lib::replacement::EvaluatedPopulation, + pairs: impl Iterator>, + rng: &mut dyn RngCore + ) -> eoa_lib::replacement::Population { + 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; + impl FitnessFunction for ZeroFitness { + type In = NodePermutation>; + type Out = f64; + type Err = Infallible; + + fn fit(self: &Self, _: &Self::In) -> Result { + Ok(0.0) + } + } + + #[test] + fn test_edge_recombination_properties() { + let crossover = EdgeRecombinationCrossover::>::new(); + let initializer = TSPRandomInitializer::>::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 = vec![0, 1, 2, 4, 5, 3]; + let parent2: Vec = vec![2, 0, 1, 3, 4, 5]; + + let parent1 = NodePermutation:: { permutation: SVector::::from_vec(parent1) }; + let parent2 = NodePermutation:: { permutation: SVector::::from_vec(parent2) }; + + let pairing = SVector::::new(0, 1); + let pairings = vec![pairing].into_iter(); + + let parents = Population::from_vec(vec![parent1, parent2]).evaluate(&ZeroFitness).unwrap(); + + let crossover = EdgeRecombinationCrossover::::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::>()) + } + +}