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