Skip to main content

aprender_tsp/solver/
ga.rs

1//! Genetic Algorithm for TSP.
2//!
3//! Reference: Goldberg (1989) "Genetic Algorithms in Search, Optimization, and Machine Learning"
4
5use crate::error::TspResult;
6use crate::instance::TspInstance;
7use crate::solver::{Budget, TspSolution, TspSolver};
8use rand::prelude::*;
9
10/// Genetic Algorithm solver for TSP
11#[derive(Debug, Clone)]
12pub struct GaSolver {
13    /// Population size
14    pub population_size: usize,
15    /// Crossover probability
16    pub crossover_rate: f64,
17    /// Mutation probability
18    pub mutation_rate: f64,
19    /// Tournament selection size
20    pub tournament_size: usize,
21    /// Elitism: number of best individuals to preserve
22    pub elitism: usize,
23    /// Random seed
24    seed: Option<u64>,
25}
26
27impl Default for GaSolver {
28    fn default() -> Self {
29        Self {
30            population_size: 50,
31            crossover_rate: 0.9,
32            mutation_rate: 0.1,
33            tournament_size: 5,
34            elitism: 2,
35            seed: None,
36        }
37    }
38}
39
40impl GaSolver {
41    /// Create a new GA solver
42    pub fn new() -> Self {
43        Self::default()
44    }
45
46    /// Set population size
47    pub fn with_population_size(mut self, size: usize) -> Self {
48        self.population_size = size;
49        self
50    }
51
52    /// Set crossover rate
53    pub fn with_crossover_rate(mut self, rate: f64) -> Self {
54        self.crossover_rate = rate;
55        self
56    }
57
58    /// Set mutation rate
59    pub fn with_mutation_rate(mut self, rate: f64) -> Self {
60        self.mutation_rate = rate;
61        self
62    }
63
64    /// Set tournament size
65    pub fn with_tournament_size(mut self, size: usize) -> Self {
66        self.tournament_size = size;
67        self
68    }
69
70    /// Set elitism count
71    pub fn with_elitism(mut self, count: usize) -> Self {
72        self.elitism = count;
73        self
74    }
75
76    /// Set random seed
77    pub fn with_seed(mut self, seed: u64) -> Self {
78        self.seed = Some(seed);
79        self
80    }
81
82    /// Generate random tour
83    fn random_tour(n: usize, rng: &mut StdRng) -> Vec<usize> {
84        let mut tour: Vec<usize> = (0..n).collect();
85        tour.shuffle(rng);
86        tour
87    }
88
89    /// Tournament selection
90    fn tournament_select<'a>(
91        &self,
92        population: &'a [(Vec<usize>, f64)],
93        rng: &mut StdRng,
94    ) -> &'a Vec<usize> {
95        let mut best_idx = rng.random_range(0..population.len());
96        let mut best_fitness = population[best_idx].1;
97
98        for _ in 1..self.tournament_size {
99            let idx = rng.random_range(0..population.len());
100            if population[idx].1 < best_fitness {
101                best_fitness = population[idx].1;
102                best_idx = idx;
103            }
104        }
105
106        &population[best_idx].0
107    }
108
109    /// Order Crossover (OX)
110    fn order_crossover(parent1: &[usize], parent2: &[usize], rng: &mut StdRng) -> Vec<usize> {
111        let n = parent1.len();
112        if n < 2 {
113            return parent1.to_vec();
114        }
115
116        let mut child = vec![usize::MAX; n];
117
118        // Select crossover segment
119        let start = rng.random_range(0..n);
120        let end = rng.random_range(start..n);
121
122        // Copy segment from parent1
123        child[start..=end].copy_from_slice(&parent1[start..=end]);
124
125        // Fill remaining from parent2 in order
126        let mut pos = (end + 1) % n;
127        let mut p2_pos = (end + 1) % n;
128
129        while child.contains(&usize::MAX) {
130            let city = parent2[p2_pos];
131            if !child.contains(&city) {
132                child[pos] = city;
133                pos = (pos + 1) % n;
134            }
135            p2_pos = (p2_pos + 1) % n;
136        }
137
138        child
139    }
140
141    /// 2-opt mutation
142    fn mutate(&self, tour: &mut [usize], rng: &mut StdRng) {
143        if rng.random::<f64>() < self.mutation_rate {
144            let n = tour.len();
145            if n < 2 {
146                return;
147            }
148
149            let i = rng.random_range(0..n);
150            let j = rng.random_range(0..n);
151
152            if i != j {
153                let (i, j) = if i < j { (i, j) } else { (j, i) };
154                tour[i..=j].reverse();
155            }
156        }
157    }
158
159    /// Evolve population for one generation
160    fn evolve_generation(
161        &self,
162        population: &mut Vec<(Vec<usize>, f64)>,
163        instance: &TspInstance,
164        rng: &mut StdRng,
165    ) -> usize {
166        let mut evaluations = 0;
167
168        // Sort by fitness (lower is better for TSP)
169        population.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(std::cmp::Ordering::Equal));
170
171        let mut new_population = Vec::with_capacity(self.population_size);
172
173        // Elitism: keep best individuals
174        for individual in population.iter().take(self.elitism) {
175            new_population.push(individual.clone());
176        }
177
178        // Generate rest of population
179        while new_population.len() < self.population_size {
180            let parent1 = self.tournament_select(population, rng);
181            let parent2 = self.tournament_select(population, rng);
182
183            let mut child = if rng.random::<f64>() < self.crossover_rate {
184                Self::order_crossover(parent1, parent2, rng)
185            } else {
186                parent1.clone()
187            };
188
189            self.mutate(&mut child, rng);
190
191            let fitness = instance.tour_length(&child);
192            evaluations += 1;
193
194            new_population.push((child, fitness));
195        }
196
197        *population = new_population;
198        evaluations
199    }
200
201    /// Get best individual from population
202    pub fn evolve(
203        &mut self,
204        instance: &TspInstance,
205        generations: usize,
206    ) -> TspResult<Vec<(Vec<usize>, f64)>> {
207        let n = instance.num_cities();
208
209        let mut rng = match self.seed {
210            Some(s) => StdRng::seed_from_u64(s),
211            None => StdRng::from_os_rng(),
212        };
213
214        // Initialize population
215        let mut population: Vec<(Vec<usize>, f64)> = (0..self.population_size)
216            .map(|_| {
217                let tour = Self::random_tour(n, &mut rng);
218                let fitness = instance.tour_length(&tour);
219                (tour, fitness)
220            })
221            .collect();
222
223        // Evolve
224        for _ in 0..generations {
225            self.evolve_generation(&mut population, instance, &mut rng);
226        }
227
228        // Sort by fitness
229        population.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(std::cmp::Ordering::Equal));
230
231        Ok(population)
232    }
233}
234
235impl TspSolver for GaSolver {
236    fn solve(&mut self, instance: &TspInstance, budget: Budget) -> TspResult<TspSolution> {
237        let n = instance.num_cities();
238        let max_generations = budget.limit();
239
240        let mut rng = match self.seed {
241            Some(s) => StdRng::seed_from_u64(s),
242            None => StdRng::from_os_rng(),
243        };
244
245        // Initialize population
246        let mut population: Vec<(Vec<usize>, f64)> = (0..self.population_size)
247            .map(|_| {
248                let tour = Self::random_tour(n, &mut rng);
249                let fitness = instance.tour_length(&tour);
250                (tour, fitness)
251            })
252            .collect();
253
254        let mut best_tour = population[0].0.clone();
255        let mut best_length = population[0].1;
256        let mut history = Vec::with_capacity(max_generations);
257        let mut evaluations = self.population_size;
258
259        // Find initial best
260        for (tour, fitness) in &population {
261            if *fitness < best_length {
262                best_length = *fitness;
263                best_tour.clone_from(tour);
264            }
265        }
266        history.push(best_length);
267
268        // Evolve
269        for _ in 1..max_generations {
270            evaluations += self.evolve_generation(&mut population, instance, &mut rng);
271
272            // Update best
273            for (tour, fitness) in &population {
274                if *fitness < best_length {
275                    best_length = *fitness;
276                    best_tour.clone_from(tour);
277                }
278            }
279
280            history.push(best_length);
281        }
282
283        Ok(TspSolution {
284            tour: best_tour,
285            length: best_length,
286            evaluations,
287            history,
288        })
289    }
290
291    fn name(&self) -> &'static str {
292        "Genetic Algorithm"
293    }
294
295    fn reset(&mut self) {
296        // GA is stateless between runs
297    }
298}
299
300#[cfg(test)]
301#[path = "ga_tests.rs"]
302mod tests;