1use crate::models::formula::{Assignment, BooleanExpr, BooleanOp, CircuitSAT};
10use crate::models::graph::SpinGlass;
11use crate::reduction;
12use crate::rules::traits::{ReduceTo, ReductionResult};
13use crate::topology::SimpleGraph;
14use num_traits::Zero;
15use std::collections::HashMap;
16use std::ops::AddAssign;
17
18#[derive(Debug, Clone)]
27pub struct LogicGadget<W> {
28 pub problem: SpinGlass<SimpleGraph, W>,
30 #[allow(dead_code)] pub inputs: Vec<usize>,
33 #[allow(dead_code)] pub outputs: Vec<usize>,
36}
37
38impl<W> LogicGadget<W> {
39 pub fn new(
41 problem: SpinGlass<SimpleGraph, W>,
42 inputs: Vec<usize>,
43 outputs: Vec<usize>,
44 ) -> Self {
45 Self {
46 problem,
47 inputs,
48 outputs,
49 }
50 }
51}
52
53impl<W: Clone + Default> LogicGadget<W> {
54 pub fn num_spins(&self) -> usize {
56 self.problem.num_spins()
57 }
58}
59
60pub fn and_gadget<W>() -> LogicGadget<W>
73where
74 W: Clone + Default + From<i32>,
75{
76 let interactions = vec![
77 ((0, 1), W::from(1)),
78 ((0, 2), W::from(-2)),
79 ((1, 2), W::from(-2)),
80 ];
81 let fields = vec![W::from(-1), W::from(-1), W::from(2)];
82 let sg = SpinGlass::new(3, interactions, fields);
83 LogicGadget::new(sg, vec![0, 1], vec![2])
84}
85
86pub fn or_gadget<W>() -> LogicGadget<W>
95where
96 W: Clone + Default + From<i32>,
97{
98 let interactions = vec![
99 ((0, 1), W::from(1)),
100 ((0, 2), W::from(-2)),
101 ((1, 2), W::from(-2)),
102 ];
103 let fields = vec![W::from(1), W::from(1), W::from(-2)];
104 let sg = SpinGlass::new(3, interactions, fields);
105 LogicGadget::new(sg, vec![0, 1], vec![2])
106}
107
108pub fn not_gadget<W>() -> LogicGadget<W>
116where
117 W: Clone + Default + From<i32> + Zero,
118{
119 let interactions = vec![((0, 1), W::from(1))];
120 let fields = vec![W::zero(), W::zero()];
121 let sg = SpinGlass::new(2, interactions, fields);
122 LogicGadget::new(sg, vec![0], vec![1])
123}
124
125pub fn xor_gadget<W>() -> LogicGadget<W>
133where
134 W: Clone + Default + From<i32>,
135{
136 let interactions = vec![
137 ((0, 1), W::from(1)),
138 ((0, 2), W::from(-1)),
139 ((0, 3), W::from(-2)),
140 ((1, 2), W::from(-1)),
141 ((1, 3), W::from(-2)),
142 ((2, 3), W::from(2)),
143 ];
144 let fields = vec![W::from(-1), W::from(-1), W::from(1), W::from(2)];
145 let sg = SpinGlass::new(4, interactions, fields);
146 LogicGadget::new(sg, vec![0, 1], vec![2])
150}
151
152pub fn set0_gadget<W>() -> LogicGadget<W>
157where
158 W: Clone + Default + From<i32>,
159{
160 let interactions = vec![];
161 let fields = vec![W::from(1)];
162 let sg = SpinGlass::new(1, interactions, fields);
163 LogicGadget::new(sg, vec![], vec![0])
164}
165
166pub fn set1_gadget<W>() -> LogicGadget<W>
171where
172 W: Clone + Default + From<i32>,
173{
174 let interactions = vec![];
175 let fields = vec![W::from(-1)];
176 let sg = SpinGlass::new(1, interactions, fields);
177 LogicGadget::new(sg, vec![], vec![0])
178}
179
180#[derive(Debug, Clone)]
182pub struct ReductionCircuitToSG {
183 target: SpinGlass<SimpleGraph, i32>,
185 variable_map: HashMap<String, usize>,
187 source_variables: Vec<String>,
189}
190
191impl ReductionResult for ReductionCircuitToSG {
192 type Source = CircuitSAT;
193 type Target = SpinGlass<SimpleGraph, i32>;
194
195 fn target_problem(&self) -> &Self::Target {
196 &self.target
197 }
198
199 fn extract_solution(&self, target_solution: &[usize]) -> Vec<usize> {
200 self.source_variables
201 .iter()
202 .map(|var| {
203 self.variable_map
204 .get(var)
205 .and_then(|&idx| target_solution.get(idx).copied())
206 .unwrap_or(0)
207 })
208 .collect()
209 }
210}
211
212struct SpinGlassBuilder<W> {
214 num_spins: usize,
216 interactions: HashMap<(usize, usize), W>,
218 fields: Vec<W>,
220 variable_map: HashMap<String, usize>,
222}
223
224impl<W> SpinGlassBuilder<W>
225where
226 W: Clone + Default + Zero + AddAssign + From<i32>,
227{
228 fn new() -> Self {
229 Self {
230 num_spins: 0,
231 interactions: HashMap::new(),
232 fields: Vec::new(),
233 variable_map: HashMap::new(),
234 }
235 }
236
237 fn allocate_spin(&mut self) -> usize {
239 let idx = self.num_spins;
240 self.num_spins += 1;
241 self.fields.push(W::zero());
242 idx
243 }
244
245 fn get_or_create_variable(&mut self, name: &str) -> usize {
247 if let Some(&idx) = self.variable_map.get(name) {
248 idx
249 } else {
250 let idx = self.allocate_spin();
251 self.variable_map.insert(name.to_string(), idx);
252 idx
253 }
254 }
255
256 fn add_gadget(&mut self, gadget: &LogicGadget<W>, spin_map: &[usize]) {
258 for ((i, j), weight) in gadget.problem.interactions() {
260 let global_i = spin_map[i];
261 let global_j = spin_map[j];
262 let key = if global_i < global_j {
263 (global_i, global_j)
264 } else {
265 (global_j, global_i)
266 };
267 self.interactions
268 .entry(key)
269 .or_insert_with(W::zero)
270 .add_assign(weight.clone());
271 }
272
273 for (local_idx, field) in gadget.problem.fields().iter().enumerate() {
275 let global_idx = spin_map[local_idx];
276 self.fields[global_idx].add_assign(field.clone());
277 }
278 }
279
280 fn build(self) -> (SpinGlass<SimpleGraph, W>, HashMap<String, usize>) {
282 let mut interactions: Vec<((usize, usize), W)> = self.interactions.into_iter().collect();
283 interactions.sort_by_key(|((u, v), _)| (*u, *v));
284 let sg = SpinGlass::new(self.num_spins, interactions, self.fields);
285 (sg, self.variable_map)
286 }
287}
288
289fn process_expression<W>(expr: &BooleanExpr, builder: &mut SpinGlassBuilder<W>) -> usize
291where
292 W: Clone + Default + Zero + AddAssign + From<i32>,
293{
294 match &expr.op {
295 BooleanOp::Var(name) => builder.get_or_create_variable(name),
296
297 BooleanOp::Const(value) => {
298 let gadget: LogicGadget<W> = if *value { set1_gadget() } else { set0_gadget() };
299 let output_spin = builder.allocate_spin();
300 let spin_map = vec![output_spin];
301 builder.add_gadget(&gadget, &spin_map);
302 output_spin
303 }
304
305 BooleanOp::Not(inner) => {
306 let input_spin = process_expression(inner, builder);
307 let gadget: LogicGadget<W> = not_gadget();
308 let output_spin = builder.allocate_spin();
309 let spin_map = vec![input_spin, output_spin];
310 builder.add_gadget(&gadget, &spin_map);
311 output_spin
312 }
313
314 BooleanOp::And(args) => process_binary_chain(args, builder, and_gadget),
315
316 BooleanOp::Or(args) => process_binary_chain(args, builder, or_gadget),
317
318 BooleanOp::Xor(args) => process_binary_chain(args, builder, xor_gadget),
319 }
320}
321
322fn process_binary_chain<W, F>(
324 args: &[BooleanExpr],
325 builder: &mut SpinGlassBuilder<W>,
326 gadget_fn: F,
327) -> usize
328where
329 W: Clone + Default + Zero + AddAssign + From<i32>,
330 F: Fn() -> LogicGadget<W>,
331{
332 assert!(
333 !args.is_empty(),
334 "Binary gate must have at least one argument"
335 );
336
337 if args.len() == 1 {
338 return process_expression(&args[0], builder);
340 }
341
342 let mut result_spin = {
344 let input0 = process_expression(&args[0], builder);
345 let input1 = process_expression(&args[1], builder);
346 let gadget = gadget_fn();
347 let output_spin = builder.allocate_spin();
348
349 let spin_map = if gadget.num_spins() == 4 {
351 let aux_spin = builder.allocate_spin();
353 vec![input0, input1, output_spin, aux_spin]
354 } else {
355 vec![input0, input1, output_spin]
357 };
358
359 builder.add_gadget(&gadget, &spin_map);
360 output_spin
361 };
362
363 for arg in args.iter().skip(2) {
365 let next_input = process_expression(arg, builder);
366 let gadget = gadget_fn();
367 let output_spin = builder.allocate_spin();
368
369 let spin_map = if gadget.num_spins() == 4 {
370 let aux_spin = builder.allocate_spin();
371 vec![result_spin, next_input, output_spin, aux_spin]
372 } else {
373 vec![result_spin, next_input, output_spin]
374 };
375
376 builder.add_gadget(&gadget, &spin_map);
377 result_spin = output_spin;
378 }
379
380 result_spin
381}
382
383fn process_assignment<W>(assignment: &Assignment, builder: &mut SpinGlassBuilder<W>)
385where
386 W: Clone + Default + Zero + AddAssign + From<i32>,
387{
388 let expr_output = process_expression(&assignment.expr, builder);
390
391 for output_name in &assignment.outputs {
394 let output_spin = builder.get_or_create_variable(output_name);
395
396 if output_spin != expr_output {
398 let key = if output_spin < expr_output {
402 (output_spin, expr_output)
403 } else {
404 (expr_output, output_spin)
405 };
406 builder
407 .interactions
408 .entry(key)
409 .or_insert_with(W::zero)
410 .add_assign(W::from(-4)); }
412 }
413}
414
415#[reduction(
416 overhead = {
417 num_spins = "num_assignments * num_variables",
418 num_interactions = "num_assignments * num_variables",
419 }
420)]
421impl ReduceTo<SpinGlass<SimpleGraph, i32>> for CircuitSAT {
422 type Result = ReductionCircuitToSG;
423
424 fn reduce_to(&self) -> Self::Result {
425 let mut builder: SpinGlassBuilder<i32> = SpinGlassBuilder::new();
426
427 for assignment in &self.circuit().assignments {
429 process_assignment(assignment, &mut builder);
430 }
431
432 let (target, variable_map) = builder.build();
433 let source_variables = self.variable_names().to_vec();
434
435 ReductionCircuitToSG {
436 target,
437 variable_map,
438 source_variables,
439 }
440 }
441}
442
443#[cfg(feature = "example-db")]
444pub(crate) fn canonical_rule_example_specs() -> Vec<crate::example_db::specs::RuleExampleSpec> {
445 use crate::export::SolutionPair;
446 use crate::models::formula::{Assignment, BooleanExpr, Circuit, CircuitSAT};
447
448 fn full_adder_circuit_sat() -> CircuitSAT {
449 let circuit = Circuit::new(vec![
450 Assignment::new(
451 vec!["t".to_string()],
452 BooleanExpr::xor(vec![BooleanExpr::var("a"), BooleanExpr::var("b")]),
453 ),
454 Assignment::new(
455 vec!["sum".to_string()],
456 BooleanExpr::xor(vec![BooleanExpr::var("t"), BooleanExpr::var("cin")]),
457 ),
458 Assignment::new(
459 vec!["ab".to_string()],
460 BooleanExpr::and(vec![BooleanExpr::var("a"), BooleanExpr::var("b")]),
461 ),
462 Assignment::new(
463 vec!["cin_t".to_string()],
464 BooleanExpr::and(vec![BooleanExpr::var("cin"), BooleanExpr::var("t")]),
465 ),
466 Assignment::new(
467 vec!["cout".to_string()],
468 BooleanExpr::or(vec![BooleanExpr::var("ab"), BooleanExpr::var("cin_t")]),
469 ),
470 ]);
471 CircuitSAT::new(circuit)
472 }
473
474 vec![crate::example_db::specs::RuleExampleSpec {
475 id: "circuitsat_to_spinglass",
476 build: || {
477 crate::example_db::specs::rule_example_with_witness::<_, SpinGlass<SimpleGraph, i32>>(
478 full_adder_circuit_sat(),
479 SolutionPair {
480 source_config: vec![0, 0, 0, 0, 0, 0, 0, 0],
481 target_config: vec![0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
482 },
483 )
484 },
485 }]
486}
487
488#[cfg(test)]
489#[path = "../unit_tests/rules/circuit_spinglass.rs"]
490mod tests;