1use std::collections::{BTreeMap, BTreeSet};
10
11use crate::models::algebraic::QuadraticCongruences;
12use crate::models::formula::KSatisfiability;
13use crate::reduction;
14use crate::rules::traits::{ReduceTo, ReductionResult};
15use crate::variant::K3;
16use num_bigint::{BigInt, BigUint};
17#[cfg(any(test, feature = "example-db"))]
18use num_traits::Signed;
19use num_traits::{One, Zero};
20
21#[derive(Debug, Clone)]
22pub struct Reduction3SATToQuadraticCongruences {
23 target: QuadraticCongruences,
24 source_num_vars: usize,
25 active_to_source: Vec<usize>,
26 clause_count: usize,
27 h: BigUint,
28 prime_powers: Vec<BigUint>,
29}
30
31impl ReductionResult for Reduction3SATToQuadraticCongruences {
32 type Source = KSatisfiability<K3>;
33 type Target = QuadraticCongruences;
34
35 fn target_problem(&self) -> &Self::Target {
36 &self.target
37 }
38
39 fn extract_solution(
40 &self,
41 target_solution: &<Self::Target as crate::traits::Problem>::Solution,
42 ) -> crate::rules::ExtractionResult<<Self::Source as crate::traits::Problem>::Solution> {
43 crate::rules::traits::validate_target_witness(
44 self.target_problem(),
45 target_solution,
46 |value| value.0,
47 "target integer does not satisfy the bounded quadratic congruence",
48 )?;
49 let h_minus_x = &self.h - target_solution;
53 let positive_orientation = (&h_minus_x % &self.prime_powers[0]).is_zero();
54 let mut assignment = vec![false; self.source_num_vars];
55 for (active, &original) in self.active_to_source.iter().enumerate() {
56 let coordinate = 2 * self.clause_count + active + 1;
57 let positive = (&h_minus_x % &self.prime_powers[coordinate]).is_zero();
58 assignment[original] = positive != positive_orientation;
59 }
60 Ok(assignment)
61 }
62}
63
64#[cfg_attr(not(any(test, feature = "example-db")), allow(dead_code))]
65#[derive(Debug, Clone)]
66struct MandersAdlemanConstruction {
67 target: QuadraticCongruences,
68 source_num_vars: usize,
69 active_to_source: Vec<usize>,
70 clauses: Vec<Vec<i64>>,
71 #[cfg_attr(not(test), allow(dead_code))]
72 coefficients: Vec<BigInt>,
73 #[cfg_attr(not(test), allow(dead_code))]
74 tau: BigInt,
75 thetas: Vec<BigUint>,
76 h: BigUint,
77 prime_powers: Vec<BigUint>,
78}
79
80fn is_prime(candidate: u64) -> bool {
81 if candidate < 2 {
82 return false;
83 }
84 if candidate == 2 {
85 return true;
86 }
87 if candidate.is_multiple_of(2) {
88 return false;
89 }
90 let mut divisor = 3u64;
91 while divisor <= candidate / divisor {
92 if candidate.is_multiple_of(divisor) {
93 return false;
94 }
95 divisor += 2;
96 }
97 true
98}
99
100fn admissible_primes(count: usize) -> Result<Vec<u64>, crate::rules::ReductionError> {
101 let mut primes = Vec::with_capacity(count);
102 let mut candidate = 13u64;
103 while primes.len() < count {
104 if is_prime(candidate) {
105 primes.push(candidate);
106 }
107 candidate = candidate.checked_add(1).ok_or_else(|| {
108 crate::rules::ReductionError::integer_overflow::<
109 KSatisfiability<K3>,
110 QuadraticCongruences,
111 >("enumerating CRT primes")
112 })?;
113 }
114 Ok(primes)
115}
116
117fn bigint_mod_to_biguint(value: &BigInt, modulus: &BigUint) -> BigUint {
118 let modulus_bigint = BigInt::from(modulus.clone());
119 let reduced = ((value % &modulus_bigint) + &modulus_bigint) % &modulus_bigint;
120 reduced
121 .to_biguint()
122 .expect("Euclidean residue is nonnegative")
123}
124
125fn normalize_clause(clause: &[i64]) -> Option<Vec<i64>> {
126 let mut literals = BTreeSet::new();
127 for &literal in clause {
128 if literals.contains(&-literal) {
129 return None;
130 }
131 literals.insert(literal);
132 }
133 Some(literals.into_iter().collect())
134}
135
136fn build_construction(
137 source: &KSatisfiability<K3>,
138) -> Result<MandersAdlemanConstruction, crate::rules::ReductionError> {
139 let clauses: BTreeSet<_> = source
140 .clauses()
141 .iter()
142 .filter_map(|clause| normalize_clause(&clause.literals))
143 .collect();
144 let active_vars: Vec<_> = clauses
145 .iter()
146 .flatten()
147 .map(|literal| {
148 usize::try_from(literal.unsigned_abs()).expect("native SAT indices fit usize")
149 })
150 .collect::<BTreeSet<_>>()
151 .into_iter()
152 .collect();
153 let var_map: BTreeMap<_, _> = active_vars
154 .iter()
155 .enumerate()
156 .map(|(compact, &original)| (original, compact + 1))
157 .collect();
158 let clauses: Vec<Vec<i64>> = clauses
159 .into_iter()
160 .map(|clause| {
161 clause
162 .into_iter()
163 .map(|literal| {
164 let original = usize::try_from(literal.unsigned_abs())
165 .expect("native SAT indices fit usize");
166 let variable =
167 i64::try_from(var_map[&original]).expect("compact SAT indices fit i64");
168 if literal > 0 {
169 variable
170 } else {
171 -variable
172 }
173 })
174 .collect()
175 })
176 .collect();
177 let m = clauses.len();
178 let coordinate_count = m
179 .checked_mul(2)
180 .and_then(|aux| aux.checked_add(active_vars.len()))
181 .and_then(|count| count.checked_add(1))
182 .ok_or_else(|| {
183 crate::rules::ReductionError::integer_overflow::<
184 KSatisfiability<K3>,
185 QuadraticCongruences,
186 >("counting signed-knapsack coordinates")
187 })?;
188 let mut coefficients = vec![BigInt::zero(); coordinate_count];
189 coefficients[0] = BigInt::one();
190 let mut tau = BigInt::one();
191 let mut weight = BigUint::one();
192 for (j, clause) in clauses.iter().enumerate() {
193 weight *= 8u32;
194 let half_weight = BigInt::from(&weight / 2u32);
195 coefficients[2 * j + 1] = -&half_weight;
196 coefficients[2 * j + 2] = -BigInt::from(weight.clone());
197 let width =
198 i64::try_from(clause.len()).expect("native K3 clauses have at most three literals");
199 tau += (width - 5) * &half_weight;
200 for &literal in clause {
201 let variable =
202 usize::try_from(literal.unsigned_abs()).expect("compact SAT indices fit usize");
203 if literal > 0 {
204 coefficients[2 * m + variable] += &half_weight;
205 } else {
206 coefficients[2 * m + variable] -= &half_weight;
207 }
208 }
209 }
210 let linear_modulus = weight * 8u32;
213 let primes = admissible_primes(coordinate_count)?;
214 let prime_powers: Vec<_> = primes
215 .iter()
216 .map(|&prime| num_traits::Pow::pow(BigUint::from(prime), coordinate_count))
217 .collect();
218 let k: BigUint = prime_powers.iter().product();
219 let mut thetas = Vec::with_capacity(coordinate_count);
220 for ((coefficient, prime_power), prime) in coefficients.iter().zip(&prime_powers).zip(&primes) {
221 let other = &k / prime_power;
222 let step = &other * &linear_modulus;
223 let residue = bigint_mod_to_biguint(coefficient, &linear_modulus);
224 let inverse = other
225 .modinv(&linear_modulus)
226 .expect("an odd prime-power product is coprime to a power of two");
227 let mut theta = &other * ((residue * inverse) % &linear_modulus);
228 if theta.is_zero() {
229 theta += &step;
230 }
231 if (&theta % BigUint::from(*prime)).is_zero() {
232 theta += &step;
233 }
234 thetas.push(theta);
235 }
236 let h: BigUint = thetas.iter().sum();
237 let square_modulus = &linear_modulus * 2u32;
240 let b = &square_modulus * &k;
241 let inverse = (&square_modulus + &k)
242 .modinv(&b)
243 .expect("the two CRT moduli are coprime");
244 let tau_squared = (&tau * &tau).to_biguint().expect("a square is nonnegative");
245 let a = (inverse * (&k * tau_squared + &square_modulus * &h * &h)) % &b;
246 let target = QuadraticCongruences::try_new(a, b, &h + BigUint::one()).map_err(
247 crate::rules::ReductionError::construction::<KSatisfiability<K3>, QuadraticCongruences>,
248 )?;
249 Ok(MandersAdlemanConstruction {
250 target,
251 source_num_vars: source.num_vars(),
252 active_to_source: active_vars
253 .into_iter()
254 .map(|variable| variable - 1)
255 .collect(),
256 clauses,
257 coefficients,
258 tau,
259 thetas,
260 h,
261 prime_powers,
262 })
263}
264
265#[cfg(any(test, feature = "example-db"))]
266fn build_alphas(construction: &MandersAdlemanConstruction, assignment: &[bool]) -> Option<Vec<i8>> {
267 if assignment.len() != construction.source_num_vars {
268 return None;
269 }
270 let m = construction.clauses.len();
271 let mut alphas = vec![1; construction.thetas.len()];
272 for (i, &original) in construction.active_to_source.iter().enumerate() {
273 alphas[2 * m + i + 1] = 1 - 2 * i8::from(assignment[original]);
274 }
275 for (j, clause) in construction.clauses.iter().enumerate() {
276 let mut y = -1i8;
277 for &literal in clause {
278 let variable =
279 usize::try_from(literal.unsigned_abs()).expect("compact SAT indices fit usize") - 1;
280 let truth = assignment[construction.active_to_source[variable]];
281 y += i8::from(truth == (literal > 0));
282 }
283 let (first, second) = match y {
284 0 => (1, 1),
285 1 => (-1, 1),
286 2 => (1, -1),
287 _ => return None,
288 };
289 alphas[2 * j + 1] = first;
290 alphas[2 * j + 2] = second;
291 }
292 Some(alphas)
293}
294
295#[cfg(any(test, feature = "example-db"))]
296fn witness_value_from_alphas(alphas: &[i8], thetas: &[BigUint]) -> BigUint {
297 alphas
298 .iter()
299 .zip(thetas)
300 .map(|(&alpha, theta)| BigInt::from(alpha) * BigInt::from(theta.clone()))
301 .sum::<BigInt>()
302 .abs()
303 .to_biguint()
304 .expect("absolute witness is nonnegative")
305}
306
307#[cfg(any(test, feature = "example-db"))]
308fn witness_config_for_assignment(
309 source: &KSatisfiability<K3>,
310 assignment: &[bool],
311) -> Option<BigUint> {
312 let construction = build_construction(source).expect("example or test source must reduce");
313 let alphas = build_alphas(&construction, assignment)?;
314 Some(witness_value_from_alphas(&alphas, &construction.thetas))
315}
316
317#[crate::aggregate_reduction(identity)]
318impl crate::rules::AggregateReductionResult for Reduction3SATToQuadraticCongruences {}
319
320#[reduction(
321 transform = upper_bound {
322 bit_length_a = "64 * (2 * num_clauses + num_vars + 1)^2 + 3 * num_clauses + 4",
323 bit_length_b = "64 * (2 * num_clauses + num_vars + 1)^2 + 3 * num_clauses + 4",
324 bit_length_c = "64 * (2 * num_clauses + num_vars + 1)^2 + 3 * num_clauses + 4",
325 }
326)]
327impl ReduceTo<QuadraticCongruences> for KSatisfiability<K3> {
328 type Result = Reduction3SATToQuadraticCongruences;
329
330 fn reduce_to(&self) -> Result<Self::Result, crate::rules::ReductionError> {
331 let construction = build_construction(self)?;
332 Ok(Reduction3SATToQuadraticCongruences {
333 target: construction.target,
334 source_num_vars: construction.source_num_vars,
335 active_to_source: construction.active_to_source,
336 clause_count: construction.clauses.len(),
337 h: construction.h,
338 prime_powers: construction.prime_powers,
339 })
340 }
341}
342
343#[cfg(feature = "example-db")]
344pub(crate) fn canonical_rule_example_specs() -> Vec<crate::example_db::specs::RuleExampleSpec> {
345 use crate::export::SolutionPair;
346
347 vec![crate::example_db::specs::RuleExampleSpec {
348 id: "ksatisfiability_to_quadraticcongruences",
349 build: || {
350 let source = KSatisfiability::<K3>::new(
351 3,
352 vec![crate::models::formula::CNFClause::new(vec![1, 2, 3])],
353 );
354 let target_config = witness_config_for_assignment(&source, &[true, false, false])
355 .expect("canonical satisfying assignment should lift to a QC witness");
356 crate::example_db::specs::rule_example_with_witness::<_, QuadraticCongruences>(
357 source,
358 SolutionPair {
359 source_config: serde_json::json!(vec![true, false, false]),
360 target_config: serde_json::to_value(target_config)
361 .expect("solution serialization must succeed"),
362 },
363 )
364 },
365 }]
366}
367
368#[cfg(test)]
369#[path = "../unit_tests/rules/ksatisfiability_quadraticcongruences.rs"]
370mod tests;