1use crate::models::graph::MaxCut;
7use crate::models::optimization::SpinGlass;
8use crate::rules::traits::{ReduceTo, ReductionResult};
9use crate::traits::Problem;
10use crate::types::ProblemSize;
11use num_traits::{Num, Zero};
12use std::ops::AddAssign;
13
14#[derive(Debug, Clone)]
16pub struct ReductionMaxCutToSG<W> {
17 target: SpinGlass<W>,
18 source_size: ProblemSize,
19}
20
21impl<W> ReductionResult for ReductionMaxCutToSG<W>
22where
23 W: Clone + Default + PartialOrd + Num + Zero + AddAssign + From<i32> + 'static,
24{
25 type Source = MaxCut<W>;
26 type Target = SpinGlass<W>;
27
28 fn target_problem(&self) -> &Self::Target {
29 &self.target
30 }
31
32 fn extract_solution(&self, target_solution: &[usize]) -> Vec<usize> {
33 target_solution.to_vec()
34 }
35
36 fn source_size(&self) -> ProblemSize {
37 self.source_size.clone()
38 }
39
40 fn target_size(&self) -> ProblemSize {
41 self.target.problem_size()
42 }
43}
44
45impl<W> ReduceTo<SpinGlass<W>> for MaxCut<W>
46where
47 W: Clone + Default + PartialOrd + Num + Zero + AddAssign + From<i32> + 'static,
48{
49 type Result = ReductionMaxCutToSG<W>;
50
51 fn reduce_to(&self) -> Self::Result {
52 let n = self.num_vertices();
53 let edges_with_weights = self.edges();
54
55 let interactions: Vec<((usize, usize), W)> = edges_with_weights
73 .into_iter()
74 .map(|(u, v, w)| ((u, v), w))
75 .collect();
76
77 let onsite = vec![W::zero(); n];
79
80 let target = SpinGlass::new(n, interactions, onsite);
81
82 ReductionMaxCutToSG {
83 target,
84 source_size: self.problem_size(),
85 }
86 }
87}
88
89#[derive(Debug, Clone)]
91pub struct ReductionSGToMaxCut<W> {
92 target: MaxCut<W>,
93 source_size: ProblemSize,
94 ancilla: Option<usize>,
96}
97
98impl<W> ReductionResult for ReductionSGToMaxCut<W>
99where
100 W: Clone + Default + PartialOrd + Num + Zero + AddAssign + From<i32> + 'static,
101{
102 type Source = SpinGlass<W>;
103 type Target = MaxCut<W>;
104
105 fn target_problem(&self) -> &Self::Target {
106 &self.target
107 }
108
109 fn extract_solution(&self, target_solution: &[usize]) -> Vec<usize> {
110 match self.ancilla {
111 None => target_solution.to_vec(),
112 Some(anc) => {
113 let mut sol = target_solution.to_vec();
115 if sol[anc] == 1 {
116 for x in sol.iter_mut() {
117 *x = 1 - *x;
118 }
119 }
120 sol.remove(anc);
121 sol
122 }
123 }
124 }
125
126 fn source_size(&self) -> ProblemSize {
127 self.source_size.clone()
128 }
129
130 fn target_size(&self) -> ProblemSize {
131 self.target.problem_size()
132 }
133}
134
135impl<W> ReduceTo<MaxCut<W>> for SpinGlass<W>
136where
137 W: Clone + Default + PartialOrd + Num + Zero + AddAssign + From<i32> + 'static,
138{
139 type Result = ReductionSGToMaxCut<W>;
140
141 fn reduce_to(&self) -> Self::Result {
142 let n = self.num_spins();
143 let interactions = self.interactions();
144 let fields = self.fields();
145
146 let need_ancilla = fields.iter().any(|h| !h.is_zero());
148 let total_vertices = if need_ancilla { n + 1 } else { n };
149 let ancilla_idx = if need_ancilla { Some(n) } else { None };
150
151 let mut edges = Vec::new();
152 let mut weights = Vec::new();
153
154 for &((i, j), ref w) in interactions {
156 edges.push((i, j));
157 weights.push(w.clone());
158 }
159
160 if need_ancilla {
164 for (i, h) in fields.iter().enumerate() {
165 if !h.is_zero() {
166 edges.push((i, n));
167 weights.push(h.clone());
168 }
169 }
170 }
171
172 let target = MaxCut::with_weights(total_vertices, edges, weights);
173
174 ReductionSGToMaxCut {
175 target,
176 source_size: self.problem_size(),
177 ancilla: ancilla_idx,
178 }
179 }
180}
181
182#[cfg(test)]
183mod tests {
184 use super::*;
185 use crate::solvers::{BruteForce, Solver};
186
187 #[test]
188 fn test_maxcut_to_spinglass() {
189 let mc = MaxCut::<i32>::unweighted(3, vec![(0, 1), (1, 2), (0, 2)]);
191 let reduction = ReduceTo::<SpinGlass<i32>>::reduce_to(&mc);
192 let sg = reduction.target_problem();
193
194 let solver = BruteForce::new();
195 let solutions = solver.find_best(sg);
196
197 assert!(!solutions.is_empty());
198 }
199
200 #[test]
201 fn test_spinglass_to_maxcut_no_onsite() {
202 let sg = SpinGlass::new(3, vec![((0, 1), 1), ((1, 2), 1)], vec![0, 0, 0]);
204 let reduction = ReduceTo::<MaxCut<i32>>::reduce_to(&sg);
205 let mc = reduction.target_problem();
206
207 assert_eq!(mc.num_vertices(), 3); assert!(reduction.ancilla.is_none());
209 }
210
211 #[test]
212 fn test_spinglass_to_maxcut_with_onsite() {
213 let sg = SpinGlass::new(2, vec![((0, 1), 1)], vec![1, 0]);
215 let reduction = ReduceTo::<MaxCut<i32>>::reduce_to(&sg);
216 let mc = reduction.target_problem();
217
218 assert_eq!(mc.num_vertices(), 3); assert_eq!(reduction.ancilla, Some(2));
220 }
221
222 #[test]
223 fn test_solution_extraction_no_ancilla() {
224 let sg = SpinGlass::new(2, vec![((0, 1), 1)], vec![0, 0]);
225 let reduction = ReduceTo::<MaxCut<i32>>::reduce_to(&sg);
226
227 let mc_sol = vec![0, 1];
228 let extracted = reduction.extract_solution(&mc_sol);
229 assert_eq!(extracted, vec![0, 1]);
230 }
231
232 #[test]
233 fn test_solution_extraction_with_ancilla() {
234 let sg = SpinGlass::new(2, vec![((0, 1), 1)], vec![1, 0]);
235 let reduction = ReduceTo::<MaxCut<i32>>::reduce_to(&sg);
236
237 let mc_sol = vec![0, 1, 0];
239 let extracted = reduction.extract_solution(&mc_sol);
240 assert_eq!(extracted, vec![0, 1]);
241
242 let mc_sol = vec![0, 1, 1];
244 let extracted = reduction.extract_solution(&mc_sol);
245 assert_eq!(extracted, vec![1, 0]); }
247
248 #[test]
249 fn test_weighted_maxcut() {
250 let mc = MaxCut::new(3, vec![(0, 1, 10), (1, 2, 20)]);
251 let reduction = ReduceTo::<SpinGlass<i32>>::reduce_to(&mc);
252 let sg = reduction.target_problem();
253
254 let interactions = sg.interactions();
256 assert_eq!(interactions.len(), 2);
257 }
258}
259
260use crate::poly;
262use crate::rules::registry::{ReductionEntry, ReductionOverhead};
263
264inventory::submit! {
265 ReductionEntry {
266 source_name: "MaxCut",
267 target_name: "SpinGlass",
268 source_graph: "SimpleGraph",
269 target_graph: "SpinGlassGraph",
270 overhead_fn: || ReductionOverhead::new(vec![
271 ("num_spins", poly!(num_vertices)),
272 ("num_interactions", poly!(num_edges)),
273 ]),
274 }
275}
276
277inventory::submit! {
278 ReductionEntry {
279 source_name: "SpinGlass",
280 target_name: "MaxCut",
281 source_graph: "SpinGlassGraph",
282 target_graph: "SimpleGraph",
283 overhead_fn: || ReductionOverhead::new(vec![
284 ("num_vertices", poly!(num_spins)),
285 ("num_edges", poly!(num_interactions)),
286 ]),
287 }
288}