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 interactions: Vec<((usize, usize), W)> = self.interactions.into_iter().collect();
283 let sg = SpinGlass::new(self.num_spins, interactions, self.fields);
284 (sg, self.variable_map)
285 }
286}
287
288fn process_expression<W>(expr: &BooleanExpr, builder: &mut SpinGlassBuilder<W>) -> usize
290where
291 W: Clone + Default + Zero + AddAssign + From<i32>,
292{
293 match &expr.op {
294 BooleanOp::Var(name) => builder.get_or_create_variable(name),
295
296 BooleanOp::Const(value) => {
297 let gadget: LogicGadget<W> = if *value { set1_gadget() } else { set0_gadget() };
298 let output_spin = builder.allocate_spin();
299 let spin_map = vec![output_spin];
300 builder.add_gadget(&gadget, &spin_map);
301 output_spin
302 }
303
304 BooleanOp::Not(inner) => {
305 let input_spin = process_expression(inner, builder);
306 let gadget: LogicGadget<W> = not_gadget();
307 let output_spin = builder.allocate_spin();
308 let spin_map = vec![input_spin, output_spin];
309 builder.add_gadget(&gadget, &spin_map);
310 output_spin
311 }
312
313 BooleanOp::And(args) => process_binary_chain(args, builder, and_gadget),
314
315 BooleanOp::Or(args) => process_binary_chain(args, builder, or_gadget),
316
317 BooleanOp::Xor(args) => process_binary_chain(args, builder, xor_gadget),
318 }
319}
320
321fn process_binary_chain<W, F>(
323 args: &[BooleanExpr],
324 builder: &mut SpinGlassBuilder<W>,
325 gadget_fn: F,
326) -> usize
327where
328 W: Clone + Default + Zero + AddAssign + From<i32>,
329 F: Fn() -> LogicGadget<W>,
330{
331 assert!(
332 !args.is_empty(),
333 "Binary gate must have at least one argument"
334 );
335
336 if args.len() == 1 {
337 return process_expression(&args[0], builder);
339 }
340
341 let mut result_spin = {
343 let input0 = process_expression(&args[0], builder);
344 let input1 = process_expression(&args[1], builder);
345 let gadget = gadget_fn();
346 let output_spin = builder.allocate_spin();
347
348 let spin_map = if gadget.num_spins() == 4 {
350 let aux_spin = builder.allocate_spin();
352 vec![input0, input1, output_spin, aux_spin]
353 } else {
354 vec![input0, input1, output_spin]
356 };
357
358 builder.add_gadget(&gadget, &spin_map);
359 output_spin
360 };
361
362 for arg in args.iter().skip(2) {
364 let next_input = process_expression(arg, builder);
365 let gadget = gadget_fn();
366 let output_spin = builder.allocate_spin();
367
368 let spin_map = if gadget.num_spins() == 4 {
369 let aux_spin = builder.allocate_spin();
370 vec![result_spin, next_input, output_spin, aux_spin]
371 } else {
372 vec![result_spin, next_input, output_spin]
373 };
374
375 builder.add_gadget(&gadget, &spin_map);
376 result_spin = output_spin;
377 }
378
379 result_spin
380}
381
382fn process_assignment<W>(assignment: &Assignment, builder: &mut SpinGlassBuilder<W>)
384where
385 W: Clone + Default + Zero + AddAssign + From<i32>,
386{
387 let expr_output = process_expression(&assignment.expr, builder);
389
390 for output_name in &assignment.outputs {
393 let output_spin = builder.get_or_create_variable(output_name);
394
395 if output_spin != expr_output {
397 let key = if output_spin < expr_output {
401 (output_spin, expr_output)
402 } else {
403 (expr_output, output_spin)
404 };
405 builder
406 .interactions
407 .entry(key)
408 .or_insert_with(W::zero)
409 .add_assign(W::from(-4)); }
411 }
412}
413
414#[reduction(
415 overhead = {
416 num_spins = "num_assignments",
417 num_interactions = "num_assignments",
418 }
419)]
420impl ReduceTo<SpinGlass<SimpleGraph, i32>> for CircuitSAT {
421 type Result = ReductionCircuitToSG;
422
423 fn reduce_to(&self) -> Self::Result {
424 let mut builder: SpinGlassBuilder<i32> = SpinGlassBuilder::new();
425
426 for assignment in &self.circuit().assignments {
428 process_assignment(assignment, &mut builder);
429 }
430
431 let (target, variable_map) = builder.build();
432 let source_variables = self.variable_names().to_vec();
433
434 ReductionCircuitToSG {
435 target,
436 variable_map,
437 source_variables,
438 }
439 }
440}
441
442#[cfg(test)]
443#[path = "../unit_tests/rules/circuit_spinglass.rs"]
444mod tests;