1use crate::models::formula::NAESatisfiability;
8use crate::models::graph::PartitionIntoPerfectMatchings;
9use crate::reduction;
10use crate::rules::traits::{ReduceTo, ReductionResult};
11use crate::topology::SimpleGraph;
12
13#[derive(Debug, Clone, Copy)]
14struct VariableVertices {
15 t: usize,
16 t_prime: usize,
17 f: usize,
18 f_prime: usize,
19}
20
21#[derive(Debug, Clone, Copy)]
22struct SignalVertices {
23 s: usize,
24 s_prime: usize,
25}
26
27#[derive(Debug, Clone)]
28struct ClauseLayout {
29 literals: [i32; 3],
30 signals: [SignalVertices; 3],
31 clause_vertices: [usize; 4],
32}
33
34#[derive(Debug, Clone, Copy)]
35struct ChainPairVertices {
36 mu: usize,
37 mu_prime: usize,
38}
39
40#[derive(Debug, Clone)]
41struct ReductionLayout {
42 variables: Vec<VariableVertices>,
43 #[cfg(any(test, feature = "example-db"))]
44 clauses: Vec<ClauseLayout>,
45 #[cfg(any(test, feature = "example-db"))]
46 positive_chains: Vec<Vec<ChainPairVertices>>,
47 #[cfg(any(test, feature = "example-db"))]
48 negative_chains: Vec<Vec<ChainPairVertices>>,
49 num_vertices: usize,
50 edges: Vec<(usize, usize)>,
51}
52
53#[derive(Debug, Clone)]
55pub struct ReductionNAESATToPartitionIntoPerfectMatchings {
56 target: PartitionIntoPerfectMatchings<SimpleGraph>,
57 layout: ReductionLayout,
58}
59
60impl ReductionResult for ReductionNAESATToPartitionIntoPerfectMatchings {
61 type Source = NAESatisfiability;
62 type Target = PartitionIntoPerfectMatchings<SimpleGraph>;
63
64 fn target_problem(&self) -> &Self::Target {
65 &self.target
66 }
67
68 fn extract_solution(&self, target_solution: &[usize]) -> Vec<usize> {
69 self.layout
70 .variables
71 .iter()
72 .map(|variable| usize::from(target_solution[variable.t] == 0))
73 .collect()
74 }
75}
76
77impl ReductionNAESATToPartitionIntoPerfectMatchings {
78 #[cfg(any(test, feature = "example-db"))]
79 fn construct_target_solution(&self, source_solution: &[usize]) -> Vec<usize> {
80 assert_eq!(
81 source_solution.len(),
82 self.layout.variables.len(),
83 "source solution has {} variables but reduction expects {}",
84 source_solution.len(),
85 self.layout.variables.len()
86 );
87
88 let mut target_solution = vec![usize::MAX; self.layout.num_vertices];
89 let mut true_groups = Vec::with_capacity(self.layout.variables.len());
90 let mut false_groups = Vec::with_capacity(self.layout.variables.len());
91
92 for (index, variable) in self.layout.variables.iter().enumerate() {
93 let true_group = if source_solution[index] == 1 { 0 } else { 1 };
94 let false_group = 1 - true_group;
95 true_groups.push(true_group);
96 false_groups.push(false_group);
97
98 target_solution[variable.t] = true_group;
99 target_solution[variable.t_prime] = true_group;
100 target_solution[variable.f] = false_group;
101 target_solution[variable.f_prime] = false_group;
102 }
103
104 for clause in &self.layout.clauses {
105 for (signal, &literal) in clause.signals.iter().zip(clause.literals.iter()) {
106 let variable_index = literal.unsigned_abs() as usize - 1;
107 let signal_group = if literal > 0 {
108 true_groups[variable_index]
109 } else {
110 false_groups[variable_index]
111 };
112 target_solution[signal.s] = signal_group;
113 target_solution[signal.s_prime] = signal_group;
114 }
115 }
116
117 for (variable_index, chain_pairs) in self.layout.positive_chains.iter().enumerate() {
118 for pair in chain_pairs {
119 target_solution[pair.mu] = false_groups[variable_index];
120 target_solution[pair.mu_prime] = false_groups[variable_index];
121 }
122 }
123
124 for (variable_index, chain_pairs) in self.layout.negative_chains.iter().enumerate() {
125 for pair in chain_pairs {
126 target_solution[pair.mu] = true_groups[variable_index];
127 target_solution[pair.mu_prime] = true_groups[variable_index];
128 }
129 }
130
131 for clause in &self.layout.clauses {
132 let clause_groups = clause.signals.map(|signal| 1 - target_solution[signal.s]);
133 let zero_count = clause_groups.iter().filter(|&&group| group == 0).count();
134 let w3_group = match zero_count {
135 1 => 0,
136 2 => 1,
137 _ => panic!("source assignment is not NAE-satisfying for normalized clauses"),
138 };
139
140 for (vertex, &group) in clause
141 .clause_vertices
142 .iter()
143 .take(3)
144 .zip(clause_groups.iter())
145 {
146 target_solution[*vertex] = group;
147 }
148 target_solution[clause.clause_vertices[3]] = w3_group;
149 }
150
151 assert!(
152 target_solution.iter().all(|&group| group <= 1),
153 "constructed target solution left some vertices unassigned"
154 );
155
156 target_solution
157 }
158}
159
160fn normalize_clauses(problem: &NAESatisfiability) -> Vec<[i32; 3]> {
161 problem
162 .clauses()
163 .iter()
164 .map(|clause| match clause.literals.as_slice() {
165 [a, b] => [*a, *a, *b],
166 [a, b, c] => [*a, *b, *c],
167 literals => panic!(
168 "NAESatisfiability -> PartitionIntoPerfectMatchings expects clauses of size 2 or 3, got {}",
169 literals.len()
170 ),
171 })
172 .collect()
173}
174
175fn build_layout(problem: &NAESatisfiability) -> ReductionLayout {
176 let num_vars = problem.num_vars();
177 let clauses = normalize_clauses(problem);
178 let num_clauses = clauses.len();
179
180 let mut next_vertex = 0usize;
181 let mut edges = Vec::with_capacity(3 * num_vars + 21 * num_clauses);
182 let mut variables = Vec::with_capacity(num_vars);
183
184 for _ in 0..num_vars {
185 let variable = VariableVertices {
186 t: next_vertex,
187 t_prime: next_vertex + 1,
188 f: next_vertex + 2,
189 f_prime: next_vertex + 3,
190 };
191 next_vertex += 4;
192
193 edges.push((variable.t, variable.t_prime));
194 edges.push((variable.f, variable.f_prime));
195 edges.push((variable.t, variable.f));
196 variables.push(variable);
197 }
198
199 let mut clause_layouts = Vec::with_capacity(num_clauses);
200
201 for &literals in &clauses {
202 let mut signals = [SignalVertices { s: 0, s_prime: 0 }; 3];
203 for (literal_index, _) in literals.iter().enumerate() {
204 signals[literal_index] = SignalVertices {
205 s: next_vertex,
206 s_prime: next_vertex + 1,
207 };
208 next_vertex += 2;
209 edges.push((signals[literal_index].s, signals[literal_index].s_prime));
210 }
211
212 clause_layouts.push(ClauseLayout {
213 literals,
214 signals,
215 clause_vertices: [0; 4],
216 });
217 }
218
219 for clause_layout in &mut clause_layouts {
220 let clause_vertices = [
221 next_vertex,
222 next_vertex + 1,
223 next_vertex + 2,
224 next_vertex + 3,
225 ];
226 next_vertex += 4;
227
228 for a in 0..4 {
229 for b in (a + 1)..4 {
230 edges.push((clause_vertices[a], clause_vertices[b]));
231 }
232 }
233 for (literal_index, &clause_vertex) in clause_vertices.iter().enumerate().take(3) {
234 edges.push((clause_layout.signals[literal_index].s, clause_vertex));
235 }
236 clause_layout.clause_vertices = clause_vertices;
237 }
238
239 let mut positive_occurrences: Vec<Vec<(usize, usize)>> = vec![Vec::new(); num_vars];
240 let mut negative_occurrences: Vec<Vec<(usize, usize)>> = vec![Vec::new(); num_vars];
241 for (clause_index, clause_layout) in clause_layouts.iter().enumerate() {
242 for (literal_index, &literal) in clause_layout.literals.iter().enumerate() {
243 let variable_index = literal.unsigned_abs() as usize - 1;
244 if literal > 0 {
245 positive_occurrences[variable_index].push((clause_index, literal_index));
246 } else {
247 negative_occurrences[variable_index].push((clause_index, literal_index));
248 }
249 }
250 }
251
252 let mut positive_chains: Vec<Vec<ChainPairVertices>> = vec![Vec::new(); num_vars];
253 let mut negative_chains: Vec<Vec<ChainPairVertices>> = vec![Vec::new(); num_vars];
254
255 for variable_index in 0..num_vars {
256 let mut source_vertex = variables[variable_index].t;
257 for &(clause_index, literal_index) in &positive_occurrences[variable_index] {
258 let pair = ChainPairVertices {
259 mu: next_vertex,
260 mu_prime: next_vertex + 1,
261 };
262 next_vertex += 2;
263
264 let signal_vertex = clause_layouts[clause_index].signals[literal_index].s;
265 edges.push((pair.mu, pair.mu_prime));
266 edges.push((source_vertex, pair.mu));
267 edges.push((signal_vertex, pair.mu));
268 positive_chains[variable_index].push(pair);
269 source_vertex = signal_vertex;
270 }
271
272 let mut source_vertex = variables[variable_index].f;
273 for &(clause_index, literal_index) in &negative_occurrences[variable_index] {
274 let pair = ChainPairVertices {
275 mu: next_vertex,
276 mu_prime: next_vertex + 1,
277 };
278 next_vertex += 2;
279
280 let signal_vertex = clause_layouts[clause_index].signals[literal_index].s;
281 edges.push((pair.mu, pair.mu_prime));
282 edges.push((source_vertex, pair.mu));
283 edges.push((signal_vertex, pair.mu));
284 negative_chains[variable_index].push(pair);
285 source_vertex = signal_vertex;
286 }
287 }
288
289 ReductionLayout {
290 variables,
291 #[cfg(any(test, feature = "example-db"))]
292 clauses: clause_layouts,
293 #[cfg(any(test, feature = "example-db"))]
294 positive_chains,
295 #[cfg(any(test, feature = "example-db"))]
296 negative_chains,
297 num_vertices: next_vertex,
298 edges,
299 }
300}
301
302#[reduction(
303 overhead = {
304 num_vertices = "4 * num_vars + 16 * num_clauses",
305 num_edges = "3 * num_vars + 21 * num_clauses",
306 num_matchings = "2",
307 }
308)]
309impl ReduceTo<PartitionIntoPerfectMatchings<SimpleGraph>> for NAESatisfiability {
310 type Result = ReductionNAESATToPartitionIntoPerfectMatchings;
311
312 fn reduce_to(&self) -> Self::Result {
313 let layout = build_layout(self);
314 let target = PartitionIntoPerfectMatchings::new(
315 SimpleGraph::new(layout.num_vertices, layout.edges.clone()),
316 2,
317 );
318
319 ReductionNAESATToPartitionIntoPerfectMatchings { target, layout }
320 }
321}
322
323#[cfg(feature = "example-db")]
324pub(crate) fn canonical_rule_example_specs() -> Vec<crate::example_db::specs::RuleExampleSpec> {
325 use crate::export::SolutionPair;
326 use crate::models::formula::CNFClause;
327
328 vec![crate::example_db::specs::RuleExampleSpec {
329 id: "naesatisfiability_to_partitionintoperfectmatchings",
330 build: || {
331 let source = NAESatisfiability::new(
332 3,
333 vec![
334 CNFClause::new(vec![1, 2, 3]),
335 CNFClause::new(vec![-1, 2, -3]),
336 ],
337 );
338 let source_config = vec![1, 1, 0];
339 let reduction =
340 ReduceTo::<PartitionIntoPerfectMatchings<SimpleGraph>>::reduce_to(&source);
341 let target_config = reduction.construct_target_solution(&source_config);
342
343 crate::example_db::specs::assemble_rule_example(
344 &source,
345 reduction.target_problem(),
346 vec![SolutionPair {
347 source_config,
348 target_config,
349 }],
350 )
351 },
352 }]
353}
354
355#[cfg(test)]
356#[path = "../unit_tests/rules/naesatisfiability_partitionintoperfectmatchings.rs"]
357mod tests;