1use crate::models::optimization::SpinGlass;
10use crate::models::specialized::{Assignment, BooleanExpr, BooleanOp, CircuitSAT};
11use crate::rules::traits::{ReduceTo, ReductionResult};
12use crate::traits::Problem;
13use crate::types::ProblemSize;
14use num_traits::{Num, Zero};
15use std::collections::HashMap;
16use std::ops::AddAssign;
17
18#[derive(Debug, Clone)]
27pub struct LogicGadget<W> {
28 pub problem: SpinGlass<W>,
30 pub inputs: Vec<usize>,
32 pub outputs: Vec<usize>,
34}
35
36impl<W> LogicGadget<W> {
37 pub fn new(problem: SpinGlass<W>, inputs: Vec<usize>, outputs: Vec<usize>) -> Self {
39 Self {
40 problem,
41 inputs,
42 outputs,
43 }
44 }
45}
46
47impl<W: Clone + Default> LogicGadget<W> {
48 pub fn num_spins(&self) -> usize {
50 self.problem.num_spins()
51 }
52}
53
54pub fn and_gadget<W>() -> LogicGadget<W>
67where
68 W: Clone + Default + From<i32>,
69{
70 let interactions = vec![
71 ((0, 1), W::from(1)),
72 ((0, 2), W::from(-2)),
73 ((1, 2), W::from(-2)),
74 ];
75 let fields = vec![W::from(-1), W::from(-1), W::from(2)];
76 let sg = SpinGlass::new(3, interactions, fields);
77 LogicGadget::new(sg, vec![0, 1], vec![2])
78}
79
80pub fn or_gadget<W>() -> LogicGadget<W>
89where
90 W: Clone + Default + From<i32>,
91{
92 let interactions = vec![
93 ((0, 1), W::from(1)),
94 ((0, 2), W::from(-2)),
95 ((1, 2), W::from(-2)),
96 ];
97 let fields = vec![W::from(1), W::from(1), W::from(-2)];
98 let sg = SpinGlass::new(3, interactions, fields);
99 LogicGadget::new(sg, vec![0, 1], vec![2])
100}
101
102pub fn not_gadget<W>() -> LogicGadget<W>
110where
111 W: Clone + Default + From<i32> + Zero,
112{
113 let interactions = vec![((0, 1), W::from(1))];
114 let fields = vec![W::zero(), W::zero()];
115 let sg = SpinGlass::new(2, interactions, fields);
116 LogicGadget::new(sg, vec![0], vec![1])
117}
118
119pub fn xor_gadget<W>() -> LogicGadget<W>
127where
128 W: Clone + Default + From<i32>,
129{
130 let interactions = vec![
131 ((0, 1), W::from(1)),
132 ((0, 2), W::from(-1)),
133 ((0, 3), W::from(-2)),
134 ((1, 2), W::from(-1)),
135 ((1, 3), W::from(-2)),
136 ((2, 3), W::from(2)),
137 ];
138 let fields = vec![W::from(-1), W::from(-1), W::from(1), W::from(2)];
139 let sg = SpinGlass::new(4, interactions, fields);
140 LogicGadget::new(sg, vec![0, 1], vec![2])
144}
145
146pub fn set0_gadget<W>() -> LogicGadget<W>
151where
152 W: Clone + Default + From<i32>,
153{
154 let interactions = vec![];
155 let fields = vec![W::from(1)];
156 let sg = SpinGlass::new(1, interactions, fields);
157 LogicGadget::new(sg, vec![], vec![0])
158}
159
160pub fn set1_gadget<W>() -> LogicGadget<W>
165where
166 W: Clone + Default + From<i32>,
167{
168 let interactions = vec![];
169 let fields = vec![W::from(-1)];
170 let sg = SpinGlass::new(1, interactions, fields);
171 LogicGadget::new(sg, vec![], vec![0])
172}
173
174#[derive(Debug, Clone)]
176pub struct ReductionCircuitToSG<W> {
177 target: SpinGlass<W>,
179 variable_map: HashMap<String, usize>,
181 source_variables: Vec<String>,
183 source_size: ProblemSize,
185}
186
187impl<W> ReductionResult for ReductionCircuitToSG<W>
188where
189 W: Clone + Default + PartialOrd + Num + Zero + AddAssign + From<i32> + 'static,
190{
191 type Source = CircuitSAT<W>;
192 type Target = SpinGlass<W>;
193
194 fn target_problem(&self) -> &Self::Target {
195 &self.target
196 }
197
198 fn extract_solution(&self, target_solution: &[usize]) -> Vec<usize> {
199 self.source_variables
200 .iter()
201 .map(|var| {
202 self.variable_map
203 .get(var)
204 .and_then(|&idx| target_solution.get(idx).copied())
205 .unwrap_or(0)
206 })
207 .collect()
208 }
209
210 fn source_size(&self) -> ProblemSize {
211 self.source_size.clone()
212 }
213
214 fn target_size(&self) -> ProblemSize {
215 self.target.problem_size()
216 }
217}
218
219struct SpinGlassBuilder<W> {
221 num_spins: usize,
223 interactions: HashMap<(usize, usize), W>,
225 fields: Vec<W>,
227 variable_map: HashMap<String, usize>,
229}
230
231impl<W> SpinGlassBuilder<W>
232where
233 W: Clone + Default + Zero + AddAssign + From<i32>,
234{
235 fn new() -> Self {
236 Self {
237 num_spins: 0,
238 interactions: HashMap::new(),
239 fields: Vec::new(),
240 variable_map: HashMap::new(),
241 }
242 }
243
244 fn allocate_spin(&mut self) -> usize {
246 let idx = self.num_spins;
247 self.num_spins += 1;
248 self.fields.push(W::zero());
249 idx
250 }
251
252 fn get_or_create_variable(&mut self, name: &str) -> usize {
254 if let Some(&idx) = self.variable_map.get(name) {
255 idx
256 } else {
257 let idx = self.allocate_spin();
258 self.variable_map.insert(name.to_string(), idx);
259 idx
260 }
261 }
262
263 fn add_gadget(&mut self, gadget: &LogicGadget<W>, spin_map: &[usize]) {
265 for ((i, j), weight) in gadget.problem.interactions() {
267 let global_i = spin_map[*i];
268 let global_j = spin_map[*j];
269 let key = if global_i < global_j {
270 (global_i, global_j)
271 } else {
272 (global_j, global_i)
273 };
274 self.interactions
275 .entry(key)
276 .or_insert_with(W::zero)
277 .add_assign(weight.clone());
278 }
279
280 for (local_idx, field) in gadget.problem.fields().iter().enumerate() {
282 let global_idx = spin_map[local_idx];
283 self.fields[global_idx].add_assign(field.clone());
284 }
285 }
286
287 fn build(self) -> (SpinGlass<W>, HashMap<String, usize>) {
289 let interactions: Vec<((usize, usize), W)> =
290 self.interactions.into_iter().collect();
291 let sg = SpinGlass::new(self.num_spins, interactions, self.fields);
292 (sg, self.variable_map)
293 }
294}
295
296fn process_expression<W>(
298 expr: &BooleanExpr,
299 builder: &mut SpinGlassBuilder<W>,
300) -> usize
301where
302 W: Clone + Default + Zero + AddAssign + From<i32>,
303{
304 match &expr.op {
305 BooleanOp::Var(name) => builder.get_or_create_variable(name),
306
307 BooleanOp::Const(value) => {
308 let gadget: LogicGadget<W> = if *value {
309 set1_gadget()
310 } else {
311 set0_gadget()
312 };
313 let output_spin = builder.allocate_spin();
314 let spin_map = vec![output_spin];
315 builder.add_gadget(&gadget, &spin_map);
316 output_spin
317 }
318
319 BooleanOp::Not(inner) => {
320 let input_spin = process_expression(inner, builder);
321 let gadget: LogicGadget<W> = not_gadget();
322 let output_spin = builder.allocate_spin();
323 let spin_map = vec![input_spin, output_spin];
324 builder.add_gadget(&gadget, &spin_map);
325 output_spin
326 }
327
328 BooleanOp::And(args) => process_binary_chain(args, builder, and_gadget),
329
330 BooleanOp::Or(args) => process_binary_chain(args, builder, or_gadget),
331
332 BooleanOp::Xor(args) => process_binary_chain(args, builder, xor_gadget),
333 }
334}
335
336fn process_binary_chain<W, F>(
338 args: &[BooleanExpr],
339 builder: &mut SpinGlassBuilder<W>,
340 gadget_fn: F,
341) -> usize
342where
343 W: Clone + Default + Zero + AddAssign + From<i32>,
344 F: Fn() -> LogicGadget<W>,
345{
346 assert!(!args.is_empty(), "Binary gate must have at least one argument");
347
348 if args.len() == 1 {
349 return process_expression(&args[0], builder);
351 }
352
353 let mut result_spin = {
355 let input0 = process_expression(&args[0], builder);
356 let input1 = process_expression(&args[1], builder);
357 let gadget = gadget_fn();
358 let output_spin = builder.allocate_spin();
359
360 let spin_map = if gadget.num_spins() == 4 {
362 let aux_spin = builder.allocate_spin();
364 vec![input0, input1, output_spin, aux_spin]
365 } else {
366 vec![input0, input1, output_spin]
368 };
369
370 builder.add_gadget(&gadget, &spin_map);
371 output_spin
372 };
373
374 for arg in args.iter().skip(2) {
376 let next_input = process_expression(arg, builder);
377 let gadget = gadget_fn();
378 let output_spin = builder.allocate_spin();
379
380 let spin_map = if gadget.num_spins() == 4 {
381 let aux_spin = builder.allocate_spin();
382 vec![result_spin, next_input, output_spin, aux_spin]
383 } else {
384 vec![result_spin, next_input, output_spin]
385 };
386
387 builder.add_gadget(&gadget, &spin_map);
388 result_spin = output_spin;
389 }
390
391 result_spin
392}
393
394fn process_assignment<W>(
396 assignment: &Assignment,
397 builder: &mut SpinGlassBuilder<W>,
398) where
399 W: Clone + Default + Zero + AddAssign + From<i32>,
400{
401 let expr_output = process_expression(&assignment.expr, builder);
403
404 for output_name in &assignment.outputs {
407 let output_spin = builder.get_or_create_variable(output_name);
408
409 if output_spin != expr_output {
411 let key = if output_spin < expr_output {
415 (output_spin, expr_output)
416 } else {
417 (expr_output, output_spin)
418 };
419 builder
420 .interactions
421 .entry(key)
422 .or_insert_with(W::zero)
423 .add_assign(W::from(-4)); }
425 }
426}
427
428impl<W> ReduceTo<SpinGlass<W>> for CircuitSAT<W>
429where
430 W: Clone + Default + PartialOrd + Num + Zero + AddAssign + From<i32> + 'static,
431{
432 type Result = ReductionCircuitToSG<W>;
433
434 fn reduce_to(&self) -> Self::Result {
435 let mut builder = SpinGlassBuilder::new();
436
437 for assignment in &self.circuit().assignments {
439 process_assignment(assignment, &mut builder);
440 }
441
442 let (target, variable_map) = builder.build();
443 let source_variables = self.variable_names().to_vec();
444 let source_size = self.problem_size();
445
446 ReductionCircuitToSG {
447 target,
448 variable_map,
449 source_variables,
450 source_size,
451 }
452 }
453}
454
455#[cfg(test)]
456mod tests {
457 use super::*;
458 use crate::models::specialized::Circuit;
459 use crate::solvers::{BruteForce, Solver};
460
461 fn verify_gadget_truth_table<W>(gadget: &LogicGadget<W>, expected: &[(Vec<usize>, Vec<usize>)])
463 where
464 W: Clone
465 + Default
466 + PartialOrd
467 + Num
468 + Zero
469 + AddAssign
470 + From<i32>
471 + std::ops::Mul<Output = W>
472 + std::fmt::Debug
473 + 'static,
474 {
475 let solver = BruteForce::new();
476 let solutions = solver.find_best(&gadget.problem);
477
478 for (inputs, outputs) in expected {
480 let found = solutions.iter().any(|sol| {
481 let input_match = gadget
482 .inputs
483 .iter()
484 .zip(inputs)
485 .all(|(&idx, &expected)| sol[idx] == expected);
486 let output_match = gadget
487 .outputs
488 .iter()
489 .zip(outputs)
490 .all(|(&idx, &expected)| sol[idx] == expected);
491 input_match && output_match
492 });
493 assert!(
494 found,
495 "Expected ground state with inputs {:?} and outputs {:?} not found in {:?}",
496 inputs, outputs, solutions
497 );
498 }
499 }
500
501 #[test]
502 fn test_and_gadget() {
503 let gadget: LogicGadget<i32> = and_gadget();
504 assert_eq!(gadget.num_spins(), 3);
505 assert_eq!(gadget.inputs, vec![0, 1]);
506 assert_eq!(gadget.outputs, vec![2]);
507
508 let truth_table = vec![
510 (vec![0, 0], vec![0]), (vec![0, 1], vec![0]), (vec![1, 0], vec![0]), (vec![1, 1], vec![1]), ];
515 verify_gadget_truth_table(&gadget, &truth_table);
516 }
517
518 #[test]
519 fn test_or_gadget() {
520 let gadget: LogicGadget<i32> = or_gadget();
521 assert_eq!(gadget.num_spins(), 3);
522 assert_eq!(gadget.inputs, vec![0, 1]);
523 assert_eq!(gadget.outputs, vec![2]);
524
525 let truth_table = vec![
527 (vec![0, 0], vec![0]), (vec![0, 1], vec![1]), (vec![1, 0], vec![1]), (vec![1, 1], vec![1]), ];
532 verify_gadget_truth_table(&gadget, &truth_table);
533 }
534
535 #[test]
536 fn test_not_gadget() {
537 let gadget: LogicGadget<i32> = not_gadget();
538 assert_eq!(gadget.num_spins(), 2);
539 assert_eq!(gadget.inputs, vec![0]);
540 assert_eq!(gadget.outputs, vec![1]);
541
542 let truth_table = vec![
544 (vec![0], vec![1]), (vec![1], vec![0]), ];
547 verify_gadget_truth_table(&gadget, &truth_table);
548 }
549
550 #[test]
551 fn test_xor_gadget() {
552 let gadget: LogicGadget<i32> = xor_gadget();
553 assert_eq!(gadget.num_spins(), 4);
554 assert_eq!(gadget.inputs, vec![0, 1]);
555 assert_eq!(gadget.outputs, vec![2]);
556
557 let truth_table = vec![
559 (vec![0, 0], vec![0]), (vec![0, 1], vec![1]), (vec![1, 0], vec![1]), (vec![1, 1], vec![0]), ];
564 verify_gadget_truth_table(&gadget, &truth_table);
565 }
566
567 #[test]
568 fn test_set0_gadget() {
569 let gadget: LogicGadget<i32> = set0_gadget();
570 assert_eq!(gadget.num_spins(), 1);
571 assert_eq!(gadget.inputs, Vec::<usize>::new());
572 assert_eq!(gadget.outputs, vec![0]);
573
574 let solver = BruteForce::new();
575 let solutions = solver.find_best(&gadget.problem);
576 assert!(solutions.contains(&vec![0]));
578 assert!(!solutions.contains(&vec![1]));
579 }
580
581 #[test]
582 fn test_set1_gadget() {
583 let gadget: LogicGadget<i32> = set1_gadget();
584 assert_eq!(gadget.num_spins(), 1);
585 assert_eq!(gadget.inputs, Vec::<usize>::new());
586 assert_eq!(gadget.outputs, vec![0]);
587
588 let solver = BruteForce::new();
589 let solutions = solver.find_best(&gadget.problem);
590 assert!(solutions.contains(&vec![1]));
592 assert!(!solutions.contains(&vec![0]));
593 }
594
595 #[test]
596 fn test_simple_and_circuit() {
597 let circuit = Circuit::new(vec![Assignment::new(
599 vec!["c".to_string()],
600 BooleanExpr::and(vec![BooleanExpr::var("x"), BooleanExpr::var("y")]),
601 )]);
602 let problem = CircuitSAT::<i32>::new(circuit);
603 let reduction = problem.reduce_to();
604 let sg = reduction.target_problem();
605
606 let solver = BruteForce::new();
607 let solutions = solver.find_best(sg);
608
609 let extracted: Vec<Vec<usize>> = solutions
611 .iter()
612 .map(|s| reduction.extract_solution(s))
613 .collect();
614
615 let valid_configs = vec![
618 vec![0, 0, 0], vec![0, 0, 1], vec![0, 1, 0], vec![1, 1, 1], ];
623
624 for config in &valid_configs {
625 assert!(
626 extracted.contains(config),
627 "Expected valid config {:?} not found in {:?}",
628 config,
629 extracted
630 );
631 }
632 }
633
634 #[test]
635 fn test_simple_or_circuit() {
636 let circuit = Circuit::new(vec![Assignment::new(
638 vec!["c".to_string()],
639 BooleanExpr::or(vec![BooleanExpr::var("x"), BooleanExpr::var("y")]),
640 )]);
641 let problem = CircuitSAT::<i32>::new(circuit);
642 let reduction = problem.reduce_to();
643 let sg = reduction.target_problem();
644
645 let solver = BruteForce::new();
646 let solutions = solver.find_best(sg);
647
648 let extracted: Vec<Vec<usize>> = solutions
649 .iter()
650 .map(|s| reduction.extract_solution(s))
651 .collect();
652
653 let valid_configs = vec![
655 vec![0, 0, 0], vec![1, 0, 1], vec![1, 1, 0], vec![1, 1, 1], ];
660
661 for config in &valid_configs {
662 assert!(
663 extracted.contains(config),
664 "Expected valid config {:?} not found in {:?}",
665 config,
666 extracted
667 );
668 }
669 }
670
671 #[test]
672 fn test_not_circuit() {
673 let circuit = Circuit::new(vec![Assignment::new(
675 vec!["c".to_string()],
676 BooleanExpr::not(BooleanExpr::var("x")),
677 )]);
678 let problem = CircuitSAT::<i32>::new(circuit);
679 let reduction = problem.reduce_to();
680 let sg = reduction.target_problem();
681
682 let solver = BruteForce::new();
683 let solutions = solver.find_best(sg);
684
685 let extracted: Vec<Vec<usize>> = solutions
686 .iter()
687 .map(|s| reduction.extract_solution(s))
688 .collect();
689
690 let valid_configs = vec![
692 vec![1, 0], vec![0, 1], ];
695
696 for config in &valid_configs {
697 assert!(
698 extracted.contains(config),
699 "Expected valid config {:?} not found in {:?}",
700 config,
701 extracted
702 );
703 }
704 }
705
706 #[test]
707 fn test_xor_circuit() {
708 let circuit = Circuit::new(vec![Assignment::new(
710 vec!["c".to_string()],
711 BooleanExpr::xor(vec![BooleanExpr::var("x"), BooleanExpr::var("y")]),
712 )]);
713 let problem = CircuitSAT::<i32>::new(circuit);
714 let reduction = problem.reduce_to();
715 let sg = reduction.target_problem();
716
717 let solver = BruteForce::new();
718 let solutions = solver.find_best(sg);
719
720 let extracted: Vec<Vec<usize>> = solutions
721 .iter()
722 .map(|s| reduction.extract_solution(s))
723 .collect();
724
725 let valid_configs = vec![
727 vec![0, 0, 0], vec![1, 0, 1], vec![1, 1, 0], vec![0, 1, 1], ];
732
733 for config in &valid_configs {
734 assert!(
735 extracted.contains(config),
736 "Expected valid config {:?} not found in {:?}",
737 config,
738 extracted
739 );
740 }
741 }
742
743 #[test]
744 fn test_constant_true() {
745 let circuit = Circuit::new(vec![Assignment::new(
747 vec!["c".to_string()],
748 BooleanExpr::constant(true),
749 )]);
750 let problem = CircuitSAT::<i32>::new(circuit);
751 let reduction = problem.reduce_to();
752 let sg = reduction.target_problem();
753
754 let solver = BruteForce::new();
755 let solutions = solver.find_best(sg);
756
757 let extracted: Vec<Vec<usize>> = solutions
758 .iter()
759 .map(|s| reduction.extract_solution(s))
760 .collect();
761
762 assert!(extracted.contains(&vec![1]), "Expected c=1 in {:?}", extracted);
764 }
765
766 #[test]
767 fn test_constant_false() {
768 let circuit = Circuit::new(vec![Assignment::new(
770 vec!["c".to_string()],
771 BooleanExpr::constant(false),
772 )]);
773 let problem = CircuitSAT::<i32>::new(circuit);
774 let reduction = problem.reduce_to();
775 let sg = reduction.target_problem();
776
777 let solver = BruteForce::new();
778 let solutions = solver.find_best(sg);
779
780 let extracted: Vec<Vec<usize>> = solutions
781 .iter()
782 .map(|s| reduction.extract_solution(s))
783 .collect();
784
785 assert!(extracted.contains(&vec![0]), "Expected c=0 in {:?}", extracted);
787 }
788
789 #[test]
790 fn test_multi_input_and() {
791 let circuit = Circuit::new(vec![Assignment::new(
793 vec!["c".to_string()],
794 BooleanExpr::and(vec![
795 BooleanExpr::var("x"),
796 BooleanExpr::var("y"),
797 BooleanExpr::var("z"),
798 ]),
799 )]);
800 let problem = CircuitSAT::<i32>::new(circuit);
801 let reduction = problem.reduce_to();
802 let sg = reduction.target_problem();
803
804 let solver = BruteForce::new();
805 let solutions = solver.find_best(sg);
806
807 let extracted: Vec<Vec<usize>> = solutions
808 .iter()
809 .map(|s| reduction.extract_solution(s))
810 .collect();
811
812 assert!(
815 extracted.contains(&vec![1, 1, 1, 1]),
816 "Expected (1,1,1,1) in {:?}",
817 extracted
818 );
819 assert!(
821 extracted.contains(&vec![0, 0, 0, 0]),
822 "Expected (0,0,0,0) in {:?}",
823 extracted
824 );
825 }
826
827 #[test]
828 fn test_chained_circuit() {
829 let circuit = Circuit::new(vec![
832 Assignment::new(
833 vec!["c".to_string()],
834 BooleanExpr::and(vec![BooleanExpr::var("x"), BooleanExpr::var("y")]),
835 ),
836 Assignment::new(
837 vec!["d".to_string()],
838 BooleanExpr::or(vec![BooleanExpr::var("c"), BooleanExpr::var("z")]),
839 ),
840 ]);
841 let problem = CircuitSAT::<i32>::new(circuit);
842 let reduction = problem.reduce_to();
843 let sg = reduction.target_problem();
844
845 let solver = BruteForce::new();
846 let solutions = solver.find_best(sg);
847
848 let extracted: Vec<Vec<usize>> = solutions
849 .iter()
850 .map(|s| reduction.extract_solution(s))
851 .collect();
852
853 assert!(
859 extracted.contains(&vec![1, 1, 1, 1, 0]),
860 "Expected (1,1,1,1,0) in {:?}",
861 extracted
862 );
863
864 assert!(
866 extracted.contains(&vec![0, 1, 0, 0, 1]),
867 "Expected (0,1,0,0,1) in {:?}",
868 extracted
869 );
870
871 assert!(
873 extracted.contains(&vec![0, 0, 0, 0, 0]),
874 "Expected (0,0,0,0,0) in {:?}",
875 extracted
876 );
877 }
878
879 #[test]
880 fn test_nested_expression() {
881 let circuit = Circuit::new(vec![Assignment::new(
883 vec!["c".to_string()],
884 BooleanExpr::or(vec![
885 BooleanExpr::and(vec![BooleanExpr::var("x"), BooleanExpr::var("y")]),
886 BooleanExpr::var("z"),
887 ]),
888 )]);
889 let problem = CircuitSAT::<i32>::new(circuit);
890 let reduction = problem.reduce_to();
891 let sg = reduction.target_problem();
892
893 let solver = BruteForce::new();
894 let solutions = solver.find_best(sg);
895
896 let extracted: Vec<Vec<usize>> = solutions
897 .iter()
898 .map(|s| reduction.extract_solution(s))
899 .collect();
900
901 assert!(
906 extracted.contains(&vec![1, 1, 1, 0]),
907 "Expected (1,1,1,0) in {:?}",
908 extracted
909 );
910
911 assert!(
913 extracted.contains(&vec![1, 0, 0, 1]),
914 "Expected (1,0,0,1) in {:?}",
915 extracted
916 );
917
918 assert!(
920 extracted.contains(&vec![0, 0, 0, 0]),
921 "Expected (0,0,0,0) in {:?}",
922 extracted
923 );
924 }
925
926 #[test]
927 fn test_reduction_result_methods() {
928 let circuit = Circuit::new(vec![Assignment::new(
929 vec!["c".to_string()],
930 BooleanExpr::var("x"),
931 )]);
932 let problem = CircuitSAT::<i32>::new(circuit);
933 let reduction = problem.reduce_to();
934
935 let source_size = reduction.source_size();
937 let target_size = reduction.target_size();
938
939 assert!(source_size.get("num_variables").is_some());
940 assert!(target_size.get("num_spins").is_some());
941 }
942
943 #[test]
944 fn test_empty_circuit() {
945 let circuit = Circuit::new(vec![]);
946 let problem = CircuitSAT::<i32>::new(circuit);
947 let reduction = problem.reduce_to();
948 let sg = reduction.target_problem();
949
950 assert_eq!(sg.num_spins(), 0);
952 }
953
954 #[test]
955 fn test_solution_extraction() {
956 let circuit = Circuit::new(vec![Assignment::new(
957 vec!["c".to_string()],
958 BooleanExpr::and(vec![BooleanExpr::var("x"), BooleanExpr::var("y")]),
959 )]);
960 let problem = CircuitSAT::<i32>::new(circuit);
961 let reduction = problem.reduce_to();
962
963 assert_eq!(reduction.source_variables, vec!["c", "x", "y"]);
965
966 let sg = reduction.target_problem();
969 assert!(sg.num_spins() >= 3); }
971}
972
973use crate::poly;
975use crate::rules::registry::{ReductionEntry, ReductionOverhead};
976
977inventory::submit! {
978 ReductionEntry {
979 source_name: "CircuitSAT",
980 target_name: "SpinGlass",
981 source_graph: "Circuit",
982 target_graph: "SpinGlassGraph",
983 overhead_fn: || ReductionOverhead::new(vec![
984 ("num_spins", poly!(num_assignments)),
985 ("num_interactions", poly!(num_assignments)),
986 ]),
987 }
988}