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