1use super::engine::{
21 CriterionResult, DemoEngine, DemoError, DemoMeta, FalsificationCriterion, MetamorphicRelation,
22 MrResult, Severity,
23};
24use super::tsp_instance::{TspAlgorithmConfig, TspCity, TspMeta};
25use crate::engine::rng::SimRng;
26use serde::{Deserialize, Serialize};
27use std::collections::hash_map::DefaultHasher;
28use std::hash::{Hash, Hasher};
29
30#[derive(Debug, Clone, Serialize, Deserialize)]
36pub struct SimulationConfig {
37 #[serde(rename = "type")]
38 pub sim_type: String,
39 pub name: String,
40}
41
42#[derive(Debug, Clone, Serialize, Deserialize)]
44pub struct TspConfig {
45 pub simulation: SimulationConfig,
47 pub meta: TspMeta,
49 pub cities: Vec<TspCity>,
51 pub matrix: Vec<Vec<u32>>,
53 #[serde(default)]
55 pub algorithm: TspAlgorithmConfig,
56}
57
58#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
67pub struct TspState {
68 pub tour: Vec<usize>,
70 pub tour_length: u32,
72 pub best_tour: Vec<usize>,
74 pub best_tour_length: u32,
76 pub restart_count: u32,
78 pub two_opt_count: u64,
80 pub step_count: u64,
82 pub is_converged: bool,
84}
85
86impl TspState {
87 #[must_use]
89 pub fn compute_hash(&self) -> u64 {
90 let mut hasher = DefaultHasher::new();
91 self.tour.hash(&mut hasher);
92 self.tour_length.hash(&mut hasher);
93 self.best_tour.hash(&mut hasher);
94 self.best_tour_length.hash(&mut hasher);
95 self.restart_count.hash(&mut hasher);
96 self.step_count.hash(&mut hasher);
97 hasher.finish()
98 }
99}
100
101#[derive(Debug, Clone)]
103pub struct TspStepResult {
104 pub improved: bool,
106 pub tour_length: u32,
108 pub optimality_gap: Option<f64>,
110}
111
112#[derive(Debug, Clone)]
123pub struct TspEngine {
124 config: TspConfig,
126 n: usize,
128 distances: Vec<Vec<u32>>,
130 tour: Vec<usize>,
132 tour_length: u32,
134 best_tour: Vec<usize>,
136 best_tour_length: u32,
138 rcl_size: usize,
140 restart_count: u32,
142 max_restarts: u32,
144 two_opt_count: u64,
146 step_count: u64,
148 stagnation_count: u32,
150 rng: SimRng,
152 seed: u64,
154 demo_meta: DemoMeta,
156}
157
158impl TspEngine {
159 fn calculate_tour_length(&self, tour: &[usize]) -> u32 {
161 if tour.is_empty() {
162 return 0;
163 }
164 let mut total = 0u32;
165 for i in 0..tour.len() {
166 let from = tour[i];
167 let to = tour[(i + 1) % tour.len()];
168 total += self.distances[from][to];
169 }
170 total
171 }
172
173 fn gen_range(&mut self, max: usize) -> usize {
175 if max == 0 {
176 return 0;
177 }
178 (self.rng.gen_u64() as usize) % max
179 }
180
181 fn construct_greedy_tour(&mut self) -> Vec<usize> {
183 let mut visited = vec![false; self.n];
184 let mut tour = Vec::with_capacity(self.n);
185
186 let start = self.gen_range(self.n);
188 tour.push(start);
189 visited[start] = true;
190
191 while tour.len() < self.n {
192 let current = tour.last().copied().unwrap_or(0);
194
195 let mut candidates: Vec<(usize, u32)> = (0..self.n)
197 .filter(|&c| !visited[c])
198 .map(|c| (c, self.distances[current][c]))
199 .collect();
200 candidates.sort_by_key(|&(_, d)| d);
201
202 let rcl_size = self.rcl_size.min(candidates.len());
204 let idx = self.gen_range(rcl_size);
205 let next = candidates[idx].0;
206
207 tour.push(next);
208 visited[next] = true;
209 }
210
211 tour
212 }
213
214 fn two_opt(&self, tour: &mut [usize]) -> u64 {
216 let mut improvements = 0u64;
217 let mut improved = true;
218
219 while improved {
220 improved = false;
221 for i in 0..self.n - 1 {
222 for j in i + 2..self.n {
223 if j == i + 1 || (i == 0 && j == self.n - 1) {
225 continue;
226 }
227
228 let a = tour[i];
230 let b = tour[i + 1];
231 let c = tour[j];
232 let d = tour[(j + 1) % self.n];
233
234 let current = self.distances[a][b] + self.distances[c][d];
235 let swapped = self.distances[a][c] + self.distances[b][d];
236
237 if swapped < current {
238 tour[i + 1..=j].reverse();
240 improved = true;
241 improvements += 1;
242 }
243 }
244 }
245 }
246
247 improvements
248 }
249
250 fn do_restart(&mut self) -> bool {
252 let mut tour = self.construct_greedy_tour();
254
255 let improvements = self.two_opt(&mut tour);
257 self.two_opt_count += improvements;
258
259 let length = self.calculate_tour_length(&tour);
261
262 self.tour = tour.clone();
264 self.tour_length = length;
265 self.restart_count += 1;
266
267 let improved = length < self.best_tour_length;
269 if improved {
270 self.best_tour = tour;
271 self.best_tour_length = length;
272 self.stagnation_count = 0;
273 } else {
274 self.stagnation_count += 1;
275 }
276
277 improved
278 }
279}
280
281impl DemoEngine for TspEngine {
282 type Config = TspConfig;
283 type State = TspState;
284 type StepResult = TspStepResult;
285
286 fn from_yaml(yaml: &str) -> Result<Self, DemoError> {
287 let config: TspConfig = serde_yaml::from_str(yaml)?;
288 Ok(Self::from_config(config))
289 }
290
291 fn from_config(config: Self::Config) -> Self {
292 let n = config.cities.len();
293 let distances = config.matrix.clone();
294 let seed = config.algorithm.params.seed;
295 let rcl_size = config.algorithm.params.rcl_size;
296 let max_restarts = config.algorithm.params.restarts as u32;
297
298 let demo_meta = DemoMeta {
299 id: config.meta.id.clone(),
300 version: config.meta.version.clone(),
301 demo_type: "tsp".to_string(),
302 description: config.meta.description.clone(),
303 author: config.meta.source.clone(),
304 created: String::new(),
305 };
306
307 let tour: Vec<usize> = (0..n).collect();
309 let tour_length = u32::MAX;
310 let best_tour = tour.clone();
311 let best_tour_length = u32::MAX;
312
313 let mut engine = Self {
314 config,
315 n,
316 distances,
317 tour,
318 tour_length,
319 best_tour,
320 best_tour_length,
321 rcl_size,
322 restart_count: 0,
323 max_restarts,
324 two_opt_count: 0,
325 step_count: 0,
326 stagnation_count: 0,
327 rng: SimRng::new(seed),
328 seed,
329 demo_meta,
330 };
331
332 engine.tour = engine.construct_greedy_tour();
334 let improvements = engine.two_opt(&mut engine.tour.clone());
335 engine.two_opt_count = improvements;
336 engine.tour_length = engine.calculate_tour_length(&engine.tour);
337 engine.best_tour = engine.tour.clone();
338 engine.best_tour_length = engine.tour_length;
339
340 engine
341 }
342
343 fn config(&self) -> &Self::Config {
344 &self.config
345 }
346
347 fn reset(&mut self) {
348 self.reset_with_seed(self.seed);
349 }
350
351 fn reset_with_seed(&mut self, seed: u64) {
352 self.rng = SimRng::new(seed);
353 self.seed = seed;
354 self.restart_count = 0;
355 self.two_opt_count = 0;
356 self.step_count = 0;
357 self.stagnation_count = 0;
358
359 self.tour = self.construct_greedy_tour();
361 let improvements = self.two_opt(&mut self.tour.clone());
362 self.two_opt_count = improvements;
363 self.tour_length = self.calculate_tour_length(&self.tour);
364 self.best_tour = self.tour.clone();
365 self.best_tour_length = self.tour_length;
366 }
367
368 fn step(&mut self) -> Self::StepResult {
369 self.step_count += 1;
370
371 let improved = self.do_restart();
373
374 TspStepResult {
375 improved,
376 tour_length: self.tour_length,
377 optimality_gap: self
378 .config
379 .meta
380 .optimal_known
381 .map(|opt| (f64::from(self.best_tour_length) - f64::from(opt)) / f64::from(opt)),
382 }
383 }
384
385 fn is_complete(&self) -> bool {
386 self.restart_count >= self.max_restarts || self.stagnation_count >= 20
388 }
389
390 fn state(&self) -> Self::State {
391 TspState {
392 tour: self.tour.clone(),
393 tour_length: self.tour_length,
394 best_tour: self.best_tour.clone(),
395 best_tour_length: self.best_tour_length,
396 restart_count: self.restart_count,
397 two_opt_count: self.two_opt_count,
398 step_count: self.step_count,
399 is_converged: self.is_complete(),
400 }
401 }
402
403 fn restore(&mut self, state: &Self::State) {
404 self.tour.clone_from(&state.tour);
405 self.tour_length = state.tour_length;
406 self.best_tour.clone_from(&state.best_tour);
407 self.best_tour_length = state.best_tour_length;
408 self.restart_count = state.restart_count;
409 self.two_opt_count = state.two_opt_count;
410 self.step_count = state.step_count;
411 }
412
413 fn step_count(&self) -> u64 {
414 self.step_count
415 }
416
417 fn seed(&self) -> u64 {
418 self.seed
419 }
420
421 fn meta(&self) -> &DemoMeta {
422 &self.demo_meta
423 }
424
425 fn falsification_criteria(&self) -> Vec<FalsificationCriterion> {
426 vec![FalsificationCriterion {
427 id: "TSP-VALID-001".to_string(),
428 name: "Valid tour".to_string(),
429 metric: "tour_validity".to_string(),
430 threshold: 1.0,
431 condition: "all cities visited exactly once".to_string(),
432 tolerance: 0.0,
433 severity: Severity::Critical,
434 }]
435 }
436
437 fn evaluate_criteria(&self) -> Vec<CriterionResult> {
438 let mut visited = vec![false; self.n];
440 let mut valid = self.best_tour.len() == self.n;
441 for &city in &self.best_tour {
442 if city >= self.n || visited[city] {
443 valid = false;
444 break;
445 }
446 visited[city] = true;
447 }
448
449 vec![CriterionResult {
450 id: "TSP-VALID-001".to_string(),
451 passed: valid,
452 actual: if valid { 1.0 } else { 0.0 },
453 expected: 1.0,
454 message: if valid {
455 format!("Valid tour of length {}", self.best_tour_length)
456 } else {
457 "Invalid tour".to_string()
458 },
459 severity: Severity::Critical,
460 }]
461 }
462
463 fn metamorphic_relations(&self) -> Vec<MetamorphicRelation> {
464 vec![MetamorphicRelation {
465 id: "MR-PERMUTATION".to_string(),
466 description: "Relabeling cities preserves tour length".to_string(),
467 source_transform: "permute_city_labels".to_string(),
468 expected_relation: "tour_length_unchanged".to_string(),
469 tolerance: 0.0,
470 }]
471 }
472
473 fn verify_mr(&self, mr: &MetamorphicRelation) -> MrResult {
474 match mr.id.as_str() {
475 "MR-PERMUTATION" => {
476 let recalculated = self.calculate_tour_length(&self.best_tour);
478 let passed = recalculated == self.best_tour_length;
479 MrResult {
480 id: mr.id.clone(),
481 passed,
482 message: format!(
483 "Stored: {}, Recalculated: {}",
484 self.best_tour_length, recalculated
485 ),
486 source_value: Some(f64::from(self.best_tour_length)),
487 followup_value: Some(f64::from(recalculated)),
488 }
489 }
490 _ => MrResult {
491 id: mr.id.clone(),
492 passed: false,
493 message: format!("Unknown metamorphic relation: {}", mr.id),
494 source_value: None,
495 followup_value: None,
496 },
497 }
498 }
499}
500
501#[cfg(test)]
506mod tests {
507 use super::*;
508
509 fn make_test_yaml() -> String {
510 r#"
511simulation:
512 type: tsp
513 name: "Test TSP"
514
515meta:
516 id: "TEST-TSP-001"
517 version: "1.0.0"
518 description: "Test TSP instance"
519
520cities:
521 - id: 0
522 name: "A"
523 alias: "A"
524 coords: { lat: 0.0, lon: 0.0 }
525 - id: 1
526 name: "B"
527 alias: "B"
528 coords: { lat: 0.0, lon: 1.0 }
529 - id: 2
530 name: "C"
531 alias: "C"
532 coords: { lat: 1.0, lon: 0.0 }
533 - id: 3
534 name: "D"
535 alias: "D"
536 coords: { lat: 1.0, lon: 1.0 }
537
538matrix:
539 - [0, 1, 1, 2]
540 - [1, 0, 2, 1]
541 - [1, 2, 0, 1]
542 - [2, 1, 1, 0]
543
544algorithm:
545 method: "grasp"
546 params:
547 rcl_size: 2
548 restarts: 10
549 two_opt: true
550 seed: 42
551"#
552 .to_string()
553 }
554
555 #[test]
556 fn test_from_yaml() {
557 let yaml = make_test_yaml();
558 let engine = TspEngine::from_yaml(&yaml);
559 assert!(engine.is_ok(), "Failed to parse YAML: {:?}", engine.err());
560 }
561
562 #[test]
563 fn test_deterministic_state() {
564 let yaml = make_test_yaml();
565 let mut engine1 = TspEngine::from_yaml(&yaml).unwrap();
566 let mut engine2 = TspEngine::from_yaml(&yaml).unwrap();
567
568 for _ in 0..5 {
569 engine1.step();
570 engine2.step();
571 }
572
573 assert_eq!(
574 engine1.state(),
575 engine2.state(),
576 "State divergence detected"
577 );
578 }
579
580 #[test]
581 fn test_reset_replay() {
582 let yaml = make_test_yaml();
583 let mut engine = TspEngine::from_yaml(&yaml).unwrap();
584
585 for _ in 0..5 {
587 engine.step();
588 }
589 let state1 = engine.state();
590
591 engine.reset();
593 for _ in 0..5 {
594 engine.step();
595 }
596 let state2 = engine.state();
597
598 assert_eq!(state1, state2, "Reset did not produce identical replay");
599 }
600
601 #[test]
602 fn test_valid_tour() {
603 let yaml = make_test_yaml();
604 let mut engine = TspEngine::from_yaml(&yaml).unwrap();
605
606 while !engine.is_complete() {
608 engine.step();
609 }
610
611 let results = engine.evaluate_criteria();
612 assert!(!results.is_empty());
613 assert!(results[0].passed, "Tour should be valid");
614 }
615
616 #[test]
617 fn test_tour_improves() {
618 let yaml = make_test_yaml();
619 let mut engine = TspEngine::from_yaml(&yaml).unwrap();
620
621 let initial_length = engine.best_tour_length;
622
623 for _ in 0..10 {
625 engine.step();
626 }
627
628 assert!(
631 engine.best_tour_length <= initial_length,
632 "Tour should improve or stay same"
633 );
634 }
635
636 #[test]
637 fn test_step_count() {
638 let yaml = make_test_yaml();
639 let mut engine = TspEngine::from_yaml(&yaml).unwrap();
640
641 assert_eq!(engine.step_count(), 0);
642 engine.step();
643 assert_eq!(engine.step_count(), 1);
644 }
645
646 #[test]
647 fn test_seed() {
648 let yaml = make_test_yaml();
649 let engine = TspEngine::from_yaml(&yaml).unwrap();
650 assert_eq!(engine.seed(), 42);
651 }
652
653 #[test]
654 fn test_meta() {
655 let yaml = make_test_yaml();
656 let engine = TspEngine::from_yaml(&yaml).unwrap();
657 assert_eq!(engine.meta().id, "TEST-TSP-001");
658 assert_eq!(engine.meta().demo_type, "tsp");
659 }
660
661 #[test]
662 fn test_state_hash() {
663 let yaml = make_test_yaml();
664 let mut engine = TspEngine::from_yaml(&yaml).unwrap();
665 engine.step();
666
667 let state = engine.state();
668 let hash1 = state.compute_hash();
669 let hash2 = state.compute_hash();
670
671 assert_eq!(hash1, hash2, "Hash should be deterministic");
672 }
673
674 #[test]
675 fn test_is_complete() {
676 let yaml = make_test_yaml();
677 let mut engine = TspEngine::from_yaml(&yaml).unwrap();
678
679 while !engine.is_complete() {
681 engine.step();
682 }
683
684 assert!(engine.is_complete());
685 }
686}