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, and clauses with more than three literals are rejected.
6
7use 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/// Result of reducing NAE-Satisfiability to PartitionIntoPerfectMatchings.
54#[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;