Skip to main content

problemreductions/rules/
naesatisfiability_partitionintoperfectmatchings.rs

1//! Reduction from NAE-Satisfiability to Partition Into Perfect Matchings.
2//!
3//! This implements the Schaefer-style reduction for the `K = 2` case.
4//! Clauses with two literals are normalized to three literals by duplicating
5//! the first literal. Longer clauses are split using auxiliary variables:
6//! NAE(a,b,R) iff there exists z: NAE(a,b,z) and NAE(-z,R).
7
8use 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/// Result of reducing NAE-Satisfiability to PartitionIntoPerfectMatchings.
58#[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;