1use crate::error::TspResult;
10use crate::instance::TspInstance;
11use crate::solver::{AcoSolver, Budget, GaSolver, TabuSolver, TspSolution, TspSolver};
12
13#[derive(Debug, Clone)]
15pub struct HybridSolver {
16 pub ga_fraction: f64,
18 pub tabu_fraction: f64,
20 pub aco_fraction: f64,
22 pub ga_population: usize,
24 pub refine_top_k: usize,
26 seed: Option<u64>,
28}
29
30impl Default for HybridSolver {
31 fn default() -> Self {
32 Self {
33 ga_fraction: 0.4,
34 tabu_fraction: 0.3,
35 aco_fraction: 0.3,
36 ga_population: 30,
37 refine_top_k: 3,
38 seed: None,
39 }
40 }
41}
42
43impl HybridSolver {
44 pub fn new() -> Self {
46 Self::default()
47 }
48
49 pub fn with_ga_fraction(mut self, fraction: f64) -> Self {
51 self.ga_fraction = fraction.clamp(0.0, 1.0);
52 self
53 }
54
55 pub fn with_tabu_fraction(mut self, fraction: f64) -> Self {
57 self.tabu_fraction = fraction.clamp(0.0, 1.0);
58 self
59 }
60
61 pub fn with_aco_fraction(mut self, fraction: f64) -> Self {
63 self.aco_fraction = fraction.clamp(0.0, 1.0);
64 self
65 }
66
67 pub fn with_ga_population(mut self, size: usize) -> Self {
69 self.ga_population = size;
70 self
71 }
72
73 pub fn with_refine_top_k(mut self, k: usize) -> Self {
75 self.refine_top_k = k;
76 self
77 }
78
79 pub fn with_seed(mut self, seed: u64) -> Self {
81 self.seed = Some(seed);
82 self
83 }
84
85 fn normalize_fractions(&self) -> (f64, f64, f64) {
87 let total = self.ga_fraction + self.tabu_fraction + self.aco_fraction;
88 if total <= 0.0 {
89 return (0.34, 0.33, 0.33);
90 }
91 (
92 self.ga_fraction / total,
93 self.tabu_fraction / total,
94 self.aco_fraction / total,
95 )
96 }
97}
98
99impl TspSolver for HybridSolver {
100 fn solve(&mut self, instance: &TspInstance, budget: Budget) -> TspResult<TspSolution> {
101 let total_iterations = budget.limit();
102 let (ga_frac, tabu_frac, aco_frac) = self.normalize_fractions();
103
104 let ga_iters = ((total_iterations as f64 * ga_frac) as usize).max(1);
105 let tabu_iters = ((total_iterations as f64 * tabu_frac) as usize).max(1);
106 let aco_iters = ((total_iterations as f64 * aco_frac) as usize).max(1);
107
108 let mut total_evaluations = 0;
109 let mut history = Vec::new();
110
111 let mut ga = GaSolver::new().with_population_size(self.ga_population);
113 if let Some(seed) = self.seed {
114 ga = ga.with_seed(seed);
115 }
116
117 let population = ga.evolve(instance, ga_iters)?;
118 total_evaluations += self.ga_population * ga_iters;
119
120 let top_k: Vec<Vec<usize>> = population
122 .iter()
123 .take(self.refine_top_k)
124 .map(|(tour, _)| tour.clone())
125 .collect();
126
127 let mut best_tour = top_k[0].clone();
128 let mut best_length = instance.tour_length(&best_tour);
129 history.push(best_length);
130
131 let iters_per_solution = tabu_iters / self.refine_top_k.max(1);
133
134 for tour in &top_k {
135 let mut tabu = TabuSolver::new().with_tenure(15);
136 if let Some(seed) = self.seed {
137 tabu = tabu.with_seed(seed);
138 }
139
140 let refined = tabu.refine(tour.clone(), instance, iters_per_solution)?;
141 total_evaluations += refined.evaluations;
142
143 if refined.length < best_length {
144 best_length = refined.length;
145 best_tour = refined.tour;
146 }
147
148 history.push(best_length);
149 }
150
151 let mut aco = AcoSolver::new().with_num_ants(10);
154 if let Some(seed) = self.seed {
155 aco = aco.with_seed(seed);
156 }
157
158 let aco_result = aco.solve(instance, Budget::Iterations(aco_iters))?;
159 total_evaluations += aco_result.evaluations;
160
161 if aco_result.length < best_length {
162 best_length = aco_result.length;
163 best_tour = aco_result.tour;
164 }
165
166 for &h in &aco_result.history {
167 history.push(h.min(best_length));
168 }
169
170 Ok(TspSolution {
171 tour: best_tour,
172 length: best_length,
173 evaluations: total_evaluations,
174 history,
175 })
176 }
177
178 fn name(&self) -> &'static str {
179 "Hybrid (GA+Tabu+ACO)"
180 }
181
182 fn reset(&mut self) {
183 }
185}
186
187#[cfg(test)]
188mod tests {
189 use super::*;
190
191 fn square_instance() -> TspInstance {
192 let coords = vec![(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 1.0)];
193 TspInstance::from_coords("square", coords).expect("should create")
194 }
195
196 fn pentagon_instance() -> TspInstance {
197 let angle_step = 2.0 * std::f64::consts::PI / 5.0;
198 let coords: Vec<(f64, f64)> = (0..5)
199 .map(|i| {
200 let angle = i as f64 * angle_step;
201 (angle.cos(), angle.sin())
202 })
203 .collect();
204 TspInstance::from_coords("pentagon", coords).expect("should create")
205 }
206
207 fn random_instance(n: usize, seed: u64) -> TspInstance {
208 use rand::prelude::*;
209 let mut rng = StdRng::seed_from_u64(seed);
210 let coords: Vec<(f64, f64)> = (0..n).map(|_| (rng.gen(), rng.gen())).collect();
211 TspInstance::from_coords("random", coords).expect("should create")
212 }
213
214 #[test]
215 fn test_hybrid_default_params() {
216 let hybrid = HybridSolver::default();
217 assert!((hybrid.ga_fraction - 0.4).abs() < 1e-10);
218 assert!((hybrid.tabu_fraction - 0.3).abs() < 1e-10);
219 assert!((hybrid.aco_fraction - 0.3).abs() < 1e-10);
220 assert_eq!(hybrid.ga_population, 30);
221 assert_eq!(hybrid.refine_top_k, 3);
222 }
223
224 #[test]
225 fn test_hybrid_builder() {
226 let hybrid = HybridSolver::new()
227 .with_ga_fraction(0.5)
228 .with_tabu_fraction(0.25)
229 .with_aco_fraction(0.25)
230 .with_ga_population(50)
231 .with_refine_top_k(5)
232 .with_seed(42);
233
234 assert!((hybrid.ga_fraction - 0.5).abs() < 1e-10);
235 assert!((hybrid.tabu_fraction - 0.25).abs() < 1e-10);
236 assert!((hybrid.aco_fraction - 0.25).abs() < 1e-10);
237 assert_eq!(hybrid.ga_population, 50);
238 assert_eq!(hybrid.refine_top_k, 5);
239 assert_eq!(hybrid.seed, Some(42));
240 }
241
242 #[test]
243 fn test_hybrid_solves_square() {
244 let instance = square_instance();
245 let mut solver = HybridSolver::new().with_seed(42).with_ga_population(15);
246
247 let solution = solver
248 .solve(&instance, Budget::Iterations(100))
249 .expect("should solve");
250
251 assert!(solution.length <= 5.0, "Length {} > 5.0", solution.length);
253 assert_eq!(solution.tour.len(), 4);
254 assert!(instance.validate_tour(&solution.tour).is_ok());
255 }
256
257 #[test]
258 fn test_hybrid_solves_pentagon() {
259 let instance = pentagon_instance();
260 let mut solver = HybridSolver::new().with_seed(42).with_ga_population(20);
261
262 let solution = solver
263 .solve(&instance, Budget::Iterations(100))
264 .expect("should solve");
265
266 assert_eq!(solution.tour.len(), 5);
267 assert!(instance.validate_tour(&solution.tour).is_ok());
268 }
269
270 #[test]
271 fn test_hybrid_solves_larger_instance() {
272 let instance = random_instance(20, 123);
273 let mut solver = HybridSolver::new().with_seed(42).with_ga_population(25);
274
275 let solution = solver
276 .solve(&instance, Budget::Iterations(200))
277 .expect("should solve");
278
279 assert_eq!(solution.tour.len(), 20);
280 assert!(instance.validate_tour(&solution.tour).is_ok());
281 assert!(solution.length > 0.0);
282 }
283
284 #[test]
285 fn test_hybrid_deterministic_with_seed() {
286 let instance = pentagon_instance();
287
288 let mut solver1 = HybridSolver::new().with_seed(42).with_ga_population(15);
289 let mut solver2 = HybridSolver::new().with_seed(42).with_ga_population(15);
290
291 let solution1 = solver1
292 .solve(&instance, Budget::Iterations(50))
293 .expect("should solve");
294 let solution2 = solver2
295 .solve(&instance, Budget::Iterations(50))
296 .expect("should solve");
297
298 assert!((solution1.length - solution2.length).abs() < 1e-10);
299 }
300
301 #[test]
302 fn test_hybrid_tracks_history() {
303 let instance = square_instance();
304 let mut solver = HybridSolver::new().with_seed(42).with_ga_population(15);
305
306 let solution = solver
307 .solve(&instance, Budget::Iterations(50))
308 .expect("should solve");
309
310 assert!(!solution.history.is_empty());
311 }
312
313 #[test]
314 fn test_hybrid_normalize_fractions() {
315 let solver = HybridSolver::new()
316 .with_ga_fraction(0.6)
317 .with_tabu_fraction(0.2)
318 .with_aco_fraction(0.2);
319
320 let (ga, tabu, aco) = solver.normalize_fractions();
321 assert!((ga + tabu + aco - 1.0).abs() < 1e-10);
322 assert!((ga - 0.6).abs() < 1e-10);
323 }
324
325 #[test]
326 fn test_hybrid_normalize_zero_fractions() {
327 let solver = HybridSolver::new()
328 .with_ga_fraction(0.0)
329 .with_tabu_fraction(0.0)
330 .with_aco_fraction(0.0);
331
332 let (ga, tabu, aco) = solver.normalize_fractions();
333 assert!((ga + tabu + aco - 1.0).abs() < 1e-10);
335 }
336
337 #[test]
338 fn test_hybrid_name() {
339 let solver = HybridSolver::new();
340 assert_eq!(solver.name(), "Hybrid (GA+Tabu+ACO)");
341 }
342
343 #[test]
344 fn test_hybrid_counts_evaluations() {
345 let instance = square_instance();
346 let mut solver = HybridSolver::new().with_seed(42).with_ga_population(10);
347
348 let solution = solver
349 .solve(&instance, Budget::Iterations(30))
350 .expect("should solve");
351
352 assert!(solution.evaluations > 0);
354 }
355
356 #[test]
357 fn test_hybrid_improves_solution() {
358 let instance = random_instance(15, 999);
360
361 let mut solver = HybridSolver::new().with_seed(42).with_ga_population(20);
362
363 let solution = solver
364 .solve(&instance, Budget::Iterations(150))
365 .expect("should solve");
366
367 let first = solution.history[0];
369 let last = solution.history[solution.history.len() - 1];
370 assert!(last <= first);
371 }
372}