problemreductions/rules/
ksatisfiability_simultaneousincongruences.rs1use std::collections::BTreeMap;
8
9use crate::models::algebraic::simultaneous_incongruences::MAX_LCM;
10use crate::models::algebraic::SimultaneousIncongruences;
11use crate::models::formula::{ksat::first_n_odd_primes, CNFClause, KSatisfiability};
12use crate::reduction;
13use crate::rules::traits::{ReduceTo, ReductionResult};
14use crate::variant::K3;
15
16#[derive(Debug, Clone)]
17pub struct Reduction3SATToSimultaneousIncongruences {
18 target: SimultaneousIncongruences,
19 variable_primes: Vec<u64>,
20}
21
22impl ReductionResult for Reduction3SATToSimultaneousIncongruences {
23 type Source = KSatisfiability<K3>;
24 type Target = SimultaneousIncongruences;
25
26 fn target_problem(&self) -> &Self::Target {
27 &self.target
28 }
29
30 fn extract_solution(&self, target_solution: &[usize]) -> Vec<usize> {
31 let x = target_solution.first().copied().unwrap_or(0) as u64;
32 self.variable_primes
33 .iter()
34 .map(|&prime| if x % prime == 1 { 1 } else { 0 })
35 .collect()
36 }
37}
38
39fn falsifying_residue(literal: i32) -> u64 {
40 if literal > 0 {
41 2
42 } else {
43 1
44 }
45}
46
47fn modular_inverse(value: u64, modulus: u64) -> u64 {
48 let mut t = 0i128;
49 let mut new_t = 1i128;
50 let mut r = modulus as i128;
51 let mut new_r = value as i128;
52
53 while new_r != 0 {
54 let quotient = r / new_r;
55 (t, new_t) = (new_t, t - quotient * new_t);
56 (r, new_r) = (new_r, r - quotient * new_r);
57 }
58
59 assert_eq!(r, 1, "value and modulus must be coprime");
60 if t < 0 {
61 t += modulus as i128;
62 }
63 t as u64
64}
65
66fn crt_residue(congruences: &[(u64, u64)]) -> (u64, u64) {
67 let modulus = congruences.iter().fold(1u64, |product, &(m, _)| {
68 product
69 .checked_mul(m)
70 .expect("CRT modulus product overflow")
71 });
72
73 let residue = congruences
74 .iter()
75 .fold(0u128, |acc, &(modulus_i, residue_i)| {
76 let partial = modulus / modulus_i;
77 let inverse = modular_inverse(partial % modulus_i, modulus_i);
78 acc + residue_i as u128 * partial as u128 * inverse as u128
79 })
80 % modulus as u128;
81
82 (residue as u64, modulus)
83}
84
85fn clause_bad_residue(clause: &CNFClause, variable_primes: &[u64]) -> (u64, u64) {
86 let mut residue_by_var = BTreeMap::new();
87 let mut contradictory_var = None;
88
89 for &literal in &clause.literals {
90 let var_index = literal.unsigned_abs() as usize - 1;
91 let residue = falsifying_residue(literal);
92
93 match residue_by_var.insert(var_index, residue) {
94 Some(existing) if existing != residue => {
95 contradictory_var = Some(var_index);
96 residue_by_var.insert(var_index, 0);
97 break;
98 }
99 Some(existing) => {
100 residue_by_var.insert(var_index, existing);
101 }
102 None => {}
103 }
104 }
105
106 if let Some(var_index) = contradictory_var {
107 for &literal in &clause.literals {
108 let candidate = literal.unsigned_abs() as usize - 1;
109 if candidate != var_index {
110 residue_by_var
111 .entry(candidate)
112 .or_insert_with(|| falsifying_residue(literal));
113 }
114 }
115 }
116
117 let congruences = residue_by_var
118 .into_iter()
119 .map(|(var_index, residue)| {
120 (
121 *variable_primes
122 .get(var_index)
123 .expect("clause variable index must be within num_vars"),
124 residue,
125 )
126 })
127 .collect::<Vec<_>>();
128
129 crt_residue(&congruences)
130}
131
132fn ensure_prime_product_within_lcm_cap(variable_primes: &[u64]) {
133 let mut product = 1u128;
134 for &prime in variable_primes {
135 product = product.checked_mul(prime as u128).unwrap_or_else(|| {
136 panic!(
137 "3-SAT -> SimultaneousIncongruences requires the variable-prime product to fit within the target model's LCM cap ({MAX_LCM}); num_vars={} overflows while multiplying primes",
138 variable_primes.len()
139 )
140 });
141 if product > MAX_LCM {
142 panic!(
143 "3-SAT -> SimultaneousIncongruences requires the variable-prime product to fit within the target model's LCM cap ({MAX_LCM}); num_vars={} yields prime product {product}",
144 variable_primes.len()
145 );
146 }
147 }
148}
149
150#[reduction(overhead = {
151 num_pairs = "simultaneous_incongruences_num_incongruences",
152})]
153impl ReduceTo<SimultaneousIncongruences> for KSatisfiability<K3> {
154 type Result = Reduction3SATToSimultaneousIncongruences;
155
156 fn reduce_to(&self) -> Self::Result {
157 let variable_primes = first_n_odd_primes(self.num_vars());
158 ensure_prime_product_within_lcm_cap(&variable_primes);
159
160 let mut pairs = Vec::new();
161
162 for &prime in &variable_primes {
163 pairs.push((prime, prime));
167 for residue in 3..prime {
168 pairs.push((residue, prime));
169 }
170 }
171
172 for clause in self.clauses() {
173 let (bad_residue, clause_modulus) = clause_bad_residue(clause, &variable_primes);
174 let a = if bad_residue == 0 {
177 clause_modulus
178 } else {
179 bad_residue
180 };
181 pairs.push((a, clause_modulus));
182 }
183
184 Reduction3SATToSimultaneousIncongruences {
185 target: SimultaneousIncongruences::new(pairs)
186 .expect("reduction produces valid incongruences"),
187 variable_primes,
188 }
189 }
190}
191
192#[cfg(feature = "example-db")]
193pub(crate) fn canonical_rule_example_specs() -> Vec<crate::example_db::specs::RuleExampleSpec> {
194 use crate::export::SolutionPair;
195
196 vec![crate::example_db::specs::RuleExampleSpec {
197 id: "ksatisfiability_to_simultaneous_incongruences",
198 build: || {
199 let source = KSatisfiability::<K3>::new(
200 2,
201 vec![
202 CNFClause::new(vec![1, 2, 2]),
203 CNFClause::new(vec![-1, 2, 2]),
204 ],
205 );
206 crate::example_db::specs::rule_example_with_witness::<_, SimultaneousIncongruences>(
207 source,
208 SolutionPair {
209 source_config: vec![1, 1],
210 target_config: vec![1],
211 },
212 )
213 },
214 }]
215}
216
217#[cfg(test)]
218#[path = "../unit_tests/rules/ksatisfiability_simultaneousincongruences.rs"]
219mod tests;