problemreductions/rules/
matching_setpacking.rs

1//! Reductions between Matching and SetPacking problems.
2//!
3//! Matching -> SetPacking: Each edge becomes a set containing its two endpoint vertices.
4//! For edge (u, v), create set = {u, v}. Weights are preserved from edges.
5
6use crate::models::graph::Matching;
7use crate::models::set::SetPacking;
8use crate::rules::traits::{ReduceTo, ReductionResult};
9use crate::traits::{ConstraintSatisfactionProblem, Problem};
10use crate::types::ProblemSize;
11use num_traits::{Num, Zero};
12use std::ops::AddAssign;
13
14/// Result of reducing Matching to SetPacking.
15#[derive(Debug, Clone)]
16pub struct ReductionMatchingToSP<W> {
17    target: SetPacking<W>,
18    source_size: ProblemSize,
19}
20
21impl<W> ReductionResult for ReductionMatchingToSP<W>
22where
23    W: Clone + Default + PartialOrd + Num + Zero + AddAssign + 'static,
24{
25    type Source = Matching<W>;
26    type Target = SetPacking<W>;
27
28    fn target_problem(&self) -> &Self::Target {
29        &self.target
30    }
31
32    /// Solutions map directly: edge i in Matching = set i in SetPacking.
33    fn extract_solution(&self, target_solution: &[usize]) -> Vec<usize> {
34        target_solution.to_vec()
35    }
36
37    fn source_size(&self) -> ProblemSize {
38        self.source_size.clone()
39    }
40
41    fn target_size(&self) -> ProblemSize {
42        self.target.problem_size()
43    }
44}
45
46impl<W> ReduceTo<SetPacking<W>> for Matching<W>
47where
48    W: Clone + Default + PartialOrd + Num + Zero + AddAssign + From<i32> + 'static,
49{
50    type Result = ReductionMatchingToSP<W>;
51
52    fn reduce_to(&self) -> Self::Result {
53        let edges = self.edges();
54
55        // For each edge, create a set containing its two endpoint vertices
56        let sets: Vec<Vec<usize>> = edges.iter().map(|&(u, v, _)| vec![u, v]).collect();
57
58        // Preserve weights from edges
59        let weights = self.weights();
60
61        let target = SetPacking::with_weights(sets, weights);
62
63        ReductionMatchingToSP {
64            target,
65            source_size: self.problem_size(),
66        }
67    }
68}
69
70#[cfg(test)]
71mod tests {
72    use super::*;
73    use crate::solvers::{BruteForce, Solver};
74
75    #[test]
76    fn test_matching_to_setpacking_structure() {
77        // Path graph 0-1-2
78        let matching = Matching::<i32>::unweighted(3, vec![(0, 1), (1, 2)]);
79        let reduction = ReduceTo::<SetPacking<i32>>::reduce_to(&matching);
80        let sp = reduction.target_problem();
81
82        // Should have 2 sets (one for each edge)
83        assert_eq!(sp.num_sets(), 2);
84
85        // Sets should contain edge endpoints
86        let sets = sp.sets();
87        assert_eq!(sets[0], vec![0, 1]);
88        assert_eq!(sets[1], vec![1, 2]);
89    }
90
91    #[test]
92    fn test_matching_to_setpacking_path() {
93        // Path 0-1-2-3 with unit weights
94        let matching = Matching::<i32>::unweighted(4, vec![(0, 1), (1, 2), (2, 3)]);
95        let reduction = ReduceTo::<SetPacking<i32>>::reduce_to(&matching);
96        let sp = reduction.target_problem();
97
98        let solver = BruteForce::new();
99        let sp_solutions = solver.find_best(sp);
100
101        // Extract back to Matching solutions
102        let _matching_solutions: Vec<_> = sp_solutions
103            .iter()
104            .map(|s| reduction.extract_solution(s))
105            .collect();
106
107        // Verify against direct Matching solution
108        let direct_solutions = solver.find_best(&matching);
109
110        // Solutions should have same objective value
111        let sp_size: usize = sp_solutions[0].iter().sum();
112        let direct_size: usize = direct_solutions[0].iter().sum();
113        assert_eq!(sp_size, direct_size);
114        assert_eq!(sp_size, 2); // Max matching in path graph has 2 edges
115    }
116
117    #[test]
118    fn test_matching_to_setpacking_triangle() {
119        // Triangle graph
120        let matching = Matching::<i32>::unweighted(3, vec![(0, 1), (1, 2), (0, 2)]);
121        let reduction = ReduceTo::<SetPacking<i32>>::reduce_to(&matching);
122        let sp = reduction.target_problem();
123
124        let solver = BruteForce::new();
125        let sp_solutions = solver.find_best(sp);
126
127        // Max matching in triangle = 1 (any single edge)
128        for sol in &sp_solutions {
129            assert_eq!(sol.iter().sum::<usize>(), 1);
130        }
131
132        // Should have 3 optimal solutions (one for each edge)
133        assert_eq!(sp_solutions.len(), 3);
134    }
135
136    #[test]
137    fn test_matching_to_setpacking_weighted() {
138        // Weighted edges: heavy edge should win over multiple light edges
139        let matching = Matching::new(4, vec![(0, 1, 100), (0, 2, 1), (1, 3, 1)]);
140        let reduction = ReduceTo::<SetPacking<i32>>::reduce_to(&matching);
141        let sp = reduction.target_problem();
142
143        // Weights should be preserved
144        assert_eq!(sp.weights_ref(), &vec![100, 1, 1]);
145
146        let solver = BruteForce::new();
147        let sp_solutions = solver.find_best(sp);
148
149        // Edge 0-1 (weight 100) alone beats edges 0-2 + 1-3 (weight 2)
150        assert!(sp_solutions.contains(&vec![1, 0, 0]));
151
152        // Verify through direct Matching solution
153        let direct_solutions = solver.find_best(&matching);
154        assert_eq!(matching.solution_size(&sp_solutions[0]).size, 100);
155        assert_eq!(matching.solution_size(&direct_solutions[0]).size, 100);
156    }
157
158    #[test]
159    fn test_matching_to_setpacking_solution_extraction() {
160        let matching = Matching::<i32>::unweighted(4, vec![(0, 1), (1, 2), (2, 3)]);
161        let reduction = ReduceTo::<SetPacking<i32>>::reduce_to(&matching);
162
163        // Test solution extraction is 1:1
164        let sp_solution = vec![1, 0, 1];
165        let matching_solution = reduction.extract_solution(&sp_solution);
166        assert_eq!(matching_solution, vec![1, 0, 1]);
167
168        // Verify the extracted solution is valid for original Matching
169        assert!(matching.solution_size(&matching_solution).is_valid);
170    }
171
172    #[test]
173    fn test_matching_to_setpacking_k4() {
174        // Complete graph K4: can have perfect matching (2 edges covering all 4 vertices)
175        let matching = Matching::<i32>::unweighted(
176            4,
177            vec![(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)],
178        );
179        let reduction = ReduceTo::<SetPacking<i32>>::reduce_to(&matching);
180        let sp = reduction.target_problem();
181
182        let solver = BruteForce::new();
183        let sp_solutions = solver.find_best(sp);
184        let direct_solutions = solver.find_best(&matching);
185
186        // Both should find matchings of size 2
187        let sp_size: usize = sp_solutions[0].iter().sum();
188        let direct_size: usize = direct_solutions[0].iter().sum();
189        assert_eq!(sp_size, 2);
190        assert_eq!(direct_size, 2);
191    }
192
193    #[test]
194    fn test_matching_to_setpacking_empty() {
195        // Graph with no edges
196        let matching = Matching::<i32>::unweighted(3, vec![]);
197        let reduction = ReduceTo::<SetPacking<i32>>::reduce_to(&matching);
198        let sp = reduction.target_problem();
199
200        assert_eq!(sp.num_sets(), 0);
201    }
202
203    #[test]
204    fn test_matching_to_setpacking_single_edge() {
205        let matching = Matching::<i32>::unweighted(2, vec![(0, 1)]);
206        let reduction = ReduceTo::<SetPacking<i32>>::reduce_to(&matching);
207        let sp = reduction.target_problem();
208
209        assert_eq!(sp.num_sets(), 1);
210        assert_eq!(sp.sets()[0], vec![0, 1]);
211
212        let solver = BruteForce::new();
213        let sp_solutions = solver.find_best(sp);
214
215        // Should select the only set
216        assert_eq!(sp_solutions, vec![vec![1]]);
217    }
218
219    #[test]
220    fn test_matching_to_setpacking_disjoint_edges() {
221        // Two disjoint edges: 0-1 and 2-3
222        let matching = Matching::<i32>::unweighted(4, vec![(0, 1), (2, 3)]);
223        let reduction = ReduceTo::<SetPacking<i32>>::reduce_to(&matching);
224        let sp = reduction.target_problem();
225
226        let solver = BruteForce::new();
227        let sp_solutions = solver.find_best(sp);
228
229        // Both edges can be selected (they don't share vertices)
230        assert_eq!(sp_solutions, vec![vec![1, 1]]);
231    }
232
233    #[test]
234    fn test_reduction_sizes() {
235        let matching = Matching::<i32>::unweighted(5, vec![(0, 1), (1, 2), (2, 3)]);
236        let reduction = ReduceTo::<SetPacking<i32>>::reduce_to(&matching);
237
238        let source_size = reduction.source_size();
239        let target_size = reduction.target_size();
240
241        assert_eq!(source_size.get("num_vertices"), Some(5));
242        assert_eq!(source_size.get("num_edges"), Some(3));
243        assert_eq!(target_size.get("num_sets"), Some(3));
244    }
245
246    #[test]
247    fn test_matching_to_setpacking_star() {
248        // Star graph: center vertex 0 connected to 1, 2, 3
249        let matching = Matching::<i32>::unweighted(4, vec![(0, 1), (0, 2), (0, 3)]);
250        let reduction = ReduceTo::<SetPacking<i32>>::reduce_to(&matching);
251        let sp = reduction.target_problem();
252
253        let solver = BruteForce::new();
254        let sp_solutions = solver.find_best(sp);
255
256        // All edges share vertex 0, so max matching = 1
257        for sol in &sp_solutions {
258            assert_eq!(sol.iter().sum::<usize>(), 1);
259        }
260        // Should have 3 optimal solutions
261        assert_eq!(sp_solutions.len(), 3);
262    }
263}
264
265// Register reduction with inventory for auto-discovery
266use crate::poly;
267use crate::rules::registry::{ReductionEntry, ReductionOverhead};
268
269inventory::submit! {
270    ReductionEntry {
271        source_name: "Matching",
272        target_name: "SetPacking",
273        source_graph: "SimpleGraph",
274        target_graph: "SetSystem",
275        overhead_fn: || ReductionOverhead::new(vec![
276            ("num_sets", poly!(num_edges)),
277            ("num_elements", poly!(num_vertices)),
278        ]),
279    }
280}