From 5bd86ac23fbe878e0fd1ae528804bf6118231a9f Mon Sep 17 00:00:00 2001 From: Rutherther Date: Fri, 31 Oct 2025 11:18:27 +0100 Subject: [PATCH] tests(tsp): add tests for reverse subsequence --- codes/tsp_hw01/src/tsp.rs | 177 +++++++++++++++++++++++++++++++++++++- 1 file changed, 175 insertions(+), 2 deletions(-) diff --git a/codes/tsp_hw01/src/tsp.rs b/codes/tsp_hw01/src/tsp.rs index f35838658ab6282f60fea71d62f65e11920f9b97..ed0018ce3ccc2db3b1f2a38fff1de9b78f0f8dfd 100644 --- a/codes/tsp_hw01/src/tsp.rs +++ b/codes/tsp_hw01/src/tsp.rs @@ -517,9 +517,182 @@ mod tests { let offsprings = crossover.crossover(&parents, pairings, &mut MockRng); let offspring = offsprings.into_iter().next().unwrap(); - eprintln!("{:?}", offspring); - + // NOTE: this sort of relies on the implementation of the algorithm (when there are multiple possibilities + // currently the algorithm always chooses last). It's possible this test will break due to valid changes to the algorithm. assert_eq!(vec![0usize, 1, 3, 4, 5, 2], offspring.permutation.into_iter().copied().collect::>()) } + + #[test] + fn test_reverse_subsequence_perturbation_behavior() { + let perturbation = ReverseSubsequencePerturbation::>::new(); + + // Test multiple specific seeds to get predictable behavior + // We'll try different seeds until we find ones that give us the patterns we want to test + + // Test case 1: Try to find a seed that reverses a middle subsequence + let mut found_middle_reverse = false; + for seed in 0..1000 { + let mut rng = StdRng::seed_from_u64(seed); + let mut chromosome = NodePermutation::> { + permutation: SVector::::from_vec(vec![0, 1, 2, 3, 4, 5]) + }; + let original = chromosome.clone(); + + perturbation.perturb(&mut chromosome, &mut rng); + + // Check if it's a valid reverse pattern and not the whole array or single element + let result: Vec = chromosome.permutation.into_iter().copied().collect(); + if result != vec![0, 1, 2, 3, 4, 5] && // Changed + result != vec![5, 4, 3, 2, 1, 0] && // Not whole array reverse + TSPInstance::verify_solution(&chromosome) { + found_middle_reverse = true; + break; + } + } + assert!(found_middle_reverse, "Should find at least one case of partial subsequence reversal"); + } + + #[test] + fn test_reverse_subsequence_perturbation_deterministic_seed() { + let perturbation = ReverseSubsequencePerturbation::>::new(); + + // Use a specific seed that we know produces a certain result + let mut rng1 = StdRng::seed_from_u64(42); + let mut chromosome1 = NodePermutation::> { + permutation: SVector::::from_vec(vec![0, 1, 2, 3, 4, 5]) + }; + perturbation.perturb(&mut chromosome1, &mut rng1); + + // Same seed should produce same result + let mut rng2 = StdRng::seed_from_u64(42); + let mut chromosome2 = NodePermutation::> { + permutation: SVector::::from_vec(vec![0, 1, 2, 3, 4, 5]) + }; + perturbation.perturb(&mut chromosome2, &mut rng2); + + assert_eq!(chromosome1.permutation, chromosome2.permutation); + assert!(TSPInstance::verify_solution(&chromosome1)); + assert!(TSPInstance::verify_solution(&chromosome2)); + } + + #[test] + fn test_reverse_subsequence_perturbation_different_initial_permutations() { + let perturbation = ReverseSubsequencePerturbation::>::new(); + + // Test with a non-sequential initial permutation + let mut rng = StdRng::seed_from_u64(123); + let mut chromosome = NodePermutation::> { + permutation: SVector::::from_vec(vec![2, 0, 4, 1, 3]) + }; + let original_elements: std::collections::HashSet = + chromosome.permutation.iter().copied().collect(); + + perturbation.perturb(&mut chromosome, &mut rng); + + // Verify all original elements are still present + let new_elements: std::collections::HashSet = + chromosome.permutation.iter().copied().collect(); + assert_eq!(original_elements, new_elements); + + // Verify it's still a valid permutation + assert!(TSPInstance::verify_solution(&chromosome)); + } + + #[test] + fn test_reverse_subsequence_perturbation_edge_cases() { + let perturbation = ReverseSubsequencePerturbation::>::new(); + + // Test with minimum size permutation (2 elements) + let mut rng = StdRng::seed_from_u64(456); + let mut chromosome = NodePermutation::> { + permutation: SVector::::from_vec(vec![0, 1]) + }; + + perturbation.perturb(&mut chromosome, &mut rng); + + let result: Vec = chromosome.permutation.into_iter().copied().collect(); + // With 2 elements, it should either stay [0,1] or become [1,0] + assert!(result == vec![0, 1] || result == vec![1, 0]); + assert!(TSPInstance::verify_solution(&chromosome)); + } + + #[test] + fn test_reverse_subsequence_perturbation_is_reversible() { + let perturbation = ReverseSubsequencePerturbation::>::new(); + + // Any sequence of reversals should be reversible + let mut rng = StdRng::seed_from_u64(789); + let original = NodePermutation::> { + permutation: SVector::::from_vec(vec![0, 1, 2, 3, 4, 5]) + }; + let mut chromosome = original.clone(); + + // Apply perturbation twice with same seed (reset RNG) + perturbation.perturb(&mut chromosome, &mut rng); + let after_first = chromosome.clone(); + + // Since we can't easily reverse the exact operation, at least verify + // that multiple applications maintain the permutation property + for _ in 0..10 { + perturbation.perturb(&mut chromosome, &mut rng); + assert!(TSPInstance::verify_solution(&chromosome)); + } + } + + #[test] + fn test_reverse_subsequence_perturbation_preserves_elements() { + let perturbation = ReverseSubsequencePerturbation::>::new(); + let initializer = TSPRandomInitializer::>::new(); + + let mut rng = StdRng::seed_from_u64(42); + + // Test with multiple random permutations + for _ in 0..50 { + let mut chromosome = initializer.initialize_single(Const::<10>, &mut rng); + let original_elements: std::collections::HashSet = chromosome.permutation.iter().copied().collect(); + + perturbation.perturb(&mut chromosome, &mut rng); + + // Verify all elements are still present + let new_elements: std::collections::HashSet = chromosome.permutation.iter().copied().collect(); + assert_eq!(original_elements, new_elements); + + // Verify it's still a valid permutation + assert!(TSPInstance::verify_solution(&chromosome)); + } + } + + #[test] + fn test_reverse_subsequence_perturbation_actually_changes_permutation() { + let perturbation = ReverseSubsequencePerturbation::>::new(); + let mut rng = StdRng::seed_from_u64(12345); + + // Test that the perturbation actually changes the permutation (with high probability) + let mut changes_detected = 0; + let total_tests = 100; + + for _ in 0..total_tests { + let mut chromosome = NodePermutation::> { + permutation: SVector::::from_vec(vec![0, 1, 2, 3, 4, 5, 6, 7]) + }; + let original = chromosome.clone(); + + perturbation.perturb(&mut chromosome, &mut rng); + + if chromosome.permutation != original.permutation { + changes_detected += 1; + } + + // Always verify it's still a valid permutation + assert!(TSPInstance::verify_solution(&chromosome)); + } + + // We expect at least 85% of random perturbations to actually change the permutation + // (only fails if start == end randomly, which should be rare) + assert!(changes_detected >= 85, + "Expected at least 85 changes out of {} tests, but got {}", + total_tests, changes_detected); + } + }