~ruther/ctu-fee-eoa

5bd86ac23fbe878e0fd1ae528804bf6118231a9f — Rutherther a month ago 98eb8ce
tests(tsp): add tests for reverse subsequence
1 files changed, 175 insertions(+), 2 deletions(-)

M codes/tsp_hw01/src/tsp.rs
M codes/tsp_hw01/src/tsp.rs => codes/tsp_hw01/src/tsp.rs +175 -2
@@ 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::<Vec<_>>())
    }


    #[test]
    fn test_reverse_subsequence_perturbation_behavior() {
        let perturbation = ReverseSubsequencePerturbation::<Const<6>>::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::<Const<6>> {
                permutation: SVector::<usize, 6>::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<usize> = 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::<Const<6>>::new();

        // Use a specific seed that we know produces a certain result
        let mut rng1 = StdRng::seed_from_u64(42);
        let mut chromosome1 = NodePermutation::<Const<6>> {
            permutation: SVector::<usize, 6>::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::<Const<6>> {
            permutation: SVector::<usize, 6>::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::<Const<5>>::new();

        // Test with a non-sequential initial permutation
        let mut rng = StdRng::seed_from_u64(123);
        let mut chromosome = NodePermutation::<Const<5>> {
            permutation: SVector::<usize, 5>::from_vec(vec![2, 0, 4, 1, 3])
        };
        let original_elements: std::collections::HashSet<usize> =
            chromosome.permutation.iter().copied().collect();

        perturbation.perturb(&mut chromosome, &mut rng);

        // Verify all original elements are still present
        let new_elements: std::collections::HashSet<usize> =
            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::<Const<2>>::new();

        // Test with minimum size permutation (2 elements)
        let mut rng = StdRng::seed_from_u64(456);
        let mut chromosome = NodePermutation::<Const<2>> {
            permutation: SVector::<usize, 2>::from_vec(vec![0, 1])
        };

        perturbation.perturb(&mut chromosome, &mut rng);

        let result: Vec<usize> = 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::<Const<6>>::new();

        // Any sequence of reversals should be reversible
        let mut rng = StdRng::seed_from_u64(789);
        let original = NodePermutation::<Const<6>> {
            permutation: SVector::<usize, 6>::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::<Const<10>>::new();
        let initializer = TSPRandomInitializer::<Const<10>>::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<usize> = chromosome.permutation.iter().copied().collect();

            perturbation.perturb(&mut chromosome, &mut rng);

            // Verify all elements are still present
            let new_elements: std::collections::HashSet<usize> = 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::<Const<8>>::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::<Const<8>> {
                permutation: SVector::<usize, 8>::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);
    }

}