From 5f409074518fb0a5700af0bf1cd07a727bf8df17 Mon Sep 17 00:00:00 2001 From: Rutherther Date: Sat, 8 Nov 2025 19:39:35 +0100 Subject: [PATCH] refactor(tsp): Do not allocate in binary string fitness function Currently the fit function has allocated for each individual! That slows it down terribly! This reimplements it to implement the fit_population, reusing the same node permutation and same precedence_count --- .../src/binary_string_representation.rs | 70 ++++++++++++++++--- codes/tsp_hw01/src/main.rs | 30 ++++---- 2 files changed, 77 insertions(+), 23 deletions(-) diff --git a/codes/tsp_hw01/src/binary_string_representation.rs b/codes/tsp_hw01/src/binary_string_representation.rs index a50cd94beaeb1378e142e9c574c098937b23f476..275d6ce285597fefe2afe06f57b8ca5039496c65 100644 --- a/codes/tsp_hw01/src/binary_string_representation.rs +++ b/codes/tsp_hw01/src/binary_string_representation.rs @@ -2,6 +2,7 @@ use nalgebra::{allocator::Allocator, DefaultAllocator, Dim, OVector, U1}; use eoa_lib::{binary_string::BinaryString, fitness::FitnessFunction}; use thiserror::Error; use crate::tsp::{NodePermutation, TSPInstance}; +use eoa_lib::population::EvaluatedChromosome; impl<'a, DIn: Dim, DOut: Dim> FitnessFunction for TSPBinaryStringWrapper<'a, DIn, DOut> where @@ -16,6 +17,44 @@ where fn fit(self: &Self, inp: &Self::In) -> Result { Ok(self.instance.fit(&self.to_permutation(inp)?).unwrap()) } + + fn fit_population(&self, inp: Vec) -> Result>, Self::Err> { + let nodes = self.dim_out.value(); + + // Count how many nodes each node comes after (precedence count) + let mut precedence_count = OVector::::zeros_generic(self.dim_out, U1); + + let result = OVector::from_iterator_generic( + self.dim_out, + U1, + 0..nodes + ); + + let mut permutation = NodePermutation { permutation: result }; + + inp + .into_iter() + .map(|chromosome| { + // Reset + precedence_count + .apply(|c| *c = 0); + + // NOTE no need to reset the permutation + // as it's always sorted + + self.to_permutation_buff( + &chromosome, + &mut permutation, + &mut precedence_count + )?; + + Ok(EvaluatedChromosome { + evaluation: self.instance.fit(&permutation).unwrap(), + chromosome, + }) + }) + .collect::, _>>() + } } pub struct TSPBinaryStringWrapper<'a, DIn: Dim, DOut: Dim> @@ -53,16 +92,18 @@ where } } - pub fn to_permutation(&self, inp: &BinaryString) -> Result, DimensionMismatch> { + pub fn to_permutation_buff( + &self, + inp: &BinaryString, + permutation: &mut NodePermutation, + precedence_count: &mut OVector + ) -> Result<(), DimensionMismatch> { let nodes = self.dim_out.value(); if inp.vec().shape_generic().0.value() != self.dim_in.value() { return Err(DimensionMismatch::Mismatch); } - // Count how many nodes each node comes after (precedence count) - let mut precedence_count = OVector::::zeros_generic(self.dim_out, U1); - let mut in_index = 0usize; for i in 0..self.dim_out.value() { for j in i+1..nodes { @@ -86,17 +127,30 @@ where return Err(DimensionMismatch::Mismatch); } + permutation.permutation + .as_mut_slice() + .sort_unstable_by_key(|&node| precedence_count[node]); + + Ok(()) + } + + pub fn to_permutation(&self, inp: &BinaryString) -> Result, DimensionMismatch> { + let nodes = self.dim_out.value(); + + // Count how many nodes each node comes after (precedence count) + let mut precedence_count = OVector::::zeros_generic(self.dim_out, U1); + let mut result = OVector::from_iterator_generic( self.dim_out, U1, 0..nodes ); - result - .as_mut_slice() - .sort_by_key(|&node| precedence_count[node]); + let mut permutation = NodePermutation { permutation: result }; + + self.to_permutation_buff(inp, &mut permutation, &mut precedence_count)?; - Ok(NodePermutation { permutation: result }) + Ok(permutation) } } diff --git a/codes/tsp_hw01/src/main.rs b/codes/tsp_hw01/src/main.rs index 5f5fddc5cf8e306bfc2b844b97de2b6110929bc4..36065dfb30648417f2b2a8f6afedbb3aa18ea47a 100644 --- a/codes/tsp_hw01/src/main.rs +++ b/codes/tsp_hw01/src/main.rs @@ -13,7 +13,7 @@ use perturbations::{MovePerturbation, ReverseSubsequencePerturbation, SwapPertur use binary_string_representation::TSPBinaryStringWrapper; use nalgebra::{Dim, Dyn}; use eoa_lib::{ - binary_string::BinaryString, comparison::MinimizingOperator, crossover::BinaryNPointCrossover, evolution::{evolution_algorithm, EvolutionStats}, evolutionary_strategy::IdentityStrategy, initializer::{Initializer, RandomInitializer}, local_search::{local_search_first_improving, LocalSearchStats}, pairing::AdjacentPairing, perturbation::{apply_to_perturbations, BinaryStringBitPerturbation, BinaryStringFlipNPerturbation, BinaryStringSingleBitPerturbation, CombinedPerturbation, IdentityPerturbation, MutationPerturbation, OneOfPerturbation}, random_search::random_search, replacement::{BestReplacement, TournamentReplacement}, selection::{BestSelection, RouletteWheelSelection}, terminating::MaximumCyclesTerminatingCondition + binary_string::BinaryString, comparison::MinimizingOperator, crossover::BinaryNPointCrossover, evolution::{evolution_algorithm, EvolutionStats}, evolutionary_strategy::IdentityStrategy, initializer::{Initializer, RandomInitializer}, local_search::{local_search_first_improving, LocalSearchStats}, pairing::AdjacentPairing, perturbation::{apply_to_perturbations, BinaryStringBitPerturbation, BinaryStringFlipNPerturbation, BinaryStringFlipPerturbation, BinaryStringSingleBitPerturbation, CombinedPerturbation, IdentityPerturbation, MutationPerturbation, OneOfPerturbation}, random_search::random_search, replacement::{BestReplacement, TournamentReplacement}, selection::{BestSelection, RouletteWheelSelection, TournamentSelection}, terminating::MaximumCyclesTerminatingCondition }; use rand::rng; use std::env; @@ -615,10 +615,10 @@ fn run_evolution_algorithm_binary(instance: &TSPInstance) -> Result) -> Result::new(); + let mut crossover = BinaryNPointCrossover::<5, _, _>::new(); let mut selection = BestSelection::new(); - let mut replacement = TournamentReplacement::new(5, 1.0); + let mut replacement = TournamentReplacement::new(5, 0.9); let mut pairing = AdjacentPairing::new(); let better_than_operator = MinimizingOperator::new(); @@ -658,15 +658,15 @@ fn run_evolution_algorithm_binary(instance: &TSPInstance) -> Result>( - perturbation, - &mut |p| { - found = true; - p.p = (0.025 * (1.0 + (iters_since_better as f64 / iters_till_end as f64))).min(0.2); - } - ); - assert!(found); + // let mut found = false; + // apply_to_perturbations::<_, BinaryStringBitPerturbation>( + // perturbation, + // &mut |p| { + // found = true; + // p.p = (0.025 * (1.0 + (iters_since_better as f64 / iters_till_end as f64))).min(0.2); + // } + // ); + // assert!(found); let mut found = 0; MutationPerturbation::apply_to_mutations( @@ -674,7 +674,7 @@ fn run_evolution_algorithm_binary(instance: &TSPInstance) -> Result 0 { - p.probability = (0.5 * (1.0 + (iters_since_better as f64 / iters_till_end as f64))).min(1.0); + p.probability = (0.4 * (0.5 + (iters_since_better as f64 / EA_MAX_ITERATIONS as f64))).min(1.0); } found += 1; }