problemreductions/rules/
independentset_setpacking.rs

1//! Reductions between IndependentSet and SetPacking problems.
2//!
3//! IS → SetPacking: Each vertex becomes a set containing its incident edge indices.
4//! SetPacking → IS: Each set becomes a vertex; two vertices are adjacent if their sets overlap.
5
6use crate::models::graph::IndependentSet;
7use crate::models::set::SetPacking;
8use crate::rules::traits::{ReduceTo, ReductionResult};
9use crate::traits::Problem;
10use crate::types::ProblemSize;
11use num_traits::{Num, Zero};
12use std::collections::HashSet;
13use std::ops::AddAssign;
14
15/// Result of reducing IndependentSet to SetPacking.
16#[derive(Debug, Clone)]
17pub struct ReductionISToSP<W> {
18    target: SetPacking<W>,
19    source_size: ProblemSize,
20}
21
22impl<W> ReductionResult for ReductionISToSP<W>
23where
24    W: Clone + Default + PartialOrd + Num + Zero + AddAssign + 'static,
25{
26    type Source = IndependentSet<W>;
27    type Target = SetPacking<W>;
28
29    fn target_problem(&self) -> &Self::Target {
30        &self.target
31    }
32
33    /// Solutions map directly: vertex selection = set selection.
34    fn extract_solution(&self, target_solution: &[usize]) -> Vec<usize> {
35        target_solution.to_vec()
36    }
37
38    fn source_size(&self) -> ProblemSize {
39        self.source_size.clone()
40    }
41
42    fn target_size(&self) -> ProblemSize {
43        self.target.problem_size()
44    }
45}
46
47impl<W> ReduceTo<SetPacking<W>> for IndependentSet<W>
48where
49    W: Clone + Default + PartialOrd + Num + Zero + AddAssign + From<i32> + 'static,
50{
51    type Result = ReductionISToSP<W>;
52
53    fn reduce_to(&self) -> Self::Result {
54        let edges = self.edges();
55        let n = self.num_vertices();
56
57        // For each vertex, collect the indices of its incident edges
58        let mut sets: Vec<Vec<usize>> = vec![Vec::new(); n];
59        for (edge_idx, &(u, v)) in edges.iter().enumerate() {
60            sets[u].push(edge_idx);
61            sets[v].push(edge_idx);
62        }
63
64        let target = SetPacking::with_weights(sets, self.weights_ref().clone());
65
66        ReductionISToSP {
67            target,
68            source_size: self.problem_size(),
69        }
70    }
71}
72
73/// Result of reducing SetPacking to IndependentSet.
74#[derive(Debug, Clone)]
75pub struct ReductionSPToIS<W> {
76    target: IndependentSet<W>,
77    source_size: ProblemSize,
78}
79
80impl<W> ReductionResult for ReductionSPToIS<W>
81where
82    W: Clone + Default + PartialOrd + Num + Zero + AddAssign + 'static,
83{
84    type Source = SetPacking<W>;
85    type Target = IndependentSet<W>;
86
87    fn target_problem(&self) -> &Self::Target {
88        &self.target
89    }
90
91    /// Solutions map directly.
92    fn extract_solution(&self, target_solution: &[usize]) -> Vec<usize> {
93        target_solution.to_vec()
94    }
95
96    fn source_size(&self) -> ProblemSize {
97        self.source_size.clone()
98    }
99
100    fn target_size(&self) -> ProblemSize {
101        self.target.problem_size()
102    }
103}
104
105impl<W> ReduceTo<IndependentSet<W>> for SetPacking<W>
106where
107    W: Clone + Default + PartialOrd + Num + Zero + AddAssign + From<i32> + 'static,
108{
109    type Result = ReductionSPToIS<W>;
110
111    fn reduce_to(&self) -> Self::Result {
112        let sets = self.sets();
113        let n = sets.len();
114
115        // Create edges between sets that overlap
116        let mut edges = Vec::new();
117        for (i, set_i_vec) in sets.iter().enumerate() {
118            let set_i: HashSet<_> = set_i_vec.iter().collect();
119            for (j, set_j) in sets.iter().enumerate().skip(i + 1) {
120                // Check if sets[i] and sets[j] overlap
121                if set_j.iter().any(|elem| set_i.contains(elem)) {
122                    edges.push((i, j));
123                }
124            }
125        }
126
127        let target = IndependentSet::with_weights(n, edges, self.weights_ref().clone());
128
129        ReductionSPToIS {
130            target,
131            source_size: self.problem_size(),
132        }
133    }
134}
135
136#[cfg(test)]
137mod tests {
138    use super::*;
139    use crate::solvers::{BruteForce, Solver};
140
141    #[test]
142    fn test_is_to_setpacking() {
143        // Triangle graph
144        let is_problem = IndependentSet::<i32>::new(3, vec![(0, 1), (1, 2), (0, 2)]);
145        let reduction = ReduceTo::<SetPacking<i32>>::reduce_to(&is_problem);
146        let sp_problem = reduction.target_problem();
147
148        let solver = BruteForce::new();
149        let sp_solutions = solver.find_best(sp_problem);
150
151        // Extract back
152        let is_solutions: Vec<_> = sp_solutions
153            .iter()
154            .map(|s| reduction.extract_solution(s))
155            .collect();
156
157        // Max IS in triangle = 1
158        for sol in &is_solutions {
159            let size: usize = sol.iter().sum();
160            assert_eq!(size, 1);
161        }
162    }
163
164    #[test]
165    fn test_setpacking_to_is() {
166        // Two disjoint sets and one overlapping
167        let sets = vec![
168            vec![0, 1],
169            vec![2, 3],
170            vec![1, 2], // overlaps with both
171        ];
172        let sp_problem = SetPacking::<i32>::new(sets);
173        let reduction: ReductionSPToIS<i32> =
174            ReduceTo::<IndependentSet<i32>>::reduce_to(&sp_problem);
175        let is_problem = reduction.target_problem();
176
177        let solver = BruteForce::new();
178        let is_solutions = solver.find_best(is_problem);
179
180        // Max packing = 2 (sets 0 and 1)
181        for sol in &is_solutions {
182            let size: usize = sol.iter().sum();
183            assert_eq!(size, 2);
184        }
185    }
186
187    #[test]
188    fn test_roundtrip_is_sp_is() {
189        let original = IndependentSet::<i32>::new(4, vec![(0, 1), (1, 2), (2, 3)]);
190        let solver = BruteForce::new();
191        let original_solutions = solver.find_best(&original);
192
193        // IS -> SP -> IS
194        let reduction1 = ReduceTo::<SetPacking<i32>>::reduce_to(&original);
195        let sp = reduction1.target_problem().clone();
196        let reduction2: ReductionSPToIS<i32> =
197            ReduceTo::<IndependentSet<i32>>::reduce_to(&sp);
198        let roundtrip = reduction2.target_problem();
199
200        let roundtrip_solutions = solver.find_best(roundtrip);
201
202        // Solutions should have same objective value
203        let orig_size: usize = original_solutions[0].iter().sum();
204        let rt_size: usize = roundtrip_solutions[0].iter().sum();
205        assert_eq!(orig_size, rt_size);
206    }
207
208    #[test]
209    fn test_weighted_reduction() {
210        let is_problem =
211            IndependentSet::with_weights(3, vec![(0, 1), (1, 2)], vec![10, 20, 30]);
212        let reduction = ReduceTo::<SetPacking<i32>>::reduce_to(&is_problem);
213        let sp_problem = reduction.target_problem();
214
215        // Weights should be preserved
216        assert_eq!(sp_problem.weights_ref(), &vec![10, 20, 30]);
217    }
218
219    #[test]
220    fn test_empty_graph() {
221        // No edges means all sets are empty (or we need to handle it)
222        let is_problem = IndependentSet::<i32>::new(3, vec![]);
223        let reduction = ReduceTo::<SetPacking<i32>>::reduce_to(&is_problem);
224        let sp_problem = reduction.target_problem();
225
226        // All sets should be empty (no edges to include)
227        assert_eq!(sp_problem.num_sets(), 3);
228
229        let solver = BruteForce::new();
230        let solutions = solver.find_best(sp_problem);
231
232        // With no overlaps, we can select all sets
233        assert_eq!(solutions[0].iter().sum::<usize>(), 3);
234    }
235
236    #[test]
237    fn test_disjoint_sets() {
238        // Completely disjoint sets
239        let sets = vec![vec![0], vec![1], vec![2]];
240        let sp_problem = SetPacking::<i32>::new(sets);
241        let reduction: ReductionSPToIS<i32> =
242            ReduceTo::<IndependentSet<i32>>::reduce_to(&sp_problem);
243        let is_problem = reduction.target_problem();
244
245        // No edges in the intersection graph
246        assert_eq!(is_problem.num_edges(), 0);
247    }
248}
249
250// Register reductions with inventory for auto-discovery
251use crate::poly;
252use crate::rules::registry::{ReductionEntry, ReductionOverhead};
253
254inventory::submit! {
255    ReductionEntry {
256        source_name: "IndependentSet",
257        target_name: "SetPacking",
258        source_graph: "SimpleGraph",
259        target_graph: "SetSystem",
260        overhead_fn: || ReductionOverhead::new(vec![
261            ("num_sets", poly!(num_vertices)),
262            ("num_elements", poly!(num_vertices)),
263        ]),
264    }
265}
266
267inventory::submit! {
268    ReductionEntry {
269        source_name: "SetPacking",
270        target_name: "IndependentSet",
271        source_graph: "SetSystem",
272        target_graph: "SimpleGraph",
273        overhead_fn: || ReductionOverhead::new(vec![
274            ("num_vertices", poly!(num_sets)),
275            ("num_edges", poly!(num_sets)),
276        ]),
277    }
278}