1use crate::models::decision::Decision;
15use crate::models::formula::NAESatisfiability;
16use crate::models::graph::MaxCut;
17use crate::reduction;
18use crate::rules::traits::{ReduceTo, ReductionResult};
19use crate::topology::SimpleGraph;
20
21#[derive(Debug, Clone)]
23pub struct ReductionNAESATToMaxCut {
24 target: Decision<MaxCut<SimpleGraph, i64>>,
25 source_num_vars: usize,
26}
27
28impl ReductionResult for ReductionNAESATToMaxCut {
29 type Source = NAESatisfiability;
30 type Target = Decision<MaxCut<SimpleGraph, i64>>;
31
32 fn target_problem(&self) -> &Self::Target {
33 &self.target
34 }
35
36 fn extract_solution(
42 &self,
43 target_solution: &<Self::Target as crate::traits::Problem>::Solution,
44 ) -> crate::rules::ExtractionResult<<Self::Source as crate::traits::Problem>::Solution> {
45 crate::rules::traits::validate_target_witness(
46 self.target_problem(),
47 target_solution,
48 |value| value.0,
49 "target cut does not certify a satisfying NAE assignment",
50 )?;
51
52 Ok({
53 (0..self.source_num_vars)
54 .map(|i| target_solution[2 * i])
55 .collect()
56 })
57 }
58}
59
60#[crate::aggregate_reduction(identity)]
61impl crate::rules::AggregateReductionResult for ReductionNAESATToMaxCut {}
62
63fn nae_maxcut_parameters(
65 n: usize,
66 lengths: impl ExactSizeIterator<Item = usize>,
67) -> Result<(usize, usize, i64, i64), crate::rules::ReductionError> {
68 let overflow = |operation| {
69 crate::rules::ReductionError::integer_overflow::<
70 NAESatisfiability,
71 Decision<MaxCut<SimpleGraph, i64>>,
72 >(operation)
73 };
74 let weight = i64::try_from(lengths.len())
75 .ok()
76 .and_then(|m| m.checked_add(1))
77 .ok_or_else(|| overflow("computing the variable-edge weight"))?;
78 let mut variables = n;
79 let mut clause_edges = 0usize;
80 let mut clause_cap = 0usize;
81 for length in lengths {
82 let (auxiliary, edges, cap) = if length == 2 {
84 (0, 1, 1)
85 } else {
86 let triangles = length - 2;
87 (
88 length - 3,
89 triangles
90 .checked_mul(3)
91 .ok_or_else(|| overflow("counting clause edges"))?,
92 triangles
93 .checked_mul(2)
94 .ok_or_else(|| overflow("counting clause cut capacity"))?,
95 )
96 };
97 variables = variables
98 .checked_add(auxiliary)
99 .ok_or_else(|| overflow("counting auxiliary variables"))?;
100 clause_edges = clause_edges
101 .checked_add(edges)
102 .ok_or_else(|| overflow("counting all clause edges"))?;
103 clause_cap = clause_cap
104 .checked_add(cap)
105 .ok_or_else(|| overflow("counting all clause cut capacities"))?;
106 }
107 let vertices = variables
108 .checked_mul(2)
109 .ok_or_else(|| overflow("counting literal vertices"))?;
110 let edges = variables
111 .checked_add(clause_edges)
112 .ok_or_else(|| overflow("counting target edges"))?;
113 let variable_weight = i64::try_from(variables)
114 .ok()
115 .and_then(|q| q.checked_mul(weight))
116 .ok_or_else(|| overflow("summing variable-edge weights"))?;
117 variable_weight
119 .checked_add(
120 i64::try_from(clause_edges).map_err(|_| overflow("converting clause-edge count"))?,
121 )
122 .ok_or_else(|| overflow("summing all target edge weights"))?;
123 let feasible_cut = variable_weight
124 .checked_add(
125 i64::try_from(clause_cap).map_err(|_| overflow("converting clause cut capacity"))?,
126 )
127 .ok_or_else(|| overflow("computing the NAE cut certificate"))?;
128 petgraph::graph::DefaultIx::try_from(vertices)
130 .map_err(|_| overflow("representing target vertex indices"))?;
131 petgraph::graph::DefaultIx::try_from(edges)
132 .map_err(|_| overflow("representing target edge indices"))?;
133 Ok((vertices, edges, weight, feasible_cut))
134}
135
136#[reduction(
137 transform = upper_bound {
138 num_vertices = "2 * (num_vars + num_literals - 2 * num_clauses)",
139 num_edges = "num_vars + 4 * num_literals - 7 * num_clauses",
140 }
141)]
142impl ReduceTo<Decision<MaxCut<SimpleGraph, i64>>> for NAESatisfiability {
143 type Result = ReductionNAESATToMaxCut;
144
145 fn reduce_to(&self) -> Result<Self::Result, crate::rules::ReductionError> {
146 let (total_vertices, total_edges, weight, feasible_cut) =
147 nae_maxcut_parameters(self.num_vars(), self.clauses().iter().map(|c| c.len()))?;
148 let total_variables = total_vertices / 2;
149 let mut edges = Vec::with_capacity(total_edges);
150 let mut weights = Vec::with_capacity(total_edges);
151 for i in 0..total_variables {
152 edges.push((2 * i, 2 * i + 1));
153 weights.push(weight);
154 }
155
156 let mut next_auxiliary = self.num_vars();
157 for clause in self.clauses() {
158 let literals: Vec<_> = clause
159 .literals
160 .iter()
161 .map(|&literal| {
162 let index = usize::try_from(literal.unsigned_abs()).map_err(|_| {
163 crate::rules::ReductionError::integer_overflow::<
164 NAESatisfiability,
165 Decision<MaxCut<SimpleGraph, i64>>,
166 >("converting a literal index")
167 })? - 1;
168 Ok(2 * index + usize::from(literal < 0))
170 })
171 .collect::<Result<_, crate::rules::ReductionError>>()?;
172 if literals.len() == 2 {
173 edges.push((literals[0], literals[1]));
174 weights.push(1);
175 } else {
176 let mut first = literals[0];
177 for &middle in &literals[1..literals.len() - 2] {
178 let auxiliary = 2 * next_auxiliary;
179 next_auxiliary += 1;
180 edges.extend([(first, middle), (first, auxiliary), (middle, auxiliary)]);
181 weights.extend([1; 3]);
182 first = auxiliary + 1;
183 }
184 let second = literals[literals.len() - 2];
185 let third = literals[literals.len() - 1];
186 edges.extend([(first, second), (first, third), (second, third)]);
187 weights.extend([1; 3]);
188 }
189 }
190
191 Ok(ReductionNAESATToMaxCut {
192 target: Decision::new(
193 MaxCut::new(SimpleGraph::new(total_vertices, edges), weights),
194 feasible_cut,
195 ),
196 source_num_vars: self.num_vars(),
197 })
198 }
199}
200
201#[cfg(feature = "example-db")]
202pub(crate) fn canonical_rule_example_specs() -> Vec<crate::example_db::specs::RuleExampleSpec> {
203 use crate::export::SolutionPair;
204 use crate::models::formula::CNFClause;
205
206 vec![crate::example_db::specs::RuleExampleSpec {
207 id: "naesatisfiability_to_maxcut",
208 build: || {
209 let source = NAESatisfiability::new(
214 3,
215 vec![
216 CNFClause::new(vec![1, 2, -3]),
217 CNFClause::new(vec![-1, 3, 2]),
218 ],
219 );
220 crate::example_db::specs::rule_example_with_witness::<
221 _,
222 Decision<MaxCut<SimpleGraph, i64>>,
223 >(
224 source,
225 SolutionPair {
226 source_config: serde_json::json!(vec![true, false, true]),
228 target_config: serde_json::json!(vec![true, false, false, true, true, false]),
234 },
235 )
236 },
237 }]
238}
239
240#[cfg(test)]
241#[path = "../unit_tests/rules/naesatisfiability_maxcut.rs"]
242mod tests;