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 crate::types::WeightElement;
15use num_traits::Zero;
16use std::collections::HashMap;
17#[cfg(test)]
18use std::ops::AddAssign;
19
20type BuiltSpinGlass = (SpinGlass<SimpleGraph, i64>, HashMap<String, usize>, i64);
21
22#[derive(Debug, Clone)]
31pub struct LogicGadget<W> {
32 pub problem: SpinGlass<SimpleGraph, W>,
34 #[allow(dead_code)] pub inputs: Vec<usize>,
37 #[allow(dead_code)] pub outputs: Vec<usize>,
40}
41
42impl<W> LogicGadget<W> {
43 pub fn new(
45 problem: SpinGlass<SimpleGraph, W>,
46 inputs: Vec<usize>,
47 outputs: Vec<usize>,
48 ) -> Self {
49 Self {
50 problem,
51 inputs,
52 outputs,
53 }
54 }
55}
56
57impl<W: Clone + Default> LogicGadget<W> {
58 pub fn num_spins(&self) -> usize {
60 self.problem.num_spins()
61 }
62}
63
64pub fn and_gadget<W>() -> LogicGadget<W>
77where
78 W: WeightElement + From<i64>,
79{
80 let interactions = vec![
81 ((0, 1), W::from(1)),
82 ((0, 2), W::from(-2)),
83 ((1, 2), W::from(-2)),
84 ];
85 let fields = vec![W::from(-1), W::from(-1), W::from(2)];
86 let sg = SpinGlass::new(3, interactions, fields);
87 LogicGadget::new(
88 sg.expect("static AND gadget must be valid"),
89 vec![0, 1],
90 vec![2],
91 )
92}
93
94pub fn or_gadget<W>() -> LogicGadget<W>
103where
104 W: WeightElement + From<i64>,
105{
106 let interactions = vec![
107 ((0, 1), W::from(1)),
108 ((0, 2), W::from(-2)),
109 ((1, 2), W::from(-2)),
110 ];
111 let fields = vec![W::from(1), W::from(1), W::from(-2)];
112 let sg = SpinGlass::new(3, interactions, fields);
113 LogicGadget::new(
114 sg.expect("static OR gadget must be valid"),
115 vec![0, 1],
116 vec![2],
117 )
118}
119
120pub fn not_gadget<W>() -> LogicGadget<W>
128where
129 W: WeightElement + From<i64> + Zero,
130{
131 let interactions = vec![((0, 1), W::from(1))];
132 let fields = vec![W::zero(), W::zero()];
133 let sg = SpinGlass::new(2, interactions, fields);
134 LogicGadget::new(
135 sg.expect("static NOT gadget must be valid"),
136 vec![0],
137 vec![1],
138 )
139}
140
141pub fn xor_gadget<W>() -> LogicGadget<W>
149where
150 W: WeightElement + From<i64>,
151{
152 let interactions = vec![
153 ((0, 1), W::from(1)),
154 ((0, 2), W::from(-1)),
155 ((0, 3), W::from(-2)),
156 ((1, 2), W::from(-1)),
157 ((1, 3), W::from(-2)),
158 ((2, 3), W::from(2)),
159 ];
160 let fields = vec![W::from(-1), W::from(-1), W::from(1), W::from(2)];
161 let sg = SpinGlass::new(4, interactions, fields);
162 LogicGadget::new(
166 sg.expect("static XOR gadget must be valid"),
167 vec![0, 1],
168 vec![2],
169 )
170}
171
172pub fn set0_gadget<W>() -> LogicGadget<W>
177where
178 W: WeightElement + From<i64>,
179{
180 let interactions = vec![];
181 let fields = vec![W::from(1)];
182 let sg = SpinGlass::new(1, interactions, fields);
183 LogicGadget::new(
184 sg.expect("static SET0 gadget must be valid"),
185 vec![],
186 vec![0],
187 )
188}
189
190pub fn set1_gadget<W>() -> LogicGadget<W>
195where
196 W: WeightElement + From<i64>,
197{
198 let interactions = vec![];
199 let fields = vec![W::from(-1)];
200 let sg = SpinGlass::new(1, interactions, fields);
201 LogicGadget::new(
202 sg.expect("static SET1 gadget must be valid"),
203 vec![],
204 vec![0],
205 )
206}
207
208#[derive(Debug, Clone)]
210pub struct ReductionCircuitToSG {
211 target: SpinGlass<SimpleGraph, i64>,
213 variable_map: HashMap<String, usize>,
215 source_variables: Vec<String>,
217 zero_penalty_energy: i64,
219}
220
221impl ReductionResult for ReductionCircuitToSG {
222 type Source = CircuitSAT;
223 type Target = SpinGlass<SimpleGraph, i64>;
224
225 fn target_problem(&self) -> &Self::Target {
226 &self.target
227 }
228
229 fn extract_solution(
230 &self,
231 target_solution: &<Self::Target as crate::traits::Problem>::Solution,
232 ) -> crate::rules::ExtractionResult<<Self::Source as crate::traits::Problem>::Solution> {
233 let value =
234 crate::rules::traits::validate_target_solution(self.target_problem(), target_solution)?;
235 if !crate::rules::AggregateReductionResult::extract_value(self, value).0 {
236 return Err(crate::rules::ExtractionError::invalid(
237 "SpinGlass energy does not meet the circuit zero-penalty threshold",
238 ));
239 }
240
241 Ok(self
242 .source_variables
243 .iter()
244 .map(|variable| target_solution[self.variable_map[variable]] == 1)
245 .collect())
246 }
247}
248
249impl crate::rules::AggregateReductionResult for ReductionCircuitToSG {
250 type Source = CircuitSAT;
251 type Target = SpinGlass<SimpleGraph, i64>;
252
253 fn target_problem(&self) -> &Self::Target {
254 &self.target
255 }
256
257 fn extract_value(&self, value: crate::types::Min<i64>) -> crate::types::Or {
258 crate::types::Or(value.0 == Some(self.zero_penalty_energy))
259 }
260}
261
262struct SpinGlassBuilder {
264 num_spins: usize,
266 interactions: HashMap<(usize, usize), i64>,
268 fields: Vec<i64>,
270 variable_map: HashMap<String, usize>,
272 zero_penalty_energy: i64,
273}
274
275impl SpinGlassBuilder {
276 fn new() -> Self {
277 Self {
278 num_spins: 0,
279 interactions: HashMap::new(),
280 fields: Vec::new(),
281 variable_map: HashMap::new(),
282 zero_penalty_energy: 0,
283 }
284 }
285
286 fn allocate_spin(&mut self) -> Result<usize, crate::registry::ConstructionError> {
288 let idx = self.num_spins;
289 self.num_spins = self
290 .num_spins
291 .checked_add(1)
292 .ok_or("spin count exceeds usize")?;
293 self.fields.push(0);
294 Ok(idx)
295 }
296
297 fn get_or_create_variable(
299 &mut self,
300 name: &str,
301 ) -> Result<usize, crate::registry::ConstructionError> {
302 if let Some(&idx) = self.variable_map.get(name) {
303 Ok(idx)
304 } else {
305 let idx = self.allocate_spin()?;
306 self.variable_map.insert(name.to_string(), idx);
307 Ok(idx)
308 }
309 }
310
311 fn add_gadget(
313 &mut self,
314 gadget: &LogicGadget<i64>,
315 spin_map: &[usize],
316 ground_energy: i64,
317 ) -> Result<(), crate::registry::ConstructionError> {
318 self.zero_penalty_energy = self
319 .zero_penalty_energy
320 .checked_add(ground_energy)
321 .ok_or_else(|| {
322 crate::registry::ConstructionError::IntegerOverflow(
323 "summing circuit gate ground energies".into(),
324 )
325 })?;
326 for ((i, j), weight) in gadget.problem.interactions() {
329 let global_i = spin_map[i];
330 let global_j = spin_map[j];
331 let key = if global_i < global_j {
332 (global_i, global_j)
333 } else {
334 (global_j, global_i)
335 };
336 let entry = self.interactions.entry(key).or_insert(0);
337 *entry = entry
338 .checked_add(weight)
339 .ok_or("circuit SpinGlass coupling overflow")?;
340 }
341
342 for (local_idx, field) in gadget.problem.fields().iter().enumerate() {
344 let global_idx = spin_map[local_idx];
345 self.fields[global_idx] = self.fields[global_idx]
346 .checked_add(*field)
347 .ok_or("circuit SpinGlass field overflow")?;
348 }
349 Ok(())
350 }
351
352 fn build(self) -> Result<BuiltSpinGlass, crate::registry::ConstructionError> {
354 let mut interactions: Vec<((usize, usize), i64)> = self.interactions.into_iter().collect();
355 interactions.sort_by_key(|((u, v), _)| (*u, *v));
356 let sg = SpinGlass::new(self.num_spins, interactions, self.fields);
357 Ok((sg?, self.variable_map, self.zero_penalty_energy))
358 }
359}
360
361fn process_expression(
363 expr: &BooleanExpr,
364 builder: &mut SpinGlassBuilder,
365) -> Result<usize, crate::registry::ConstructionError> {
366 match &expr.op {
367 BooleanOp::Var(name) => builder.get_or_create_variable(name),
368
369 BooleanOp::Const(value) => {
370 let gadget: LogicGadget<i64> = if *value { set1_gadget() } else { set0_gadget() };
371 let output_spin = builder.allocate_spin()?;
372 let spin_map = vec![output_spin];
373 builder.add_gadget(&gadget, &spin_map, -1)?;
374 Ok(output_spin)
375 }
376
377 BooleanOp::Not(inner) => {
378 let input_spin = process_expression(inner, builder)?;
379 let gadget: LogicGadget<i64> = not_gadget();
380 let output_spin = builder.allocate_spin()?;
381 let spin_map = vec![input_spin, output_spin];
382 builder.add_gadget(&gadget, &spin_map, -1)?;
383 Ok(output_spin)
384 }
385
386 BooleanOp::And(args) => process_binary_chain(args, builder, and_gadget, -3, true),
387
388 BooleanOp::Or(args) => process_binary_chain(args, builder, or_gadget, -3, false),
389
390 BooleanOp::Xor(args) => process_binary_chain(args, builder, xor_gadget, -4, false),
391 }
392}
393
394fn process_binary_chain<F>(
396 args: &[BooleanExpr],
397 builder: &mut SpinGlassBuilder,
398 gadget_fn: F,
399 ground_energy: i64,
400 empty_value: bool,
401) -> Result<usize, crate::registry::ConstructionError>
402where
403 F: Fn() -> LogicGadget<i64>,
404{
405 if args.is_empty() {
406 return process_expression(&BooleanExpr::constant(empty_value), builder);
408 }
409
410 if args.len() == 1 {
411 return process_expression(&args[0], builder);
413 }
414
415 let mut result_spin = {
417 let input0 = process_expression(&args[0], builder)?;
418 let input1 = process_expression(&args[1], builder)?;
419 let gadget = gadget_fn();
420 let output_spin = builder.allocate_spin()?;
421
422 let spin_map = if gadget.num_spins() == 4 {
424 let aux_spin = builder.allocate_spin()?;
426 vec![input0, input1, output_spin, aux_spin]
427 } else {
428 vec![input0, input1, output_spin]
430 };
431
432 builder.add_gadget(&gadget, &spin_map, ground_energy)?;
433 output_spin
434 };
435
436 for arg in args.iter().skip(2) {
438 let next_input = process_expression(arg, builder)?;
439 let gadget = gadget_fn();
440 let output_spin = builder.allocate_spin()?;
441
442 let spin_map = if gadget.num_spins() == 4 {
443 let aux_spin = builder.allocate_spin()?;
444 vec![result_spin, next_input, output_spin, aux_spin]
445 } else {
446 vec![result_spin, next_input, output_spin]
447 };
448
449 builder.add_gadget(&gadget, &spin_map, ground_energy)?;
450 result_spin = output_spin;
451 }
452
453 Ok(result_spin)
454}
455
456fn process_assignment(
458 assignment: &Assignment,
459 builder: &mut SpinGlassBuilder,
460) -> Result<(), crate::registry::ConstructionError> {
461 let expr_output = process_expression(&assignment.expr, builder)?;
463
464 for output_name in &assignment.outputs {
467 let output_spin = builder.get_or_create_variable(output_name)?;
468
469 if output_spin != expr_output {
471 let key = if output_spin < expr_output {
474 (output_spin, expr_output)
475 } else {
476 (expr_output, output_spin)
477 };
478 builder.zero_penalty_energy =
479 builder.zero_penalty_energy.checked_sub(4).ok_or_else(|| {
480 crate::registry::ConstructionError::IntegerOverflow(
481 "summing circuit equality ground energies".into(),
482 )
483 })?;
484 let entry = builder.interactions.entry(key).or_insert(0);
485 *entry = entry
486 .checked_add(-4)
487 .ok_or("circuit SpinGlass equality coupling overflow")?;
488 }
489 }
490 Ok(())
491}
492
493#[reduction(
494 aggregate = custom,
495 transform = upper_bound {
496 num_spins = "num_variables + 3 * num_expression_nodes",
497 num_interactions = "6 * num_expression_nodes + num_assignment_outputs",
498 }
499)]
500impl ReduceTo<SpinGlass<SimpleGraph, i64>> for CircuitSAT {
501 type Result = ReductionCircuitToSG;
502
503 fn reduce_to(&self) -> Result<Self::Result, crate::rules::ReductionError> {
504 let mut builder = SpinGlassBuilder::new();
505
506 for assignment in &self.circuit().assignments {
508 process_assignment(assignment, &mut builder).map_err(
509 crate::rules::ReductionError::construction::<
510 CircuitSAT,
511 SpinGlass<SimpleGraph, i64>,
512 >,
513 )?;
514 }
515
516 let (target, variable_map, zero_penalty_energy) = builder.build().map_err(
517 crate::rules::ReductionError::construction::<CircuitSAT, SpinGlass<SimpleGraph, i64>>,
518 )?;
519 let source_variables = self.variable_names().to_vec();
520
521 Ok(ReductionCircuitToSG {
522 target,
523 variable_map,
524 source_variables,
525 zero_penalty_energy,
526 })
527 }
528}
529
530#[cfg(feature = "example-db")]
531pub(crate) fn canonical_rule_example_specs() -> Vec<crate::example_db::specs::RuleExampleSpec> {
532 use crate::export::SolutionPair;
533 use crate::models::formula::{Assignment, BooleanExpr, Circuit, CircuitSAT};
534
535 fn full_adder_circuit_sat() -> CircuitSAT {
536 let circuit = Circuit::new(vec![
537 Assignment::new(
538 vec!["t".to_string()],
539 BooleanExpr::xor(vec![BooleanExpr::var("a"), BooleanExpr::var("b")]),
540 ),
541 Assignment::new(
542 vec!["sum".to_string()],
543 BooleanExpr::xor(vec![BooleanExpr::var("t"), BooleanExpr::var("cin")]),
544 ),
545 Assignment::new(
546 vec!["ab".to_string()],
547 BooleanExpr::and(vec![BooleanExpr::var("a"), BooleanExpr::var("b")]),
548 ),
549 Assignment::new(
550 vec!["cin_t".to_string()],
551 BooleanExpr::and(vec![BooleanExpr::var("cin"), BooleanExpr::var("t")]),
552 ),
553 Assignment::new(
554 vec!["cout".to_string()],
555 BooleanExpr::or(vec![BooleanExpr::var("ab"), BooleanExpr::var("cin_t")]),
556 ),
557 ]);
558 CircuitSAT::new(circuit)
559 }
560
561 vec![crate::example_db::specs::RuleExampleSpec {
562 id: "circuitsat_to_spinglass",
563 build: || {
564 crate::example_db::specs::rule_example_with_witness::<_, SpinGlass<SimpleGraph, i64>>(
565 full_adder_circuit_sat(),
566 SolutionPair {
567 source_config: serde_json::json!(vec![
568 false, false, false, false, false, false, false, false
569 ]),
570 target_config: serde_json::json!(vec![
571 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1
572 ]),
573 },
574 )
575 },
576 }]
577}
578
579#[cfg(test)]
580#[path = "../unit_tests/rules/circuit_spinglass.rs"]
581mod tests;