problemreductions/rules/
partitionintocliques_minimumcoveringbycliques.rs1use crate::models::graph::{MinimumCoveringByCliques, PartitionIntoCliques};
9use crate::reduction;
10use crate::rules::traits::{ReduceTo, ReductionResult};
11use crate::topology::{Graph, SimpleGraph};
12use std::collections::BTreeMap;
13
14#[derive(Debug, Clone)]
15struct OrlinLayout {
16 num_source_vertices: usize,
17 directed_pairs: Vec<(usize, usize)>,
18}
19
20impl OrlinLayout {
21 fn new(graph: &SimpleGraph) -> Self {
22 let n = graph.num_vertices();
23 let directed_pairs = (0..n)
24 .flat_map(|i| {
25 (0..n).filter_map(move |j| (i != j && graph.has_edge(i, j)).then_some((i, j)))
26 })
27 .collect();
28 Self {
29 num_source_vertices: n,
30 directed_pairs,
31 }
32 }
33
34 fn num_directed_pairs(&self) -> usize {
35 self.directed_pairs.len()
36 }
37
38 fn x(&self, i: usize) -> usize {
39 i
40 }
41
42 fn y(&self, i: usize) -> usize {
43 self.num_source_vertices + i
44 }
45
46 fn a(&self, directed_pair_index: usize) -> usize {
47 2 * self.num_source_vertices + directed_pair_index
48 }
49
50 fn b(&self, directed_pair_index: usize) -> usize {
51 2 * self.num_source_vertices + self.num_directed_pairs() + directed_pair_index
52 }
53
54 fn z_left(&self) -> usize {
55 2 * self.num_source_vertices + 2 * self.num_directed_pairs()
56 }
57
58 fn z_right(&self) -> usize {
59 self.z_left() + 1
60 }
61
62 fn left_vertices(&self) -> Vec<usize> {
63 let mut vertices = (0..self.num_source_vertices)
64 .map(|i| self.x(i))
65 .collect::<Vec<_>>();
66 vertices.extend((0..self.num_directed_pairs()).map(|idx| self.a(idx)));
67 vertices
68 }
69
70 fn right_vertices(&self) -> Vec<usize> {
71 let mut vertices = (0..self.num_source_vertices)
72 .map(|i| self.y(i))
73 .collect::<Vec<_>>();
74 vertices.extend((0..self.num_directed_pairs()).map(|idx| self.b(idx)));
75 vertices
76 }
77
78 fn total_vertices(&self) -> usize {
79 self.z_right() + 1
80 }
81}
82
83fn add_clique_edges(vertices: &[usize], edges: &mut Vec<(usize, usize)>) {
84 for i in 0..vertices.len() {
85 for j in (i + 1)..vertices.len() {
86 edges.push((vertices[i], vertices[j]));
87 }
88 }
89}
90
91fn invalid_source_solution(num_source_vertices: usize, num_source_cliques: usize) -> Vec<usize> {
92 vec![num_source_cliques; num_source_vertices]
93}
94
95#[derive(Debug, Clone)]
97pub struct ReductionPartitionIntoCliquesToMinimumCoveringByCliques {
98 target: MinimumCoveringByCliques<SimpleGraph>,
99 source_graph: SimpleGraph,
100 source_num_cliques: usize,
101}
102
103impl ReductionResult for ReductionPartitionIntoCliquesToMinimumCoveringByCliques {
104 type Source = PartitionIntoCliques<SimpleGraph>;
105 type Target = MinimumCoveringByCliques<SimpleGraph>;
106
107 fn target_problem(&self) -> &Self::Target {
108 &self.target
109 }
110
111 fn extract_solution(&self, target_solution: &[usize]) -> Vec<usize> {
112 let n = self.source_graph.num_vertices();
113 let target_edges = self.target.graph().edges();
114 if target_solution.len() != target_edges.len() {
115 return invalid_source_solution(n, self.source_num_cliques);
116 }
117
118 let mut matching_labels = vec![None; n];
119 for ((u, v), &label) in target_edges.iter().zip(target_solution.iter()) {
120 let matching_index = if *u < n && *v == n + *u {
121 Some(*u)
122 } else if *v < n && *u == n + *v {
123 Some(*v)
124 } else {
125 None
126 };
127
128 if let Some(i) = matching_index {
129 matching_labels[i] = Some(label);
130 }
131 }
132
133 if matching_labels.iter().any(Option::is_none) {
134 return invalid_source_solution(n, self.source_num_cliques);
135 }
136
137 let mut label_map = BTreeMap::new();
138 let extracted = matching_labels
139 .into_iter()
140 .map(|label| {
141 let label = label.expect("checked above");
142 let next = label_map.len();
143 *label_map.entry(label).or_insert(next)
144 })
145 .collect::<Vec<_>>();
146
147 if label_map.len() > self.source_num_cliques {
148 return invalid_source_solution(n, self.source_num_cliques);
149 }
150
151 let source_problem =
152 PartitionIntoCliques::new(self.source_graph.clone(), self.source_num_cliques);
153 if <PartitionIntoCliques<SimpleGraph> as crate::traits::Problem>::evaluate(
154 &source_problem,
155 &extracted,
156 )
157 .0
158 {
159 extracted
160 } else {
161 invalid_source_solution(n, self.source_num_cliques)
162 }
163 }
164}
165
166#[reduction(
167 overhead = {
168 num_vertices = "2 * num_vertices + 4 * num_edges + 2",
169 num_edges = "(num_vertices + 2 * num_edges)^2 + 2 * num_vertices + 10 * num_edges",
170 }
171)]
172impl ReduceTo<MinimumCoveringByCliques<SimpleGraph>> for PartitionIntoCliques<SimpleGraph> {
173 type Result = ReductionPartitionIntoCliquesToMinimumCoveringByCliques;
174
175 fn reduce_to(&self) -> Self::Result {
176 let layout = OrlinLayout::new(self.graph());
177 let left_vertices = layout.left_vertices();
178 let right_vertices = layout.right_vertices();
179 let mut edges = Vec::new();
180
181 add_clique_edges(&left_vertices, &mut edges);
183 add_clique_edges(&right_vertices, &mut edges);
184
185 for &u in &left_vertices {
187 edges.push((layout.z_left(), u));
188 }
189 for &u in &right_vertices {
190 edges.push((layout.z_right(), u));
191 }
192
193 for i in 0..self.num_vertices() {
195 edges.push((layout.x(i), layout.y(i)));
196 }
197
198 for (idx, &(i, j)) in layout.directed_pairs.iter().enumerate() {
200 edges.push((layout.x(i), layout.y(j)));
201 edges.push((layout.x(i), layout.b(idx)));
202 edges.push((layout.a(idx), layout.y(j)));
203 edges.push((layout.a(idx), layout.b(idx)));
204 }
205
206 let target_graph = SimpleGraph::new(layout.total_vertices(), edges);
207 let target = MinimumCoveringByCliques::new(target_graph);
208
209 ReductionPartitionIntoCliquesToMinimumCoveringByCliques {
210 target,
211 source_graph: self.graph().clone(),
212 source_num_cliques: self.num_cliques(),
213 }
214 }
215}
216
217#[cfg(any(test, feature = "example-db"))]
218fn edge_labels_from_clique_cover(graph: &SimpleGraph, cliques: &[Vec<usize>]) -> Vec<usize> {
219 let clique_sets = cliques
220 .iter()
221 .map(|clique| {
222 clique
223 .iter()
224 .copied()
225 .collect::<std::collections::BTreeSet<_>>()
226 })
227 .collect::<Vec<_>>();
228
229 graph
230 .edges()
231 .into_iter()
232 .map(|(u, v)| {
233 clique_sets
234 .iter()
235 .position(|clique| clique.contains(&u) && clique.contains(&v))
236 .expect("canonical cover should cover every target edge")
237 })
238 .collect()
239}
240
241#[cfg(feature = "example-db")]
242pub(crate) fn canonical_rule_example_specs() -> Vec<crate::example_db::specs::RuleExampleSpec> {
243 use crate::export::SolutionPair;
244
245 vec![crate::example_db::specs::RuleExampleSpec {
246 id: "partitionintocliques_to_minimumcoveringbycliques",
247 build: || {
248 let source = PartitionIntoCliques::new(SimpleGraph::new(3, vec![(0, 1)]), 2);
249 let reduction = ReduceTo::<MinimumCoveringByCliques<SimpleGraph>>::reduce_to(&source);
250 let layout = OrlinLayout::new(source.graph());
251
252 let target_config = edge_labels_from_clique_cover(
253 reduction.target_problem().graph(),
254 &[
255 vec![layout.x(0), layout.x(1), layout.y(0), layout.y(1)],
256 vec![layout.x(2), layout.y(2)],
257 vec![layout.x(0), layout.a(0), layout.b(0), layout.y(1)],
258 vec![layout.x(1), layout.a(1), layout.b(1), layout.y(0)],
259 {
260 let mut clique = layout.left_vertices();
261 clique.push(layout.z_left());
262 clique
263 },
264 {
265 let mut clique = layout.right_vertices();
266 clique.push(layout.z_right());
267 clique
268 },
269 ],
270 );
271
272 crate::example_db::specs::rule_example_with_witness::<
273 _,
274 MinimumCoveringByCliques<SimpleGraph>,
275 >(
276 source,
277 SolutionPair {
278 source_config: vec![0, 0, 1],
279 target_config,
280 },
281 )
282 },
283 }]
284}
285
286#[cfg(test)]
287#[path = "../unit_tests/rules/partitionintocliques_minimumcoveringbycliques.rs"]
288mod tests;