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: [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#[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;