Skip to main content

problemreductions/rules/
factoring_circuit.rs

1//! Reduction from Factoring to CircuitSAT.
2//!
3//! The reduction constructs a multiplier circuit that computes p × q
4//! and constrains the output to equal the target number N.
5//! A satisfying assignment to the circuit gives the factorization.
6//!
7//! The multiplier circuit uses an array multiplier structure with
8//! carry propagation, building up partial products row by row.
9
10use crate::models::formula::{Assignment, BooleanExpr, Circuit, CircuitSAT};
11use crate::models::misc::Factoring;
12use crate::reduction;
13use crate::rules::traits::{ReduceTo, ReductionResult};
14use num_bigint::BigUint;
15use num_traits::{One, Zero};
16/// Result of reducing Factoring to CircuitSAT.
17///
18/// This struct contains:
19/// - The target CircuitSAT problem (the multiplier circuit)
20/// - Variable indices for the first factor p (m bits)
21/// - Variable indices for the second factor q (n bits)
22/// - Variable indices for the product m (m+n bits)
23#[derive(Debug, Clone)]
24pub struct ReductionFactoringToCircuit {
25    /// The target CircuitSAT problem.
26    target: CircuitSAT,
27    /// Variable names for the first factor p (bit positions).
28    p_vars: Vec<String>,
29    /// Variable names for the second factor q (bit positions).
30    q_vars: Vec<String>,
31    /// Variable names for the product (bit positions).
32    m_vars: Vec<String>,
33}
34
35impl ReductionResult for ReductionFactoringToCircuit {
36    type Source = Factoring;
37    type Target = CircuitSAT;
38
39    fn target_problem(&self) -> &Self::Target {
40        &self.target
41    }
42
43    /// Extract a Factoring solution from a CircuitSAT solution.
44    ///
45    /// Returns the decoded factors in ascending order.
46    fn extract_solution(
47        &self,
48        target_solution: &<Self::Target as crate::traits::Problem>::Solution,
49    ) -> crate::rules::ExtractionResult<<Self::Source as crate::traits::Problem>::Solution> {
50        let value =
51            crate::rules::traits::validate_target_solution(self.target_problem(), target_solution)?;
52        if !value.0 {
53            return Err(crate::rules::ExtractionError::invalid(
54                "target assignment does not satisfy the multiplication circuit",
55            ));
56        }
57
58        Ok({
59            let var_names = self.target.variable_names();
60
61            // Build a map from variable name to its value
62            let var_map: std::collections::HashMap<&str, bool> = var_names
63                .iter()
64                .enumerate()
65                .map(|(i, name)| (name.as_str(), target_solution[i]))
66                .collect();
67
68            let decode = |names: &[String]| {
69                names
70                    .iter()
71                    .enumerate()
72                    .try_fold(BigUint::zero(), |value, (index, name)| {
73                        let bit = var_map.get(name.as_str()).copied().ok_or_else(|| {
74                            crate::rules::ExtractionError::invalid(format!(
75                                "target circuit does not contain factor variable {name}"
76                            ))
77                        })?;
78                        Ok::<BigUint, crate::rules::ExtractionError>(if bit {
79                            value + (BigUint::one() << index)
80                        } else {
81                            value
82                        })
83                    })
84            };
85            let left = decode(&self.p_vars)?;
86            let right = decode(&self.q_vars)?;
87            if left <= right {
88                (left, right)
89            } else {
90                (right, left)
91            }
92        })
93    }
94}
95
96impl ReductionFactoringToCircuit {
97    /// Product width and assignment capacity, checked before circuit allocation.
98    fn dimensions(m: usize, n: usize) -> Result<(usize, usize), crate::rules::ReductionError> {
99        let overflow = || {
100            crate::rules::ReductionError::integer_overflow::<Factoring, CircuitSAT>(
101                "computing multiplication circuit dimensions",
102            )
103        };
104        let width = m.checked_add(n).ok_or_else(overflow)?;
105        let capacity = m
106            .checked_mul(n)
107            .and_then(|v| v.checked_mul(6))
108            .and_then(|v| width.checked_mul(2).and_then(|w| v.checked_add(w)))
109            .and_then(|v| v.checked_add(2))
110            .ok_or_else(overflow)?;
111        Ok((width, capacity))
112    }
113
114    /// Get the variable names for the first factor.
115    pub fn p_vars(&self) -> &[String] {
116        &self.p_vars
117    }
118
119    /// Get the variable names for the second factor.
120    pub fn q_vars(&self) -> &[String] {
121        &self.q_vars
122    }
123
124    /// Get the variable names for the product.
125    pub fn m_vars(&self) -> &[String] {
126        &self.m_vars
127    }
128}
129
130/// Read the i-th bit (1-indexed) of a number (little-endian).
131fn read_bit(n: &BigUint, i: usize) -> bool {
132    if i == 0 {
133        false
134    } else {
135        n.bit(u64::try_from(i - 1).expect("bit index fits u64"))
136    }
137}
138
139/// Build a single multiplier cell that computes:
140/// s + 2*c = p*q + s_pre + c_pre
141///
142/// This is a full adder that adds three bits: (p AND q), s_pre, and c_pre.
143/// Returns the assignments needed and the list of ancilla variable names.
144fn build_multiplier_cell(
145    s_name: &str,
146    c_name: &str,
147    p_name: &str,
148    q_name: &str,
149    s_pre: &BooleanExpr,
150    c_pre: &BooleanExpr,
151    cell_id: &str,
152) -> (Vec<Assignment>, Vec<String>) {
153    // Create unique ancilla variable names
154    let a_name = format!("a_{}", cell_id);
155    let a_xor_s_name = format!("axs_{}", cell_id);
156    let a_xor_s_and_c_name = format!("axsc_{}", cell_id);
157    let a_and_s_name = format!("as_{}", cell_id);
158
159    let p = BooleanExpr::var(p_name);
160    let q = BooleanExpr::var(q_name);
161    let a = BooleanExpr::var(&a_name);
162    let a_xor_s = BooleanExpr::var(&a_xor_s_name);
163
164    // Build the assignments:
165    // a = p & q (AND of the two factor bits)
166    let assign_a = Assignment::new(vec![a_name.clone()], BooleanExpr::and(vec![p, q]));
167
168    // a_xor_s = a XOR s_pre
169    let assign_a_xor_s = Assignment::new(
170        vec![a_xor_s_name.clone()],
171        BooleanExpr::xor(vec![a.clone(), s_pre.clone()]),
172    );
173
174    // s = a_xor_s XOR c_pre (sum output)
175    let assign_s = Assignment::new(
176        vec![s_name.to_string()],
177        BooleanExpr::xor(vec![a_xor_s.clone(), c_pre.clone()]),
178    );
179
180    // a_xor_s_and_c = a_xor_s & c_pre
181    let assign_a_xor_s_and_c = Assignment::new(
182        vec![a_xor_s_and_c_name.clone()],
183        BooleanExpr::and(vec![a_xor_s, c_pre.clone()]),
184    );
185
186    // a_and_s = a & s_pre
187    let assign_a_and_s = Assignment::new(
188        vec![a_and_s_name.clone()],
189        BooleanExpr::and(vec![a, s_pre.clone()]),
190    );
191
192    // c = a_xor_s_and_c | a_and_s (carry output)
193    let assign_c = Assignment::new(
194        vec![c_name.to_string()],
195        BooleanExpr::or(vec![
196            BooleanExpr::var(&a_xor_s_and_c_name),
197            BooleanExpr::var(&a_and_s_name),
198        ]),
199    );
200
201    let assignments = vec![
202        assign_a,
203        assign_a_xor_s,
204        assign_s,
205        assign_a_xor_s_and_c,
206        assign_a_and_s,
207        assign_c,
208    ];
209
210    let ancillas = vec![a_name, a_xor_s_name, a_xor_s_and_c_name, a_and_s_name];
211
212    (assignments, ancillas)
213}
214
215#[reduction(
216    transform = upper_bound {
217        num_variables = "6 * num_bits_first * num_bits_second + 2 * (num_bits_first + num_bits_second) + 1",
218        num_assignments = "6 * num_bits_first * num_bits_second + 2 * (num_bits_first + num_bits_second) + 2",
219    },
220    unavailable = {
221        num_assignment_outputs = "the exact target parameter is not represented by this reduction's symbolic transform",
222        num_expression_nodes = "the exact target parameter is not represented by this reduction's symbolic transform",
223    }
224)]
225impl ReduceTo<CircuitSAT> for Factoring {
226    type Result = ReductionFactoringToCircuit;
227
228    fn reduce_to(&self) -> Result<Self::Result, crate::rules::ReductionError> {
229        let n1 = self.m(); // bits for first factor
230        let n2 = self.n(); // bits for second factor
231        let target = self.target();
232        let (width, capacity) = ReductionFactoringToCircuit::dimensions(n1, n2)?;
233
234        // Create input variables for the two factors
235        let p_vars: Vec<String> = (1..=n1).map(|i| format!("p{}", i)).collect();
236        let q_vars: Vec<String> = (1..=n2).map(|i| format!("q{}", i)).collect();
237
238        // Accumulate assignments and product bits
239        let mut assignments = Vec::with_capacity(capacity);
240        let mut product_bits = Vec::with_capacity(width);
241
242        // Initialize s_pre (previous sum signals) with false constants
243        // s_pre has n2+1 elements to handle the carry propagation
244        let mut s_pre = Vec::with_capacity(n2 + 1);
245        s_pre.push(BooleanExpr::constant(false));
246        // The zero accumulator is expressed as Q AND zero. This also keeps
247        // every Q input in the circuit when no multiplier rows are present.
248        s_pre.extend(q_vars.iter().map(|name| {
249            BooleanExpr::and(vec![BooleanExpr::var(name), BooleanExpr::constant(false)])
250        }));
251
252        // Build the array multiplier row by row
253        for i in 1..=n1 {
254            // c_pre is the carry from the previous cell in this row
255            let mut c_pre = BooleanExpr::constant(false);
256
257            for j in 1..=n2 {
258                // Create signal names for this cell
259                let c_name = format!("c{}_{}", i, j);
260                let s_name = format!("s{}_{}", i, j);
261
262                // Build the multiplier cell
263                let cell_id = format!("{}_{}", i, j);
264                let (cell_assignments, _ancillas) = build_multiplier_cell(
265                    &s_name,
266                    &c_name,
267                    &p_vars[i - 1],
268                    &q_vars[j - 1],
269                    &s_pre[j], // s_pre[j+1] in 0-indexed Julia becomes s_pre[j] in 1-indexed
270                    &c_pre,
271                    &cell_id,
272                );
273
274                assignments.extend(cell_assignments);
275
276                // Update c_pre for the next cell
277                c_pre = BooleanExpr::var(&c_name);
278
279                // Update s_pre for the next row
280                // s_pre[j-1] (0-indexed) = s (the sum from this cell)
281                s_pre[j - 1] = BooleanExpr::var(&s_name);
282            }
283
284            // The final carry becomes the last element of s_pre
285            s_pre[n2] = c_pre;
286
287            // The first element of s_pre is the i-th bit of the product
288            product_bits.push(s_pre[0].clone());
289        }
290
291        // After all rows, the residual accumulator supplies the remaining
292        // high bits. With zero rows these are the actual zero expressions,
293        // not names of multiplier cells that were never constructed.
294        product_bits.extend(s_pre.into_iter().skip(1));
295        let m_vars: Vec<_> = (0..width).map(|i| format!("product_{i}")).collect();
296        for (name, expr) in m_vars.iter().zip(product_bits) {
297            assignments.push(Assignment::new(vec![name.clone()], expr));
298        }
299
300        // Constrain the output bits to match the target number
301        for (i, m_var) in m_vars.iter().enumerate() {
302            let target_bit = read_bit(target, i + 1);
303            assignments.push(Assignment::new(
304                vec![m_var.clone()],
305                BooleanExpr::constant(target_bit),
306            ));
307        }
308
309        // An m-bit by n-bit product cannot contain a set bit above position m+n-1.
310        // Encode an explicit contradiction instead of truncating an oversized target.
311        if target.bits() > u64::try_from(m_vars.len()).expect("product width fits u64") {
312            let overflow = "target_overflow".to_string();
313            assignments.push(Assignment::new(
314                vec![overflow.clone()],
315                BooleanExpr::constant(false),
316            ));
317            assignments.push(Assignment::new(vec![overflow], BooleanExpr::constant(true)));
318        }
319
320        // Build the circuit
321        let circuit = Circuit::new(assignments);
322        let circuit_sat = CircuitSAT::new(circuit);
323
324        Ok(ReductionFactoringToCircuit {
325            target: circuit_sat,
326            p_vars,
327            q_vars,
328            m_vars,
329        })
330    }
331}
332
333#[cfg(feature = "example-db")]
334pub(crate) fn canonical_rule_example_specs() -> Vec<crate::example_db::specs::RuleExampleSpec> {
335    use crate::export::SolutionPair;
336
337    vec![crate::example_db::specs::RuleExampleSpec {
338        id: "factoring_to_circuitsat",
339        build: || {
340            crate::example_db::specs::rule_example_with_witness::<_, CircuitSAT>(
341                Factoring::with_factor_bits(35, 3, 3),
342                SolutionPair {
343                    source_config: serde_json::to_value((BigUint::from(5u32), BigUint::from(7u32)))
344                        .expect("solution serialization must succeed"),
345                    target_config: serde_json::json!(vec![
346                        true, true, true, false, false, false, true, true, true, false, false,
347                        false, false, false, false, true, false, false, true, true, true, true,
348                        true, false, false, true, true, false, false, false, false, false, false,
349                        false, true, true, false, false, false, false, false, false, true, true,
350                        true, true, false, true, true, true, false, false, false, true, true, true,
351                        true, true, true, true, true, true, false, false, false, false
352                    ]),
353                },
354            )
355        },
356    }]
357}
358
359#[cfg(test)]
360#[path = "../unit_tests/rules/factoring_circuit.rs"]
361mod tests;