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}