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: [i64; 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(
69        &self,
70        target_solution: &<Self::Target as crate::traits::Problem>::Solution,
71    ) -> crate::rules::ExtractionResult<<Self::Source as crate::traits::Problem>::Solution> {
72        crate::rules::traits::validate_target_solution(self.target_problem(), target_solution)?;
73
74        Ok({
75            self.layout
76                .variables
77                .iter()
78                .map(|variable| target_solution[variable.t] == 0)
79                .collect()
80        })
81    }
82}
83
84impl ReductionNAESATToPartitionIntoPerfectMatchings {
85    #[cfg(any(test, feature = "example-db"))]
86    fn construct_target_solution(&self, source_solution: &[bool]) -> Vec<usize> {
87        assert_eq!(
88            source_solution.len(),
89            self.layout.variables.len(),
90            "source solution has {} variables but reduction expects {}",
91            source_solution.len(),
92            self.layout.variables.len()
93        );
94
95        let mut target_solution = vec![usize::MAX; self.layout.num_vertices];
96        let mut true_groups = Vec::with_capacity(self.layout.variables.len());
97        let mut false_groups = Vec::with_capacity(self.layout.variables.len());
98
99        for (index, variable) in self.layout.variables.iter().enumerate() {
100            let true_group = if source_solution[index] { 0 } else { 1 };
101            let false_group = 1 - true_group;
102            true_groups.push(true_group);
103            false_groups.push(false_group);
104
105            target_solution[variable.t] = true_group;
106            target_solution[variable.t_prime] = true_group;
107            target_solution[variable.f] = false_group;
108            target_solution[variable.f_prime] = false_group;
109        }
110
111        for clause in &self.layout.clauses {
112            for (signal, &literal) in clause.signals.iter().zip(clause.literals.iter()) {
113                let variable_index = literal.unsigned_abs() as usize - 1;
114                let signal_group = if literal > 0 {
115                    true_groups[variable_index]
116                } else {
117                    false_groups[variable_index]
118                };
119                target_solution[signal.s] = signal_group;
120                target_solution[signal.s_prime] = signal_group;
121            }
122        }
123
124        for (variable_index, chain_pairs) in self.layout.positive_chains.iter().enumerate() {
125            for pair in chain_pairs {
126                target_solution[pair.mu] = false_groups[variable_index];
127                target_solution[pair.mu_prime] = false_groups[variable_index];
128            }
129        }
130
131        for (variable_index, chain_pairs) in self.layout.negative_chains.iter().enumerate() {
132            for pair in chain_pairs {
133                target_solution[pair.mu] = true_groups[variable_index];
134                target_solution[pair.mu_prime] = true_groups[variable_index];
135            }
136        }
137
138        for clause in &self.layout.clauses {
139            let clause_groups = clause.signals.map(|signal| 1 - target_solution[signal.s]);
140            let zero_count = clause_groups.iter().filter(|&&group| group == 0).count();
141            let w3_group = match zero_count {
142                1 => 0,
143                2 => 1,
144                _ => panic!("source assignment is not NAE-satisfying for normalized clauses"),
145            };
146
147            for (vertex, &group) in clause
148                .clause_vertices
149                .iter()
150                .take(3)
151                .zip(clause_groups.iter())
152            {
153                target_solution[*vertex] = group;
154            }
155            target_solution[clause.clause_vertices[3]] = w3_group;
156        }
157
158        assert!(
159            target_solution.iter().all(|&group| group <= 1),
160            "constructed target solution left some vertices unassigned"
161        );
162
163        target_solution
164    }
165}
166
167fn normalize_clauses(
168    problem: &NAESatisfiability,
169) -> Result<Vec<[i64; 3]>, crate::registry::ConstructionError> {
170    problem
171        .clauses()
172        .iter()
173        .map(|clause| match clause.literals.as_slice() {
174            [a, b] => Ok([*a, *a, *b]),
175            [a, b, c] => Ok([*a, *b, *c]),
176            literals => Err(format!(
177                "the construction expects clauses of size 2 or 3, got {}",
178                literals.len()
179            )
180            .into()),
181        })
182        .collect()
183}
184
185fn build_layout(
186    problem: &NAESatisfiability,
187) -> Result<ReductionLayout, crate::registry::ConstructionError> {
188    let num_vars = problem.num_vars();
189    let clauses = normalize_clauses(problem)?;
190    let num_clauses = clauses.len();
191
192    let mut next_vertex = 0usize;
193    let mut edges = Vec::with_capacity(3 * num_vars + 21 * num_clauses);
194    let mut variables = Vec::with_capacity(num_vars);
195
196    for _ in 0..num_vars {
197        let variable = VariableVertices {
198            t: next_vertex,
199            t_prime: next_vertex + 1,
200            f: next_vertex + 2,
201            f_prime: next_vertex + 3,
202        };
203        next_vertex += 4;
204
205        edges.push((variable.t, variable.t_prime));
206        edges.push((variable.f, variable.f_prime));
207        edges.push((variable.t, variable.f));
208        variables.push(variable);
209    }
210
211    let mut clause_layouts = Vec::with_capacity(num_clauses);
212
213    for &literals in &clauses {
214        let mut signals = [SignalVertices { s: 0, s_prime: 0 }; 3];
215        for (literal_index, _) in literals.iter().enumerate() {
216            signals[literal_index] = SignalVertices {
217                s: next_vertex,
218                s_prime: next_vertex + 1,
219            };
220            next_vertex += 2;
221            edges.push((signals[literal_index].s, signals[literal_index].s_prime));
222        }
223
224        clause_layouts.push(ClauseLayout {
225            literals,
226            signals,
227            clause_vertices: [0; 4],
228        });
229    }
230
231    for clause_layout in &mut clause_layouts {
232        let clause_vertices = [
233            next_vertex,
234            next_vertex + 1,
235            next_vertex + 2,
236            next_vertex + 3,
237        ];
238        next_vertex += 4;
239
240        for a in 0..4 {
241            for b in (a + 1)..4 {
242                edges.push((clause_vertices[a], clause_vertices[b]));
243            }
244        }
245        for (literal_index, &clause_vertex) in clause_vertices.iter().enumerate().take(3) {
246            edges.push((clause_layout.signals[literal_index].s, clause_vertex));
247        }
248        clause_layout.clause_vertices = clause_vertices;
249    }
250
251    let mut positive_occurrences: Vec<Vec<(usize, usize)>> = vec![Vec::new(); num_vars];
252    let mut negative_occurrences: Vec<Vec<(usize, usize)>> = vec![Vec::new(); num_vars];
253    for (clause_index, clause_layout) in clause_layouts.iter().enumerate() {
254        for (literal_index, &literal) in clause_layout.literals.iter().enumerate() {
255            let variable_index = literal.unsigned_abs() as usize - 1;
256            if literal > 0 {
257                positive_occurrences[variable_index].push((clause_index, literal_index));
258            } else {
259                negative_occurrences[variable_index].push((clause_index, literal_index));
260            }
261        }
262    }
263
264    let mut positive_chains: Vec<Vec<ChainPairVertices>> = vec![Vec::new(); num_vars];
265    let mut negative_chains: Vec<Vec<ChainPairVertices>> = vec![Vec::new(); num_vars];
266
267    for variable_index in 0..num_vars {
268        let mut source_vertex = variables[variable_index].t;
269        for &(clause_index, literal_index) in &positive_occurrences[variable_index] {
270            let pair = ChainPairVertices {
271                mu: next_vertex,
272                mu_prime: next_vertex + 1,
273            };
274            next_vertex += 2;
275
276            let signal_vertex = clause_layouts[clause_index].signals[literal_index].s;
277            edges.push((pair.mu, pair.mu_prime));
278            edges.push((source_vertex, pair.mu));
279            edges.push((signal_vertex, pair.mu));
280            positive_chains[variable_index].push(pair);
281            source_vertex = signal_vertex;
282        }
283
284        let mut source_vertex = variables[variable_index].f;
285        for &(clause_index, literal_index) in &negative_occurrences[variable_index] {
286            let pair = ChainPairVertices {
287                mu: next_vertex,
288                mu_prime: next_vertex + 1,
289            };
290            next_vertex += 2;
291
292            let signal_vertex = clause_layouts[clause_index].signals[literal_index].s;
293            edges.push((pair.mu, pair.mu_prime));
294            edges.push((source_vertex, pair.mu));
295            edges.push((signal_vertex, pair.mu));
296            negative_chains[variable_index].push(pair);
297            source_vertex = signal_vertex;
298        }
299    }
300
301    Ok(ReductionLayout {
302        variables,
303        #[cfg(any(test, feature = "example-db"))]
304        clauses: clause_layouts,
305        #[cfg(any(test, feature = "example-db"))]
306        positive_chains,
307        #[cfg(any(test, feature = "example-db"))]
308        negative_chains,
309        num_vertices: next_vertex,
310        edges,
311    })
312}
313
314#[reduction(
315    transform = exact {
316        num_vertices = "4 * num_vars + 16 * num_clauses",
317        num_edges = "3 * num_vars + 21 * num_clauses",
318        num_matchings = "2",
319    }
320)]
321impl ReduceTo<PartitionIntoPerfectMatchings<SimpleGraph>> for NAESatisfiability {
322    type Result = ReductionNAESATToPartitionIntoPerfectMatchings;
323
324    fn reduce_to(&self) -> Result<Self::Result, crate::rules::ReductionError> {
325        let layout = build_layout(self).map_err(|message| {
326            crate::rules::ReductionError::invalid_target::<
327                NAESatisfiability,
328                PartitionIntoPerfectMatchings<SimpleGraph>,
329            >(message.to_string())
330        })?;
331        let target = PartitionIntoPerfectMatchings::new(
332            SimpleGraph::new(layout.num_vertices, layout.edges.clone()),
333            2,
334        );
335
336        Ok(ReductionNAESATToPartitionIntoPerfectMatchings { target, layout })
337    }
338}
339
340#[cfg(feature = "example-db")]
341pub(crate) fn canonical_rule_example_specs() -> Vec<crate::example_db::specs::RuleExampleSpec> {
342    use crate::export::SolutionPair;
343    use crate::models::formula::CNFClause;
344
345    vec![crate::example_db::specs::RuleExampleSpec {
346        id: "naesatisfiability_to_partitionintoperfectmatchings",
347        build: || {
348            let source = NAESatisfiability::new(
349                3,
350                vec![
351                    CNFClause::new(vec![1, 2, 3]),
352                    CNFClause::new(vec![-1, 2, -3]),
353                ],
354            );
355            let source_config = vec![true, true, false];
356            let reduction =
357                ReduceTo::<PartitionIntoPerfectMatchings<SimpleGraph>>::reduce_to(&source)
358                    .expect("reduction should succeed");
359            let target_config = reduction.construct_target_solution(&source_config);
360
361            crate::example_db::specs::assemble_rule_example(
362                &source,
363                reduction.target_problem(),
364                vec![SolutionPair {
365                    source_config: serde_json::to_value(source_config)
366                        .expect("solution serialization must succeed"),
367                    target_config: serde_json::to_value(target_config)
368                        .expect("solution serialization must succeed"),
369                }],
370            )
371        },
372    }]
373}
374
375#[cfg(test)]
376#[path = "../unit_tests/rules/naesatisfiability_partitionintoperfectmatchings.rs"]
377mod tests;