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::specialized::{Assignment, BooleanExpr, Circuit, CircuitSAT, Factoring};
11use crate::rules::traits::{ReduceTo, ReductionResult};
12use crate::traits::Problem;
13use crate::types::ProblemSize;
14
15/// Result of reducing Factoring to CircuitSAT.
16///
17/// This struct contains:
18/// - The target CircuitSAT problem (the multiplier circuit)
19/// - Variable indices for the first factor p (m bits)
20/// - Variable indices for the second factor q (n bits)
21/// - Variable indices for the product m (m+n bits)
22#[derive(Debug, Clone)]
23pub struct ReductionFactoringToCircuit {
24    /// The target CircuitSAT problem.
25    target: CircuitSAT<i32>,
26    /// Variable names for the first factor p (bit positions).
27    p_vars: Vec<String>,
28    /// Variable names for the second factor q (bit positions).
29    q_vars: Vec<String>,
30    /// Variable names for the product (bit positions).
31    m_vars: Vec<String>,
32    /// Size of the source problem.
33    source_size: ProblemSize,
34}
35
36impl ReductionResult for ReductionFactoringToCircuit {
37    type Source = Factoring;
38    type Target = CircuitSAT<i32>;
39
40    fn target_problem(&self) -> &Self::Target {
41        &self.target
42    }
43
44    /// Extract a Factoring solution from a CircuitSAT solution.
45    ///
46    /// Returns a configuration where the first m bits are the first factor p,
47    /// and the next n bits are the second factor q.
48    fn extract_solution(&self, target_solution: &[usize]) -> Vec<usize> {
49        let var_names = self.target.variable_names();
50
51        // Build a map from variable name to its value
52        let var_map: std::collections::HashMap<&str, usize> = var_names
53            .iter()
54            .enumerate()
55            .map(|(i, name)| (name.as_str(), target_solution.get(i).copied().unwrap_or(0)))
56            .collect();
57
58        // Extract p bits
59        let p_bits: Vec<usize> = self
60            .p_vars
61            .iter()
62            .map(|name| *var_map.get(name.as_str()).unwrap_or(&0))
63            .collect();
64
65        // Extract q bits
66        let q_bits: Vec<usize> = self
67            .q_vars
68            .iter()
69            .map(|name| *var_map.get(name.as_str()).unwrap_or(&0))
70            .collect();
71
72        // Concatenate p and q bits
73        let mut result = p_bits;
74        result.extend(q_bits);
75        result
76    }
77
78    fn source_size(&self) -> ProblemSize {
79        self.source_size.clone()
80    }
81
82    fn target_size(&self) -> ProblemSize {
83        self.target.problem_size()
84    }
85}
86
87impl ReductionFactoringToCircuit {
88    /// Get the variable names for the first factor.
89    pub fn p_vars(&self) -> &[String] {
90        &self.p_vars
91    }
92
93    /// Get the variable names for the second factor.
94    pub fn q_vars(&self) -> &[String] {
95        &self.q_vars
96    }
97
98    /// Get the variable names for the product.
99    pub fn m_vars(&self) -> &[String] {
100        &self.m_vars
101    }
102}
103
104/// Read the i-th bit (1-indexed) of a number (little-endian).
105fn read_bit(n: u64, i: usize) -> bool {
106    if i == 0 || i > 64 {
107        false
108    } else {
109        ((n >> (i - 1)) & 1) == 1
110    }
111}
112
113/// Build a single multiplier cell that computes:
114/// s + 2*c = p*q + s_pre + c_pre
115///
116/// This is a full adder that adds three bits: (p AND q), s_pre, and c_pre.
117/// Returns the assignments needed and the list of ancilla variable names.
118fn build_multiplier_cell(
119    s_name: &str,
120    c_name: &str,
121    p_name: &str,
122    q_name: &str,
123    s_pre: &BooleanExpr,
124    c_pre: &BooleanExpr,
125    cell_id: &str,
126) -> (Vec<Assignment>, Vec<String>) {
127    // Create unique ancilla variable names
128    let a_name = format!("a_{}", cell_id);
129    let a_xor_s_name = format!("axs_{}", cell_id);
130    let a_xor_s_and_c_name = format!("axsc_{}", cell_id);
131    let a_and_s_name = format!("as_{}", cell_id);
132
133    let p = BooleanExpr::var(p_name);
134    let q = BooleanExpr::var(q_name);
135    let a = BooleanExpr::var(&a_name);
136    let a_xor_s = BooleanExpr::var(&a_xor_s_name);
137
138    // Build the assignments:
139    // a = p & q (AND of the two factor bits)
140    let assign_a = Assignment::new(vec![a_name.clone()], BooleanExpr::and(vec![p, q]));
141
142    // a_xor_s = a XOR s_pre
143    let assign_a_xor_s = Assignment::new(
144        vec![a_xor_s_name.clone()],
145        BooleanExpr::xor(vec![a.clone(), s_pre.clone()]),
146    );
147
148    // s = a_xor_s XOR c_pre (sum output)
149    let assign_s = Assignment::new(
150        vec![s_name.to_string()],
151        BooleanExpr::xor(vec![a_xor_s.clone(), c_pre.clone()]),
152    );
153
154    // a_xor_s_and_c = a_xor_s & c_pre
155    let assign_a_xor_s_and_c = Assignment::new(
156        vec![a_xor_s_and_c_name.clone()],
157        BooleanExpr::and(vec![a_xor_s, c_pre.clone()]),
158    );
159
160    // a_and_s = a & s_pre
161    let assign_a_and_s = Assignment::new(
162        vec![a_and_s_name.clone()],
163        BooleanExpr::and(vec![a, s_pre.clone()]),
164    );
165
166    // c = a_xor_s_and_c | a_and_s (carry output)
167    let assign_c = Assignment::new(
168        vec![c_name.to_string()],
169        BooleanExpr::or(vec![
170            BooleanExpr::var(&a_xor_s_and_c_name),
171            BooleanExpr::var(&a_and_s_name),
172        ]),
173    );
174
175    let assignments = vec![
176        assign_a,
177        assign_a_xor_s,
178        assign_s,
179        assign_a_xor_s_and_c,
180        assign_a_and_s,
181        assign_c,
182    ];
183
184    let ancillas = vec![a_name, a_xor_s_name, a_xor_s_and_c_name, a_and_s_name];
185
186    (assignments, ancillas)
187}
188
189impl ReduceTo<CircuitSAT<i32>> for Factoring {
190    type Result = ReductionFactoringToCircuit;
191
192    fn reduce_to(&self) -> Self::Result {
193        let n1 = self.m(); // bits for first factor
194        let n2 = self.n(); // bits for second factor
195        let target = self.target();
196
197        // Create input variables for the two factors
198        let p_vars: Vec<String> = (1..=n1).map(|i| format!("p{}", i)).collect();
199        let q_vars: Vec<String> = (1..=n2).map(|i| format!("q{}", i)).collect();
200
201        // Accumulate assignments and product bits
202        let mut assignments = Vec::new();
203        let mut m_vars = Vec::new();
204
205        // Initialize s_pre (previous sum signals) with false constants
206        // s_pre has n2+1 elements to handle the carry propagation
207        let mut s_pre: Vec<BooleanExpr> = (0..=n2).map(|_| BooleanExpr::constant(false)).collect();
208
209        // Build the array multiplier row by row
210        for i in 1..=n1 {
211            // c_pre is the carry from the previous cell in this row
212            let mut c_pre = BooleanExpr::constant(false);
213
214            for j in 1..=n2 {
215                // Create signal names for this cell
216                let c_name = format!("c{}_{}", i, j);
217                let s_name = format!("s{}_{}", i, j);
218
219                // Build the multiplier cell
220                let cell_id = format!("{}_{}", i, j);
221                let (cell_assignments, _ancillas) = build_multiplier_cell(
222                    &s_name,
223                    &c_name,
224                    &p_vars[i - 1],
225                    &q_vars[j - 1],
226                    &s_pre[j], // s_pre[j+1] in 0-indexed Julia becomes s_pre[j] in 1-indexed
227                    &c_pre,
228                    &cell_id,
229                );
230
231                assignments.extend(cell_assignments);
232
233                // Update c_pre for the next cell
234                c_pre = BooleanExpr::var(&c_name);
235
236                // Update s_pre for the next row
237                // s_pre[j-1] (0-indexed) = s (the sum from this cell)
238                s_pre[j - 1] = BooleanExpr::var(&s_name);
239            }
240
241            // The final carry becomes the last element of s_pre
242            s_pre[n2] = c_pre;
243
244            // The first element of s_pre is the i-th bit of the product
245            m_vars.push(format!("s{}_{}", i, 1));
246        }
247
248        // The remaining bits of the product come from s_pre[1..=n2]
249        for j in 2..=n2 {
250            m_vars.push(format!("s{}_{}", n1, j));
251        }
252        // The final carry is the last bit
253        m_vars.push(format!("c{}_{}", n1, n2));
254
255        // Constrain the output bits to match the target number
256        for (i, m_var) in m_vars.iter().enumerate() {
257            let target_bit = read_bit(target, i + 1);
258            assignments.push(Assignment::new(
259                vec![m_var.clone()],
260                BooleanExpr::constant(target_bit),
261            ));
262        }
263
264        // Build the circuit
265        let circuit = Circuit::new(assignments);
266        let circuit_sat = CircuitSAT::new(circuit);
267
268        ReductionFactoringToCircuit {
269            target: circuit_sat,
270            p_vars,
271            q_vars,
272            m_vars,
273            source_size: self.problem_size(),
274        }
275    }
276}
277
278#[cfg(test)]
279mod tests {
280    use super::*;
281    use std::collections::HashMap;
282
283    #[test]
284    fn test_read_bit() {
285        // 6 = 110 in binary (little-endian: bit1=0, bit2=1, bit3=1)
286        assert!(!read_bit(6, 1)); // bit 1 (LSB) = 0
287        assert!(read_bit(6, 2)); // bit 2 = 1
288        assert!(read_bit(6, 3)); // bit 3 = 1
289        assert!(!read_bit(6, 4)); // bit 4 = 0
290
291        // 15 = 1111 in binary
292        assert!(read_bit(15, 1));
293        assert!(read_bit(15, 2));
294        assert!(read_bit(15, 3));
295        assert!(read_bit(15, 4));
296        assert!(!read_bit(15, 5));
297    }
298
299    #[test]
300    fn test_reduction_structure() {
301        // Factor 6 = 2 * 3 with 2-bit factors
302        let factoring = Factoring::new(2, 2, 6);
303        let reduction = ReduceTo::<CircuitSAT<i32>>::reduce_to(&factoring);
304
305        assert_eq!(reduction.p_vars().len(), 2);
306        assert_eq!(reduction.q_vars().len(), 2);
307        assert_eq!(reduction.m_vars().len(), 4); // 2 + 2 = 4 bits for product
308    }
309
310    #[test]
311    fn test_reduction_structure_3x3() {
312        // Factor 15 = 3 * 5 with 3-bit factors
313        let factoring = Factoring::new(3, 3, 15);
314        let reduction = ReduceTo::<CircuitSAT<i32>>::reduce_to(&factoring);
315
316        assert_eq!(reduction.p_vars().len(), 3);
317        assert_eq!(reduction.q_vars().len(), 3);
318        assert_eq!(reduction.m_vars().len(), 6); // 3 + 3 = 6 bits for product
319    }
320
321    /// Helper function to evaluate a circuit with given inputs.
322    /// Returns a HashMap of all variable assignments after propagation.
323    fn evaluate_multiplier_circuit(
324        reduction: &ReductionFactoringToCircuit,
325        p_val: u64,
326        q_val: u64,
327    ) -> HashMap<String, bool> {
328        let circuit = reduction.target_problem().circuit();
329        let mut assignments: HashMap<String, bool> = HashMap::new();
330
331        // Set input variables for p
332        for (i, var_name) in reduction.p_vars().iter().enumerate() {
333            let bit = ((p_val >> i) & 1) == 1;
334            assignments.insert(var_name.clone(), bit);
335        }
336
337        // Set input variables for q
338        for (i, var_name) in reduction.q_vars().iter().enumerate() {
339            let bit = ((q_val >> i) & 1) == 1;
340            assignments.insert(var_name.clone(), bit);
341        }
342
343        // Evaluate the circuit assignments in order
344        for assign in &circuit.assignments {
345            let result = assign.expr.evaluate(&assignments);
346            for out in &assign.outputs {
347                assignments.insert(out.clone(), result);
348            }
349        }
350
351        assignments
352    }
353
354    /// Check if inputs satisfying the circuit give correct factorization.
355    /// This tests the core functionality: given p and q, does the circuit
356    /// correctly identify when p * q = target?
357    fn check_factorization_satisfies(
358        factoring: &Factoring,
359        reduction: &ReductionFactoringToCircuit,
360        p_val: u64,
361        q_val: u64,
362    ) -> bool {
363        let assignments = evaluate_multiplier_circuit(reduction, p_val, q_val);
364        let circuit = reduction.target_problem().circuit();
365
366        // Check if all assignments are satisfied
367        for assign in &circuit.assignments {
368            if !assign.is_satisfied(&assignments) {
369                return false;
370            }
371        }
372
373        // Also verify the product equals target (redundant but explicit)
374        p_val * q_val == factoring.target()
375    }
376
377    #[test]
378    fn test_factorization_6_satisfies_circuit() {
379        let factoring = Factoring::new(2, 2, 6);
380        let reduction = ReduceTo::<CircuitSAT<i32>>::reduce_to(&factoring);
381
382        // 2 * 3 = 6 should satisfy the circuit
383        assert!(
384            check_factorization_satisfies(&factoring, &reduction, 2, 3),
385            "2 * 3 = 6 should satisfy the circuit"
386        );
387
388        // 3 * 2 = 6 should also satisfy
389        assert!(
390            check_factorization_satisfies(&factoring, &reduction, 3, 2),
391            "3 * 2 = 6 should satisfy the circuit"
392        );
393
394        // 1 * 1 = 1 != 6 should NOT satisfy (product constraint fails)
395        assert!(
396            !check_factorization_satisfies(&factoring, &reduction, 1, 1),
397            "1 * 1 != 6 should not satisfy the circuit"
398        );
399
400        // 2 * 2 = 4 != 6 should NOT satisfy
401        assert!(
402            !check_factorization_satisfies(&factoring, &reduction, 2, 2),
403            "2 * 2 != 6 should not satisfy the circuit"
404        );
405    }
406
407    #[test]
408    fn test_factorization_15_satisfies_circuit() {
409        let factoring = Factoring::new(4, 4, 15);
410        let reduction = ReduceTo::<CircuitSAT<i32>>::reduce_to(&factoring);
411
412        // Valid factorizations of 15
413        assert!(
414            check_factorization_satisfies(&factoring, &reduction, 3, 5),
415            "3 * 5 = 15 should satisfy"
416        );
417        assert!(
418            check_factorization_satisfies(&factoring, &reduction, 5, 3),
419            "5 * 3 = 15 should satisfy"
420        );
421        assert!(
422            check_factorization_satisfies(&factoring, &reduction, 1, 15),
423            "1 * 15 = 15 should satisfy"
424        );
425        assert!(
426            check_factorization_satisfies(&factoring, &reduction, 15, 1),
427            "15 * 1 = 15 should satisfy"
428        );
429
430        // Invalid: 2 * 7 = 14 != 15
431        assert!(
432            !check_factorization_satisfies(&factoring, &reduction, 2, 7),
433            "2 * 7 != 15 should not satisfy"
434        );
435    }
436
437    #[test]
438    fn test_factorization_21_satisfies_circuit() {
439        let factoring = Factoring::new(3, 3, 21);
440        let reduction = ReduceTo::<CircuitSAT<i32>>::reduce_to(&factoring);
441
442        // 3 * 7 = 21
443        assert!(
444            check_factorization_satisfies(&factoring, &reduction, 3, 7),
445            "3 * 7 = 21 should satisfy"
446        );
447        assert!(
448            check_factorization_satisfies(&factoring, &reduction, 7, 3),
449            "7 * 3 = 21 should satisfy"
450        );
451
452        // Invalid: 3 * 5 = 15 != 21
453        assert!(
454            !check_factorization_satisfies(&factoring, &reduction, 3, 5),
455            "3 * 5 != 21 should not satisfy"
456        );
457    }
458
459    #[test]
460    fn test_source_and_target_size() {
461        let factoring = Factoring::new(3, 4, 15);
462        let reduction = ReduceTo::<CircuitSAT<i32>>::reduce_to(&factoring);
463
464        let source_size = reduction.source_size();
465        let target_size = reduction.target_size();
466
467        assert_eq!(source_size.get("num_bits_first"), Some(3));
468        assert_eq!(source_size.get("num_bits_second"), Some(4));
469        assert!(target_size.get("num_variables").unwrap() > 0);
470        assert!(target_size.get("num_assignments").unwrap() > 0);
471    }
472
473    #[test]
474    fn test_extract_solution() {
475        let factoring = Factoring::new(2, 2, 6);
476        let reduction = ReduceTo::<CircuitSAT<i32>>::reduce_to(&factoring);
477        let circuit_sat = reduction.target_problem();
478
479        // Create a solution where p=2 (binary: 01) and q=3 (binary: 11)
480        // We need to find the indices of p1, p2, q1, q2 in the variable list
481        let var_names = circuit_sat.variable_names();
482        let mut sol = vec![0usize; var_names.len()];
483
484        // Now evaluate the circuit to set all internal variables correctly
485        let assignments = evaluate_multiplier_circuit(&reduction, 2, 3);
486        for (i, name) in var_names.iter().enumerate() {
487            if let Some(&val) = assignments.get(name) {
488                sol[i] = if val { 1 } else { 0 };
489            }
490        }
491
492        let factoring_sol = reduction.extract_solution(&sol);
493        assert_eq!(factoring_sol.len(), 4, "Should have 4 bits (2 for p, 2 for q)");
494
495        let (p, q) = factoring.read_factors(&factoring_sol);
496        assert_eq!(p, 2, "p should be 2");
497        assert_eq!(q, 3, "q should be 3");
498        assert_eq!(p * q, 6, "Product should equal target");
499    }
500
501    #[test]
502    fn test_prime_7_only_trivial_factorizations() {
503        let factoring = Factoring::new(3, 3, 7);
504        let reduction = ReduceTo::<CircuitSAT<i32>>::reduce_to(&factoring);
505
506        // Check that only trivial factorizations satisfy
507        for p in 0..8u64 {
508            for q in 0..8u64 {
509                let satisfies = check_factorization_satisfies(&factoring, &reduction, p, q);
510                let is_valid_factorization = p * q == 7;
511
512                if is_valid_factorization {
513                    assert!(
514                        satisfies,
515                        "{}*{}=7 should satisfy the circuit",
516                        p,
517                        q
518                    );
519                    // Check it's a trivial factorization (1*7 or 7*1)
520                    assert!(
521                        (p == 1 && q == 7) || (p == 7 && q == 1),
522                        "7 is prime, so only 1*7 or 7*1 should work"
523                    );
524                } else if p > 0 && q > 0 {
525                    // Non-zero products that don't equal 7 should not satisfy
526                    assert!(
527                        !satisfies,
528                        "{}*{}={} != 7 should not satisfy the circuit",
529                        p,
530                        q,
531                        p * q
532                    );
533                }
534            }
535        }
536    }
537
538    #[test]
539    fn test_all_2bit_factorizations() {
540        // Test all possible 2-bit * 2-bit multiplications for target 6
541        let factoring = Factoring::new(2, 2, 6);
542        let reduction = ReduceTo::<CircuitSAT<i32>>::reduce_to(&factoring);
543
544        let mut valid_factorizations = Vec::new();
545        for p in 0..4u64 {
546            for q in 0..4u64 {
547                if check_factorization_satisfies(&factoring, &reduction, p, q) {
548                    valid_factorizations.push((p, q));
549                }
550            }
551        }
552
553        // Only 2*3 and 3*2 should satisfy (both give 6)
554        assert_eq!(valid_factorizations.len(), 2, "Should find exactly 2 factorizations of 6");
555        assert!(valid_factorizations.contains(&(2, 3)), "Should find 2*3");
556        assert!(valid_factorizations.contains(&(3, 2)), "Should find 3*2");
557    }
558
559    #[test]
560    fn test_factorization_1_trivial() {
561        // Factor 1 = 1 * 1
562        let factoring = Factoring::new(2, 2, 1);
563        let reduction = ReduceTo::<CircuitSAT<i32>>::reduce_to(&factoring);
564
565        assert!(
566            check_factorization_satisfies(&factoring, &reduction, 1, 1),
567            "1 * 1 = 1 should satisfy"
568        );
569        assert!(
570            !check_factorization_satisfies(&factoring, &reduction, 2, 1),
571            "2 * 1 = 2 != 1 should not satisfy"
572        );
573    }
574}
575
576// Register reduction with inventory for auto-discovery
577use crate::poly;
578use crate::rules::registry::{ReductionEntry, ReductionOverhead};
579
580inventory::submit! {
581    ReductionEntry {
582        source_name: "Factoring",
583        target_name: "CircuitSAT",
584        source_graph: "Factoring",
585        target_graph: "Circuit",
586        overhead_fn: || ReductionOverhead::new(vec![
587            ("num_gates", poly!(num_bits_first^2)),
588        ]),
589    }
590}