aprender_tsp/solver/
aco.rs1use crate::error::TspResult;
7use crate::instance::TspInstance;
8use crate::solver::{Budget, TspSolution, TspSolver};
9use aprender::metaheuristics::{
10 AntColony, Budget as CoreBudget, ConstructiveMetaheuristic, SearchSpace,
11};
12
13#[derive(Debug, Clone)]
17pub struct AcoSolver {
18 pub num_ants: usize,
20 pub alpha: f64,
22 pub beta: f64,
24 pub rho: f64,
26 pub q0: f64,
29 seed: Option<u64>,
31 inner: Option<AntColony>,
33}
34
35impl Default for AcoSolver {
36 fn default() -> Self {
37 Self {
38 num_ants: 20,
39 alpha: 1.0,
40 beta: 2.5,
41 rho: 0.1,
42 q0: 0.9,
43 seed: None,
44 inner: None,
45 }
46 }
47}
48
49impl AcoSolver {
50 #[must_use]
52 pub fn new() -> Self {
53 Self::default()
54 }
55
56 #[must_use]
58 pub fn with_num_ants(mut self, num_ants: usize) -> Self {
59 self.num_ants = num_ants;
60 self
61 }
62
63 #[must_use]
65 pub fn with_alpha(mut self, alpha: f64) -> Self {
66 self.alpha = alpha;
67 self
68 }
69
70 #[must_use]
72 pub fn with_beta(mut self, beta: f64) -> Self {
73 self.beta = beta;
74 self
75 }
76
77 #[must_use]
79 pub fn with_rho(mut self, rho: f64) -> Self {
80 self.rho = rho;
81 self
82 }
83
84 #[must_use]
88 pub fn with_q0(mut self, q0: f64) -> Self {
89 self.q0 = q0;
90 self
91 }
92
93 #[must_use]
95 pub fn with_seed(mut self, seed: u64) -> Self {
96 self.seed = Some(seed);
97 self
98 }
99
100 fn build_inner(&self) -> AntColony {
102 let mut aco = AntColony::new(self.num_ants)
103 .with_alpha(self.alpha)
104 .with_beta(self.beta)
105 .with_rho(self.rho);
106
107 if let Some(seed) = self.seed {
108 aco = aco.with_seed(seed);
109 }
110
111 aco
112 }
113
114 fn to_core_budget(budget: Budget) -> CoreBudget {
116 match budget {
117 Budget::Iterations(n) => CoreBudget::Iterations(n),
118 Budget::Evaluations(n) => CoreBudget::Evaluations(n),
119 }
120 }
121}
122
123impl TspSolver for AcoSolver {
124 fn solve(&mut self, instance: &TspInstance, budget: Budget) -> TspResult<TspSolution> {
125 let space = SearchSpace::tsp(&instance.distances);
127
128 let mut aco = self.build_inner();
130
131 let objective = |tour: &Vec<usize>| -> f64 { instance.tour_length(tour) };
133
134 let result = aco.optimize(&objective, &space, Self::to_core_budget(budget));
136
137 self.inner = Some(aco);
139
140 Ok(TspSolution {
141 tour: result.solution,
142 length: result.objective_value,
143 evaluations: result.evaluations,
144 history: result.history,
145 })
146 }
147
148 fn name(&self) -> &'static str {
149 "Ant Colony Optimization"
150 }
151
152 fn reset(&mut self) {
153 self.inner = None;
154 }
155}
156
157#[cfg(test)]
158mod tests {
159 use super::*;
160
161 fn square_instance() -> TspInstance {
162 let coords = vec![(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 1.0)];
164 TspInstance::from_coords("square", coords).expect("should create")
165 }
166
167 fn triangle_instance() -> TspInstance {
168 let coords = vec![(0.0, 0.0), (3.0, 0.0), (3.0, 4.0)];
170 TspInstance::from_coords("triangle", coords).expect("should create")
171 }
172
173 #[test]
174 fn test_aco_default_params() {
175 let aco = AcoSolver::default();
176 assert_eq!(aco.num_ants, 20);
177 assert!((aco.alpha - 1.0).abs() < 1e-10);
178 assert!((aco.beta - 2.5).abs() < 1e-10);
179 assert!((aco.rho - 0.1).abs() < 1e-10);
180 assert!((aco.q0 - 0.9).abs() < 1e-10);
181 }
182
183 #[test]
184 fn test_aco_builder() {
185 let aco = AcoSolver::new()
186 .with_num_ants(50)
187 .with_alpha(2.0)
188 .with_beta(3.0)
189 .with_rho(0.2)
190 .with_seed(42);
191
192 assert_eq!(aco.num_ants, 50);
193 assert!((aco.alpha - 2.0).abs() < 1e-10);
194 assert!((aco.beta - 3.0).abs() < 1e-10);
195 assert!((aco.rho - 0.2).abs() < 1e-10);
196 assert_eq!(aco.seed, Some(42));
197 }
198
199 #[test]
200 fn test_aco_solves_square() {
201 let instance = square_instance();
202 let mut solver = AcoSolver::new().with_seed(42).with_num_ants(10);
203
204 let solution = solver
205 .solve(&instance, Budget::Iterations(50))
206 .expect("should solve");
207
208 assert!(solution.length <= 5.0, "Length {} > 5.0", solution.length);
210 assert_eq!(solution.tour.len(), 4);
211 assert!(instance.validate_tour(&solution.tour).is_ok());
212 }
213
214 #[test]
215 fn test_aco_solves_triangle() {
216 let instance = triangle_instance();
217 let mut solver = AcoSolver::new().with_seed(42).with_num_ants(10);
218
219 let solution = solver
220 .solve(&instance, Budget::Iterations(50))
221 .expect("should solve");
222
223 assert!(solution.length <= 13.0, "Length {} > 13.0", solution.length);
225 assert_eq!(solution.tour.len(), 3);
226 }
227
228 #[test]
229 fn test_aco_deterministic_with_seed() {
230 let instance = square_instance();
231
232 let mut solver1 = AcoSolver::new().with_seed(42).with_num_ants(5);
233 let mut solver2 = AcoSolver::new().with_seed(42).with_num_ants(5);
234
235 let solution1 = solver1
236 .solve(&instance, Budget::Iterations(10))
237 .expect("should solve");
238 let solution2 = solver2
239 .solve(&instance, Budget::Iterations(10))
240 .expect("should solve");
241
242 assert!((solution1.length - solution2.length).abs() < 1e-10);
243 assert_eq!(solution1.tour, solution2.tour);
244 }
245
246 #[test]
247 fn test_aco_tracks_history() {
248 let instance = square_instance();
249 let mut solver = AcoSolver::new().with_seed(42).with_num_ants(5);
250
251 let solution = solver
252 .solve(&instance, Budget::Iterations(20))
253 .expect("should solve");
254
255 assert_eq!(solution.history.len(), 20);
256 for window in solution.history.windows(2) {
258 assert!(window[1] <= window[0] + 1e-10);
259 }
260 }
261
262 #[test]
263 fn test_aco_counts_evaluations() {
264 let instance = square_instance();
265 let mut solver = AcoSolver::new().with_seed(42).with_num_ants(10);
266
267 let solution = solver
268 .solve(&instance, Budget::Iterations(5))
269 .expect("should solve");
270
271 assert_eq!(solution.evaluations, 50);
273 }
274
275 #[test]
276 fn test_aco_reset() {
277 let instance = square_instance();
278 let mut solver = AcoSolver::new().with_seed(42);
279
280 solver
281 .solve(&instance, Budget::Iterations(5))
282 .expect("should solve");
283 assert!(solver.inner.is_some());
284
285 solver.reset();
286 assert!(solver.inner.is_none());
287 }
288
289 #[test]
290 fn test_aco_name() {
291 let solver = AcoSolver::new();
292 assert_eq!(solver.name(), "Ant Colony Optimization");
293 }
294
295 #[test]
296 fn test_aco_improves_over_iterations() {
297 let instance = square_instance();
298 let mut solver = AcoSolver::new().with_seed(42).with_num_ants(10);
299
300 let solution = solver
301 .solve(&instance, Budget::Iterations(100))
302 .expect("should solve");
303
304 let first = solution.history[0];
306 let last = solution.history[solution.history.len() - 1];
307 assert!(last <= first);
308 }
309}