problemreductions/testing/
macros.rs

1//! Testing macros for problem implementations.
2
3/// Generate standard tests for a graph problem using the template.
4///
5/// This macro generates tests for:
6/// - Problem creation
7/// - Solution validity
8/// - Brute force solving (for small instances)
9/// - CSP interface
10/// - Metadata (if ProblemMetadata is implemented)
11///
12/// # Example
13///
14/// ```rust,ignore
15/// use problemreductions::testing::graph_problem_tests;
16/// use problemreductions::models::graph::{IndependentSetT, IndependentSetConstraint};
17///
18/// graph_problem_tests! {
19///     problem_type: IndependentSetT<i32>,
20///     constraint_type: IndependentSetConstraint,
21///     test_cases: [
22///         // (name, num_vertices, edges, valid_solution, expected_size, is_maximization)
23///         (triangle, 3, [(0, 1), (1, 2), (0, 2)], [1, 0, 0], 1, true),
24///         (path3, 3, [(0, 1), (1, 2)], [1, 0, 1], 2, true),
25///     ]
26/// }
27/// ```
28#[macro_export]
29macro_rules! graph_problem_tests {
30    (
31        problem_type: $problem:ty,
32        constraint_type: $constraint:ty,
33        test_cases: [
34            $(
35                ($name:ident, $n:expr, [$($edge:expr),*], [$($sol:expr),*], $size:expr, $is_max:expr)
36            ),* $(,)?
37        ]
38    ) => {
39        mod generated_tests {
40            use super::*;
41            use $crate::prelude::*;
42            use $crate::registry::ProblemMetadata;
43
44            $(
45                mod $name {
46                    use super::*;
47
48                    fn create_problem() -> $problem {
49                        <$problem>::new($n, vec![$($edge),*])
50                    }
51
52                    #[test]
53                    fn test_creation() {
54                        let problem = create_problem();
55                        assert_eq!(problem.num_variables(), $n);
56                        assert_eq!(problem.num_flavors(), 2);
57                    }
58
59                    #[test]
60                    fn test_solution_validity() {
61                        let problem = create_problem();
62                        let solution = vec![$($sol),*];
63                        let result = problem.solution_size(&solution);
64                        assert!(result.is_valid, "Solution should be valid");
65                        assert_eq!(result.size, $size, "Solution size mismatch");
66                    }
67
68                    #[test]
69                    fn test_energy_mode() {
70                        let problem = create_problem();
71                        if $is_max {
72                            assert!(problem.energy_mode().is_maximization());
73                        } else {
74                            assert!(problem.energy_mode().is_minimization());
75                        }
76                    }
77
78                    #[test]
79                    fn test_csp_interface() {
80                        let problem = create_problem();
81                        let solution = vec![$($sol),*];
82
83                        // Check constraints are generated
84                        let constraints = problem.constraints();
85                        let edge_count = vec![$($edge),*].len();
86                        assert_eq!(constraints.len(), edge_count);
87
88                        // Check objectives are generated
89                        let objectives = problem.objectives();
90                        assert_eq!(objectives.len(), $n);
91
92                        // Check is_satisfied matches solution_size validity
93                        assert_eq!(
94                            problem.is_satisfied(&solution),
95                            problem.solution_size(&solution).is_valid
96                        );
97                    }
98
99                    #[test]
100                    fn test_brute_force() {
101                        if $n <= 15 {
102                            let problem = create_problem();
103                            let solver = BruteForce::new();
104                            let solutions = solver.find_best(&problem);
105
106                            // All solutions should be valid
107                            for sol in &solutions {
108                                assert!(problem.solution_size(sol).is_valid);
109                            }
110
111                            // All solutions should have the same (optimal) size
112                            if solutions.len() > 1 {
113                                let first_size = problem.solution_size(&solutions[0]).size;
114                                for sol in &solutions[1..] {
115                                    assert_eq!(problem.solution_size(sol).size, first_size);
116                                }
117                            }
118                        }
119                    }
120                }
121            )*
122
123            #[test]
124            fn test_problem_metadata() {
125                let info = <$problem as ProblemMetadata>::problem_info();
126                assert!(!info.name.is_empty());
127                assert!(!info.description.is_empty());
128
129                let category = <$problem as ProblemMetadata>::category();
130                assert_eq!(category.name(), "graph");
131            }
132        }
133    };
134}
135
136/// Generate tests for verifying complement relationships between problems.
137///
138/// # Example
139///
140/// ```rust,ignore
141/// complement_test! {
142///     name: is_vc_complement,
143///     problem_a: IndependentSet<i32>,
144///     problem_b: VertexCovering<i32>,
145///     test_graphs: [
146///         (3, [(0, 1), (1, 2)]),
147///         (4, [(0, 1), (1, 2), (2, 3), (0, 3)]),
148///     ]
149/// }
150/// ```
151#[macro_export]
152macro_rules! complement_test {
153    (
154        name: $name:ident,
155        problem_a: $prob_a:ty,
156        problem_b: $prob_b:ty,
157        test_graphs: [
158            $(($n:expr, [$($edge:expr),*])),* $(,)?
159        ]
160    ) => {
161        #[test]
162        fn $name() {
163            use $crate::prelude::*;
164
165            $(
166                {
167                    let edges = vec![$($edge),*];
168                    let n = $n;
169
170                    let problem_a = <$prob_a>::new(n, edges.clone());
171                    let problem_b = <$prob_b>::new(n, edges);
172
173                    let solver = BruteForce::new();
174                    let solutions_a = solver.find_best(&problem_a);
175                    let solutions_b = solver.find_best(&problem_b);
176
177                    // Get optimal sizes
178                    let size_a: usize = solutions_a[0].iter().sum();
179                    let size_b: usize = solutions_b[0].iter().sum();
180
181                    // For complement problems: size_a + size_b = n
182                    assert_eq!(
183                        size_a + size_b,
184                        n,
185                        "Complement relationship failed for graph with {} vertices",
186                        n
187                    );
188
189                    // Verify that complement of solution_a is valid for problem_b
190                    for sol_a in &solutions_a {
191                        let complement: Vec<usize> = sol_a.iter().map(|&x| 1 - x).collect();
192                        assert!(
193                            problem_b.solution_size(&complement).is_valid,
194                            "Complement of A solution should be valid for B"
195                        );
196                    }
197                }
198            )*
199        }
200    };
201}
202
203/// Quick test for a single problem instance.
204///
205/// # Example
206///
207/// ```rust,ignore
208/// quick_problem_test!(
209///     IndependentSet<i32>,
210///     new(3, vec![(0, 1), (1, 2)]),
211///     solution: [1, 0, 1],
212///     expected_size: 2,
213///     is_valid: true
214/// );
215/// ```
216#[macro_export]
217macro_rules! quick_problem_test {
218    (
219        $problem_type:ty,
220        $constructor:ident($($args:expr),*),
221        solution: [$($sol:expr),*],
222        expected_size: $size:expr,
223        is_valid: $valid:expr
224    ) => {
225        {
226            let problem = <$problem_type>::$constructor($($args),*);
227            let solution = vec![$($sol),*];
228            let result = problem.solution_size(&solution);
229            assert_eq!(result.size, $size);
230            assert_eq!(result.is_valid, $valid);
231        }
232    };
233}
234
235#[cfg(test)]
236mod tests {
237    use crate::models::graph::{IndependentSetT, VertexCoverT};
238    use crate::prelude::*;
239    use crate::topology::SimpleGraph;
240
241    // Test the quick_problem_test macro
242    #[test]
243    fn test_quick_problem_test_macro() {
244        quick_problem_test!(
245            IndependentSetT<SimpleGraph, i32>,
246            new(3, vec![(0, 1), (1, 2)]),
247            solution: [1, 0, 1],
248            expected_size: 2,
249            is_valid: true
250        );
251
252        quick_problem_test!(
253            IndependentSetT<SimpleGraph, i32>,
254            new(3, vec![(0, 1), (1, 2)]),
255            solution: [1, 1, 0],
256            expected_size: 2,
257            is_valid: false
258        );
259    }
260
261    // Test the complement_test macro
262    complement_test! {
263        name: test_is_vc_complement,
264        problem_a: IndependentSetT<SimpleGraph, i32>,
265        problem_b: VertexCoverT<SimpleGraph, i32>,
266        test_graphs: [
267            (3, [(0, 1), (1, 2)]),
268            (4, [(0, 1), (1, 2), (2, 3), (0, 3)]),
269        ]
270    }
271}