1use crate::models::specialized::{Assignment, BooleanExpr, Circuit, CircuitSAT, Factoring};
11use crate::rules::traits::{ReduceTo, ReductionResult};
12use crate::traits::Problem;
13use crate::types::ProblemSize;
14
15#[derive(Debug, Clone)]
23pub struct ReductionFactoringToCircuit {
24 target: CircuitSAT<i32>,
26 p_vars: Vec<String>,
28 q_vars: Vec<String>,
30 m_vars: Vec<String>,
32 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 fn extract_solution(&self, target_solution: &[usize]) -> Vec<usize> {
49 let var_names = self.target.variable_names();
50
51 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 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 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 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 pub fn p_vars(&self) -> &[String] {
90 &self.p_vars
91 }
92
93 pub fn q_vars(&self) -> &[String] {
95 &self.q_vars
96 }
97
98 pub fn m_vars(&self) -> &[String] {
100 &self.m_vars
101 }
102}
103
104fn 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
113fn 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 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 let assign_a = Assignment::new(vec![a_name.clone()], BooleanExpr::and(vec![p, q]));
141
142 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 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 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 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 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(); let n2 = self.n(); let target = self.target();
196
197 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 let mut assignments = Vec::new();
203 let mut m_vars = Vec::new();
204
205 let mut s_pre: Vec<BooleanExpr> = (0..=n2).map(|_| BooleanExpr::constant(false)).collect();
208
209 for i in 1..=n1 {
211 let mut c_pre = BooleanExpr::constant(false);
213
214 for j in 1..=n2 {
215 let c_name = format!("c{}_{}", i, j);
217 let s_name = format!("s{}_{}", i, j);
218
219 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], &c_pre,
228 &cell_id,
229 );
230
231 assignments.extend(cell_assignments);
232
233 c_pre = BooleanExpr::var(&c_name);
235
236 s_pre[j - 1] = BooleanExpr::var(&s_name);
239 }
240
241 s_pre[n2] = c_pre;
243
244 m_vars.push(format!("s{}_{}", i, 1));
246 }
247
248 for j in 2..=n2 {
250 m_vars.push(format!("s{}_{}", n1, j));
251 }
252 m_vars.push(format!("c{}_{}", n1, n2));
254
255 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 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 assert!(!read_bit(6, 1)); assert!(read_bit(6, 2)); assert!(read_bit(6, 3)); assert!(!read_bit(6, 4)); 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 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); }
309
310 #[test]
311 fn test_reduction_structure_3x3() {
312 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); }
320
321 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 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 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 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 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 for assign in &circuit.assignments {
368 if !assign.is_satisfied(&assignments) {
369 return false;
370 }
371 }
372
373 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 assert!(
384 check_factorization_satisfies(&factoring, &reduction, 2, 3),
385 "2 * 3 = 6 should satisfy the circuit"
386 );
387
388 assert!(
390 check_factorization_satisfies(&factoring, &reduction, 3, 2),
391 "3 * 2 = 6 should satisfy the circuit"
392 );
393
394 assert!(
396 !check_factorization_satisfies(&factoring, &reduction, 1, 1),
397 "1 * 1 != 6 should not satisfy the circuit"
398 );
399
400 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 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 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 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 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 let var_names = circuit_sat.variable_names();
482 let mut sol = vec![0usize; var_names.len()];
483
484 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 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 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 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 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 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 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
576use 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}