1use crate::models::formula::KSatisfiability;
20use crate::models::misc::CyclicOrdering;
21use crate::reduction;
22use crate::rules::sat_helpers::SatVariableAllocator;
23use crate::rules::traits::{ReduceTo, ReductionResult};
24use crate::variant::K3;
25use std::collections::BTreeSet;
26
27#[derive(Debug, Clone)]
28pub struct Reduction3SATToCyclicOrdering {
29 target: CyclicOrdering,
30 source_num_vars: usize,
31 source_variables: Vec<usize>,
32}
33
34impl ReductionResult for Reduction3SATToCyclicOrdering {
35 type Source = KSatisfiability<K3>;
36 type Target = CyclicOrdering;
37
38 fn target_problem(&self) -> &Self::Target {
39 &self.target
40 }
41
42 fn extract_solution(
43 &self,
44 target_solution: &<Self::Target as crate::traits::Problem>::Solution,
45 ) -> crate::rules::ExtractionResult<<Self::Source as crate::traits::Problem>::Solution> {
46 let value =
47 crate::rules::traits::validate_target_solution(self.target_problem(), target_solution)?;
48 if !value.0 {
49 return Err(crate::rules::ExtractionError::invalid(
50 "target configuration is not a feasible cyclic ordering",
51 ));
52 }
53 let mut assignment = vec![false; self.source_num_vars];
54 for (compact, &original) in self.source_variables.iter().enumerate() {
55 let (alpha, beta, gamma) = variable_triple(compact);
56 assignment[original] = !is_cyclic_order(
57 target_solution[alpha],
58 target_solution[beta],
59 target_solution[gamma],
60 );
61 }
62 Ok(assignment)
63 }
64}
65
66fn variable_triple(var_idx: usize) -> (usize, usize, usize) {
67 let base = 3 * var_idx;
68 (base, base + 1, base + 2)
69}
70
71fn literal_triple(literal: i64) -> (usize, usize, usize) {
72 let (alpha, beta, gamma) = variable_triple(
73 usize::try_from(literal.unsigned_abs()).expect("normalized literal indices fit usize") - 1,
74 );
75 if literal > 0 {
76 (alpha, beta, gamma)
77 } else {
78 (alpha, gamma, beta)
79 }
80}
81
82#[allow(clippy::nonminimal_bool)]
83fn is_cyclic_order(a: usize, b: usize, c: usize) -> bool {
84 (a < b && b < c) || (b < c && c < a) || (c < a && a < b)
85}
86
87struct NormalizedFormula {
90 source_variables: Vec<usize>,
91 num_vars: usize,
92 clauses: Vec<[i64; 3]>,
93}
94
95fn normalize(
96 source: &KSatisfiability<K3>,
97) -> Result<NormalizedFormula, crate::rules::ReductionError> {
98 let mut clauses = Vec::new();
99 let mut variables = BTreeSet::new();
100 for clause in source.clauses() {
101 let mut literals = clause.literals.clone();
102 literals.sort_unstable_by_key(|literal| (literal.unsigned_abs(), *literal));
103 literals.dedup();
104 if literals.windows(2).any(|pair| pair[0] == -pair[1]) {
105 continue;
106 }
107 for literal in &literals {
108 variables.insert(
109 usize::try_from(literal.unsigned_abs()).expect("native SAT indices fit usize") - 1,
110 );
111 }
112 clauses.push(literals);
113 }
114 let source_variables: Vec<_> = variables.into_iter().collect();
115 let mut variables =
116 SatVariableAllocator::new("KSatisfiability -> CyclicOrdering", source_variables.len())
117 .map_err(<KSatisfiability<K3> as ReduceTo<CyclicOrdering>>::target_construction)?;
118 let mut normalized = Vec::new();
119 for clause in clauses {
120 let literals: Vec<_> = clause
121 .iter()
122 .map(|literal| {
123 let original = usize::try_from(literal.unsigned_abs())
124 .expect("native SAT indices fit usize")
125 - 1;
126 let compact = source_variables
127 .binary_search(&original)
128 .expect("all retained variables were collected")
129 + 1;
130 let index = i64::try_from(compact)
131 .expect("compaction cannot increase a valid source variable index");
132 if *literal > 0 {
133 index
134 } else {
135 -index
136 }
137 })
138 .collect();
139 match *literals.as_slice() {
140 [a, b, c] => normalized.push([a, b, c]),
141 [a, b] => {
142 let u = variables.allocate()
143 .map_err(<KSatisfiability<K3> as ReduceTo<CyclicOrdering>>::target_construction)?;
144 normalized.extend([[a, b, u], [a, b, -u]]);
145 }
146 [a] => {
147 let u = variables.allocate()
148 .map_err(<KSatisfiability<K3> as ReduceTo<CyclicOrdering>>::target_construction)?;
149 let v = variables.allocate()
150 .map_err(<KSatisfiability<K3> as ReduceTo<CyclicOrdering>>::target_construction)?;
151 normalized.extend([[a, u, v], [a, u, -v], [a, -u, v], [a, -u, -v]]);
152 }
153 _ => unreachable!("empty clauses are handled before normalization; native clauses have at most three literals"),
154 }
155 }
156 Ok(NormalizedFormula {
159 source_variables,
160 num_vars: variables.num_vars(),
161 clauses: normalized,
162 })
163}
164
165#[reduction(
166 transform = upper_bound {
167 num_elements = "3 * num_vars + 26 * num_clauses + 3",
168 num_triples = "40 * num_clauses + 2",
169 }
170)]
171impl ReduceTo<CyclicOrdering> for KSatisfiability<K3> {
172 type Result = Reduction3SATToCyclicOrdering;
173
174 fn reduce_to(&self) -> Result<Self::Result, crate::rules::ReductionError> {
175 if self
176 .clauses()
177 .iter()
178 .any(|clause| clause.literals.is_empty())
179 {
180 return Ok(Reduction3SATToCyclicOrdering {
181 target: CyclicOrdering::try_new(3, vec![(0, 1, 2), (0, 2, 1)])
182 .map_err(<Self as ReduceTo<CyclicOrdering>>::target_construction)?,
183 source_num_vars: self.num_vars(),
184 source_variables: Vec::new(),
185 });
186 }
187 let normalized = normalize(self)?;
188 let num_vars = normalized.num_vars;
189 let num_clauses = normalized.clauses.len();
190 let overflow = |operation| {
191 crate::rules::ReductionError::integer_overflow::<Self, CyclicOrdering>(operation)
192 };
193 let variable_elements = num_vars
194 .checked_mul(3)
195 .ok_or_else(|| overflow("counting variable elements"))?;
196 let clause_elements = num_clauses
197 .checked_mul(5)
198 .ok_or_else(|| overflow("counting clause elements"))?;
199 let num_elements = variable_elements
200 .checked_add(clause_elements)
201 .ok_or_else(|| overflow("counting target elements"))?
202 .max(1);
203 let num_triples = num_clauses
204 .checked_mul(10)
205 .ok_or_else(|| overflow("counting target triples"))?;
206 let mut triples = Vec::with_capacity(num_triples);
207
208 for (clause_idx, clause) in normalized.clauses.iter().enumerate() {
209 let (a, b, c) = literal_triple(clause[0]);
210 let (d, e, f) = literal_triple(clause[1]);
211 let (g, h, i) = literal_triple(clause[2]);
212
213 let base = variable_elements + 5 * clause_idx;
214 let j = base;
215 let k = base + 1;
216 let l = base + 2;
217 let m = base + 3;
218 let n = base + 4;
219
220 triples.extend([
221 (a, c, j),
222 (b, j, k),
223 (c, k, l),
224 (d, f, j),
225 (e, j, l),
226 (f, l, m),
227 (g, i, k),
228 (h, k, m),
229 (i, m, n),
230 (n, m, l),
231 ]);
232 }
233
234 Ok(Reduction3SATToCyclicOrdering {
235 target: CyclicOrdering::try_new(num_elements, triples)
236 .map_err(<Self as ReduceTo<CyclicOrdering>>::target_construction)?,
237 source_num_vars: self.num_vars(),
238 source_variables: normalized.source_variables,
239 })
240 }
241}
242
243#[cfg(feature = "example-db")]
244pub(crate) fn canonical_rule_example_specs() -> Vec<crate::example_db::specs::RuleExampleSpec> {
245 use crate::export::SolutionPair;
246 use crate::models::formula::CNFClause;
247
248 vec![crate::example_db::specs::RuleExampleSpec {
249 id: "ksatisfiability_to_cyclicordering",
250 build: || {
251 crate::example_db::specs::rule_example_with_witness::<_, CyclicOrdering>(
252 KSatisfiability::<K3>::new(3, vec![CNFClause::new(vec![1, 2, 3])]),
253 SolutionPair {
254 source_config: serde_json::json!(vec![true, true, true]),
255 target_config: serde_json::json!(vec![
256 0, 11, 1, 9, 12, 10, 6, 13, 7, 2, 3, 4, 8, 5
257 ]),
258 },
259 )
260 },
261 }]
262}
263
264#[cfg(test)]
265#[path = "../unit_tests/rules/ksatisfiability_cyclicordering.rs"]
266mod tests;