problemreductions/rules/
exactcoverby3sets_boundeddiameterspanningtree.rs1use crate::models::graph::BoundedDiameterSpanningTree;
35use crate::models::set::ExactCoverBy3Sets;
36use crate::reduction;
37use crate::rules::traits::{ReduceTo, ReductionResult};
38use crate::topology::SimpleGraph;
39use std::collections::HashSet;
40
41#[derive(Debug, Clone)]
43pub struct ReductionX3CToBoundedDiameterSpanningTree {
44 target: BoundedDiameterSpanningTree<SimpleGraph, i64>,
45 source_num_subsets: usize,
46}
47
48impl ReductionX3CToBoundedDiameterSpanningTree {
49 fn dimensions(
51 universe_size: usize,
52 m: usize,
53 ) -> Result<(usize, usize, i64), crate::rules::ReductionError> {
54 let overflow = || {
55 crate::rules::ReductionError::integer_overflow::<
56 ExactCoverBy3Sets,
57 BoundedDiameterSpanningTree<SimpleGraph, i64>,
58 >("computing bounded-diameter incidence construction dimensions")
59 };
60 let vertices = universe_size
61 .checked_add(m)
62 .and_then(|v| v.checked_add(3))
63 .ok_or_else(overflow)?;
64 let clique = match m {
65 0 => 0,
66 _ => m.checked_mul(m - 1).ok_or_else(overflow)? / 2,
67 };
68 let edges = m
69 .checked_mul(4)
70 .and_then(|v| v.checked_add(clique))
71 .and_then(|v| v.checked_add(2))
72 .ok_or_else(overflow)?;
73 let bound = (universe_size / 3)
74 .checked_mul(4)
75 .and_then(|v| v.checked_add(m))
76 .and_then(|v| v.checked_add(2))
77 .and_then(|v| i64::try_from(v).ok())
78 .ok_or_else(overflow)?;
79 Ok((vertices, edges, bound))
80 }
81}
82
83impl ReductionResult for ReductionX3CToBoundedDiameterSpanningTree {
84 type Source = ExactCoverBy3Sets;
85 type Target = BoundedDiameterSpanningTree<SimpleGraph, i64>;
86
87 fn target_problem(&self) -> &Self::Target {
88 &self.target
89 }
90
91 fn extract_solution(
98 &self,
99 target_solution: &<Self::Target as crate::traits::Problem>::Solution,
100 ) -> crate::rules::ExtractionResult<<Self::Source as crate::traits::Problem>::Solution> {
101 crate::rules::traits::validate_target_witness(
102 self.target_problem(),
103 target_solution,
104 |value| value.0,
105 "target edge selection is not a feasible bounded-diameter spanning tree",
106 )?;
107
108 Ok({
109 let m = self.source_num_subsets;
110 let root_to_set_offset = 2;
111 (0..m)
112 .map(|i| target_solution[root_to_set_offset + i])
113 .collect()
114 })
115 }
116}
117
118#[crate::aggregate_reduction(identity)]
119impl crate::rules::AggregateReductionResult for ReductionX3CToBoundedDiameterSpanningTree {}
120
121#[reduction(
122 transform = upper_bound {
123 num_vertices = "num_subsets + universe_size + 3",
124 num_edges = "2 + 4 * num_subsets + num_subsets * (num_subsets - 1) / 2",
125 })]
126impl ReduceTo<BoundedDiameterSpanningTree<SimpleGraph, i64>> for ExactCoverBy3Sets {
127 type Result = ReductionX3CToBoundedDiameterSpanningTree;
128
129 fn reduce_to(&self) -> Result<Self::Result, crate::rules::ReductionError> {
130 let universe_size = self.universe_size();
131 let m = self.num_subsets();
132 let covered: HashSet<_> = self.subsets().iter().flatten().copied().collect();
135 if covered.len() != universe_size {
136 return Ok(ReductionX3CToBoundedDiameterSpanningTree {
139 target: BoundedDiameterSpanningTree::new(SimpleGraph::empty(2), vec![], 1, 4),
140 source_num_subsets: m,
141 });
142 }
143 let (num_vertices, num_edges, weight_bound) =
144 ReductionX3CToBoundedDiameterSpanningTree::dimensions(universe_size, m)?;
145
146 let s_index = |i: usize| 3 + i;
148 let e_index = |j: usize| 3 + m + j;
149
150 let mut edges: Vec<(usize, usize)> = Vec::with_capacity(num_edges);
151 let mut weights: Vec<i64> = Vec::with_capacity(num_edges);
152
153 edges.push((0, 1)); weights.push(1);
156 edges.push((1, 2)); weights.push(1);
158
159 for i in 0..m {
161 edges.push((0, s_index(i)));
162 weights.push(2);
163 }
164 debug_assert!(
168 (0..m).all(|i| edges[2 + i] == (0, s_index(i))),
169 "root-to-set edges must occupy indices 2..2+m for extract_solution to work"
170 );
171
172 for (i, subset) in self.subsets().iter().enumerate() {
174 for &j in subset {
175 edges.push((s_index(i), e_index(j)));
176 weights.push(1);
177 }
178 }
179
180 for i in 0..m {
182 for j in (i + 1)..m {
183 edges.push((s_index(i), s_index(j)));
184 weights.push(1);
185 }
186 }
187
188 let diameter_bound: usize = 4;
189
190 let graph = SimpleGraph::new(num_vertices, edges);
191 let target = BoundedDiameterSpanningTree::new(graph, weights, weight_bound, diameter_bound);
192
193 Ok(ReductionX3CToBoundedDiameterSpanningTree {
194 target,
195 source_num_subsets: m,
196 })
197 }
198}
199
200#[cfg(feature = "example-db")]
201pub(crate) fn canonical_rule_example_specs() -> Vec<crate::example_db::specs::RuleExampleSpec> {
202 use crate::export::SolutionPair;
203
204 vec![crate::example_db::specs::RuleExampleSpec {
205 id: "exactcoverby3sets_to_boundeddiameterspanningtree",
206 build: || {
207 let source = ExactCoverBy3Sets::new(6, vec![[0, 1, 2], [3, 4, 5]]);
211
212 let source_config = vec![true, true];
214
215 let target_config = vec![
226 true, true, true, true, true, true, true, true, true, true, false,
227 ];
228
229 crate::example_db::specs::rule_example_with_witness::<
230 _,
231 BoundedDiameterSpanningTree<SimpleGraph, i64>,
232 >(
233 source,
234 SolutionPair {
235 source_config: serde_json::to_value(source_config)
236 .expect("solution serialization must succeed"),
237 target_config: serde_json::to_value(target_config)
238 .expect("solution serialization must succeed"),
239 },
240 )
241 },
242 }]
243}
244
245#[cfg(test)]
246#[path = "../unit_tests/rules/exactcoverby3sets_boundeddiameterspanningtree.rs"]
247mod tests;