problemreductions/rules/
ksatisfiability_simultaneousincongruences.rs1use std::collections::BTreeMap;
8
9use crate::models::algebraic::SimultaneousIncongruences;
10use crate::models::formula::{ksat::first_n_odd_primes, CNFClause, KSatisfiability};
11use crate::reduction;
12use crate::rules::traits::{ReduceTo, ReductionResult};
13use crate::variant::K3;
14
15#[derive(Debug, Clone)]
16pub struct Reduction3SATToSimultaneousIncongruences {
17 target: SimultaneousIncongruences,
18 variable_primes: Vec<u64>,
19}
20
21impl ReductionResult for Reduction3SATToSimultaneousIncongruences {
22 type Source = KSatisfiability<K3>;
23 type Target = SimultaneousIncongruences;
24
25 fn target_problem(&self) -> &Self::Target {
26 &self.target
27 }
28
29 fn extract_solution(
30 &self,
31 target_solution: &<Self::Target as crate::traits::Problem>::Solution,
32 ) -> crate::rules::ExtractionResult<<Self::Source as crate::traits::Problem>::Solution> {
33 crate::rules::traits::validate_target_witness(
34 self.target_problem(),
35 target_solution,
36 |value| value.0,
37 "target witness does not satisfy the target problem",
38 )?;
39
40 Ok({
41 let x = u64::try_from(*target_solution).map_err(|_| {
42 crate::rules::ExtractionError::invalid(
43 "target value cannot be represented in the CRT implementation domain",
44 )
45 })?;
46 self.variable_primes
47 .iter()
48 .map(|&prime| x % prime == 1)
49 .collect()
50 })
51 }
52}
53
54fn falsifying_residue(literal: i64) -> u64 {
55 if literal > 0 {
56 2
57 } else {
58 1
59 }
60}
61
62fn modular_inverse(value: u64, modulus: u64) -> Option<u64> {
63 let mut t = 0i128;
64 let mut new_t = 1i128;
65 let mut r = modulus as i128;
66 let mut new_r = value as i128;
67
68 while new_r != 0 {
69 let quotient = r / new_r;
70 (t, new_t) = (new_t, t - quotient * new_t);
71 (r, new_r) = (new_r, r - quotient * new_r);
72 }
73
74 if r != 1 {
75 return None;
76 }
77 if t < 0 {
78 t += modulus as i128;
79 }
80 Some(t as u64)
81}
82
83fn crt_residue(congruences: &[(u64, u64)]) -> Result<(u64, u64), &'static str> {
84 let modulus = congruences
85 .iter()
86 .try_fold(1u64, |product, &(m, _)| product.checked_mul(m))
87 .ok_or("CRT modulus product overflow")?;
88
89 let residue = congruences
90 .iter()
91 .try_fold(0u128, |acc, &(modulus_i, residue_i)| {
92 let partial = modulus / modulus_i;
93 let inverse = modular_inverse(partial % modulus_i, modulus_i)
94 .ok_or("CRT moduli must be pairwise coprime")?;
95 let term = u128::from(residue_i)
96 .checked_mul(u128::from(partial))
97 .and_then(|value| value.checked_mul(u128::from(inverse)))
98 .ok_or("CRT residue term overflow")?;
99 acc.checked_add(term).ok_or("CRT residue sum overflow")
100 })?
101 % modulus as u128;
102
103 Ok((residue as u64, modulus))
104}
105
106fn clause_bad_residue(
107 clause: &CNFClause,
108 variable_primes: &[u64],
109) -> Result<(u64, u64), &'static str> {
110 let mut residue_by_var = BTreeMap::new();
111 let mut contradictory_var = None;
112
113 for &literal in &clause.literals {
114 let var_index =
115 usize::try_from(literal.unsigned_abs()).map_err(|_| "literal index exceeds usize")? - 1;
116 let residue = falsifying_residue(literal);
117
118 match residue_by_var.insert(var_index, residue) {
119 Some(existing) if existing != residue => {
120 contradictory_var = Some(var_index);
121 residue_by_var.insert(var_index, 0);
122 break;
123 }
124 Some(existing) => {
125 residue_by_var.insert(var_index, existing);
126 }
127 None => {}
128 }
129 }
130
131 if let Some(var_index) = contradictory_var {
132 for &literal in &clause.literals {
133 let candidate = usize::try_from(literal.unsigned_abs())
134 .map_err(|_| "literal index exceeds usize")?
135 - 1;
136 if candidate != var_index {
137 residue_by_var
138 .entry(candidate)
139 .or_insert_with(|| falsifying_residue(literal));
140 }
141 }
142 }
143
144 let congruences = residue_by_var
145 .into_iter()
146 .map(|(var_index, residue)| {
147 variable_primes
148 .get(var_index)
149 .copied()
150 .map(|prime| (prime, residue))
151 .ok_or("clause variable index exceeds num_vars")
152 })
153 .collect::<Result<Vec<_>, _>>()?;
154
155 crt_residue(&congruences)
156}
157
158fn ensure_prime_product_fits_target(
159 variable_primes: &[u64],
160) -> Result<(), crate::registry::ConstructionError> {
161 let mut product = 1u128;
162 for &prime in variable_primes {
163 product = product.checked_mul(prime as u128).ok_or_else(|| {
164 format!(
165 "variable-prime product overflows for {} variables",
166 variable_primes.len()
167 )
168 })?;
169 if product > i64::MAX as u128 {
170 return Err(format!(
171 "variable-prime product {product} for {} variables exceeds the target i64 domain",
172 variable_primes.len()
173 )
174 .into());
175 }
176 }
177 Ok(())
178}
179
180#[crate::aggregate_reduction(identity)]
181impl crate::rules::AggregateReductionResult for Reduction3SATToSimultaneousIncongruences {}
182
183#[reduction(
184 transform = upper_bound {
185 num_pairs = "2 * num_vars * (num_vars + 1)^2 + num_clauses",
186 }
187)]
188impl ReduceTo<SimultaneousIncongruences> for KSatisfiability<K3> {
189 type Result = Reduction3SATToSimultaneousIncongruences;
190
191 fn reduce_to(&self) -> Result<Self::Result, crate::rules::ReductionError> {
192 let variable_primes = first_n_odd_primes(self.num_vars());
193 ensure_prime_product_fits_target(&variable_primes).map_err(|message| {
194 crate::rules::ReductionError::invalid_target::<
195 KSatisfiability<K3>,
196 SimultaneousIncongruences,
197 >(message.to_string())
198 })?;
199
200 let mut pairs = Vec::new();
201
202 for &prime in &variable_primes {
203 pairs.push((prime, prime));
207 for residue in 3..prime {
208 pairs.push((residue, prime));
209 }
210 }
211
212 for clause in self.clauses() {
213 let (bad_residue, clause_modulus) = clause_bad_residue(clause, &variable_primes)
214 .map_err(|message| {
215 crate::rules::ReductionError::invalid_target::<
216 KSatisfiability<K3>,
217 SimultaneousIncongruences,
218 >(message)
219 })?;
220 let a = if bad_residue == 0 {
223 clause_modulus
224 } else {
225 bad_residue
226 };
227 pairs.push((a, clause_modulus));
228 }
229
230 let pairs = pairs
231 .into_iter()
232 .map(|(residue, modulus)| {
233 Ok((
234 i64::try_from(residue).map_err(|_| "residue exceeds i64")?,
235 i64::try_from(modulus).map_err(|_| "modulus exceeds i64")?,
236 ))
237 })
238 .collect::<Result<Vec<_>, &str>>()
239 .map_err(|message| {
240 crate::rules::ReductionError::invalid_target::<
241 KSatisfiability<K3>,
242 SimultaneousIncongruences,
243 >(message)
244 })?;
245 let target = SimultaneousIncongruences::new(pairs).map_err(|message| {
246 crate::rules::ReductionError::construction::<
247 KSatisfiability<K3>,
248 SimultaneousIncongruences,
249 >(message)
250 })?;
251 Ok(Reduction3SATToSimultaneousIncongruences {
252 target,
253 variable_primes,
254 })
255 }
256}
257
258#[cfg(feature = "example-db")]
259pub(crate) fn canonical_rule_example_specs() -> Vec<crate::example_db::specs::RuleExampleSpec> {
260 use crate::export::SolutionPair;
261
262 vec![crate::example_db::specs::RuleExampleSpec {
263 id: "ksatisfiability_to_simultaneous_incongruences",
264 build: || {
265 let source = KSatisfiability::<K3>::new(
266 2,
267 vec![
268 CNFClause::new(vec![1, 2, 2]),
269 CNFClause::new(vec![-1, 2, 2]),
270 ],
271 );
272 crate::example_db::specs::rule_example_with_witness::<_, SimultaneousIncongruences>(
273 source,
274 SolutionPair {
275 source_config: serde_json::json!(vec![true, true]),
276 target_config: serde_json::json!(1),
277 },
278 )
279 },
280 }]
281}
282
283#[cfg(test)]
284#[path = "../unit_tests/rules/ksatisfiability_simultaneousincongruences.rs"]
285mod tests;