1use crate::models::algebraic::QUBO;
9use crate::models::graph::SpinGlass;
10use crate::reduction;
11use crate::rules::traits::{ReduceTo, ReductionResult};
12use crate::topology::SimpleGraph;
13
14#[derive(Debug, Clone)]
16pub struct ReductionQUBOToSG {
17 target: SpinGlass<SimpleGraph, f64>,
18}
19
20impl ReductionResult for ReductionQUBOToSG {
21 type Source = QUBO<f64>;
22 type Target = SpinGlass<SimpleGraph, f64>;
23
24 fn target_problem(&self) -> &Self::Target {
25 &self.target
26 }
27
28 fn extract_solution(
30 &self,
31 target_solution: &<Self::Target as crate::traits::Problem>::Solution,
32 ) -> crate::rules::ExtractionResult<<Self::Source as crate::traits::Problem>::Solution> {
33 crate::rules::traits::validate_target_solution(self.target_problem(), target_solution)?;
34
35 Ok(target_solution.iter().map(|&spin| spin == 1).collect())
36 }
37}
38
39#[reduction(
40 transform = exact {
41 num_spins = "num_vars",
42 num_interactions = "num_vars^2",
43 },
44)]
45impl ReduceTo<SpinGlass<SimpleGraph, f64>> for QUBO<f64> {
46 type Result = ReductionQUBOToSG;
47
48 fn reduce_to(&self) -> Result<Self::Result, crate::rules::ReductionError> {
49 let n = self.num_vars();
50 let matrix = self.matrix();
51
52 let mut interactions = Vec::new();
64 let mut onsite = vec![0.0; n];
65
66 for i in 0..n {
67 for j in i..n {
68 let q = matrix[i][j];
69 if q.abs() < 1e-10 {
70 continue;
71 }
72
73 if i == j {
74 onsite[i] += q / 2.0;
76 } else {
77 let j_ij = q / 4.0;
80 if j_ij.abs() > 1e-10 {
81 interactions.push(((i, j), j_ij));
82 }
83 onsite[i] += q / 4.0;
85 onsite[j] += q / 4.0;
86 }
87 }
88 }
89
90 let target = SpinGlass::<SimpleGraph, f64>::new(n, interactions, onsite).map_err(
91 |cause| {
92 crate::rules::ReductionError::construction::<
93 QUBO<f64>,
94 SpinGlass<SimpleGraph, f64>,
95 >(cause)
96 },
97 )?;
98
99 Ok(ReductionQUBOToSG { target })
100 }
101}
102
103#[derive(Debug, Clone)]
105pub struct ReductionSGToQUBO<W = f64> {
106 target: QUBO<W>,
107}
108
109impl<W> ReductionResult for ReductionSGToQUBO<W>
110where
111 W: crate::types::WeightElement + crate::types::NumericSize + crate::variant::VariantParam,
112{
113 type Source = SpinGlass<SimpleGraph, W>;
114 type Target = QUBO<W>;
115
116 fn target_problem(&self) -> &Self::Target {
117 &self.target
118 }
119
120 fn extract_solution(
121 &self,
122 target_solution: &<Self::Target as crate::traits::Problem>::Solution,
123 ) -> crate::rules::ExtractionResult<<Self::Source as crate::traits::Problem>::Solution> {
124 crate::rules::traits::validate_target_solution(self.target_problem(), target_solution)?;
125
126 Ok(target_solution
127 .iter()
128 .map(|&bit| if bit { 1 } else { -1 })
129 .collect())
130 }
131}
132
133#[reduction(
134 transform = exact {
135 num_vars = "num_spins",
136 }
137)]
138impl ReduceTo<QUBO<f64>> for SpinGlass<SimpleGraph, f64> {
139 type Result = ReductionSGToQUBO<f64>;
140
141 fn reduce_to(&self) -> Result<Self::Result, crate::rules::ReductionError> {
142 let n = self.num_spins();
143 let mut matrix = vec![vec![0.0; n]; n];
144
145 for ((i, j), j_val) in self.interactions() {
154 matrix[i][j] += 4.0 * j_val;
156 matrix[i][i] -= 2.0 * j_val;
158 matrix[j][j] -= 2.0 * j_val;
159 }
160
161 for (i, &h) in self.fields().iter().enumerate() {
163 matrix[i][i] += 2.0 * h;
165 }
166
167 let target = QUBO::from_matrix(matrix).map_err(|message| {
168 crate::rules::ReductionError::construction::<SpinGlass<SimpleGraph, f64>, QUBO<f64>>(
169 message,
170 )
171 })?;
172
173 Ok(ReductionSGToQUBO { target })
174 }
175}
176
177#[reduction(
178 transform = exact {
179 num_vars = "num_spins",
180 }
181)]
182impl ReduceTo<QUBO<i64>> for SpinGlass<SimpleGraph, i64> {
183 type Result = ReductionSGToQUBO<i64>;
184
185 fn reduce_to(&self) -> Result<Self::Result, crate::rules::ReductionError> {
186 let n = self.num_spins();
187 let mut matrix = vec![vec![0_i64; n]; n];
188 let overflow = |operation| {
189 crate::rules::ReductionError::integer_overflow::<SpinGlass<SimpleGraph, i64>, QUBO<i64>>(
190 operation,
191 )
192 };
193
194 for ((i, j), coupling) in self.interactions() {
195 let interaction = coupling
196 .checked_mul(4)
197 .ok_or_else(|| overflow("scaling a spin-glass interaction"))?;
198 matrix[i][j] = matrix[i][j]
199 .checked_add(interaction)
200 .ok_or_else(|| overflow("summing QUBO interaction coefficients"))?;
201 let diagonal = coupling
202 .checked_mul(2)
203 .ok_or_else(|| overflow("scaling a spin-glass diagonal contribution"))?;
204 matrix[i][i] = matrix[i][i]
205 .checked_sub(diagonal)
206 .ok_or_else(|| overflow("summing QUBO diagonal coefficients"))?;
207 matrix[j][j] = matrix[j][j]
208 .checked_sub(diagonal)
209 .ok_or_else(|| overflow("summing QUBO diagonal coefficients"))?;
210 }
211
212 for (i, &field) in self.fields().iter().enumerate() {
213 let diagonal = field
214 .checked_mul(2)
215 .ok_or_else(|| overflow("scaling a spin-glass field"))?;
216 matrix[i][i] = matrix[i][i]
217 .checked_add(diagonal)
218 .ok_or_else(|| overflow("summing QUBO diagonal coefficients"))?;
219 }
220
221 Ok(ReductionSGToQUBO {
222 target:
223 QUBO::from_matrix(matrix).map_err(
224 crate::rules::ReductionError::construction::<
225 SpinGlass<SimpleGraph, i64>,
226 QUBO<i64>,
227 >,
228 )?,
229 })
230 }
231}
232
233#[cfg(feature = "example-db")]
234pub(crate) fn canonical_rule_example_specs() -> Vec<crate::example_db::specs::RuleExampleSpec> {
235 use crate::export::SolutionPair;
236
237 vec![
238 crate::example_db::specs::RuleExampleSpec {
239 id: "qubo_to_spinglass",
240 build: || {
241 let (n, edges) = crate::topology::small_graphs::petersen();
242 let mut matrix = vec![vec![0.0; n]; n];
243 for (i, row) in matrix.iter_mut().enumerate() {
244 row[i] = -1.0 + 0.2 * i as f64;
245 }
246 for (idx, &(u, v)) in edges.iter().enumerate() {
247 let (i, j) = if u < v { (u, v) } else { (v, u) };
248 matrix[i][j] = if idx % 2 == 0 { 2.0 } else { -1.5 };
249 }
250 let source = QUBO::from_matrix(matrix).unwrap();
251 crate::example_db::specs::rule_example_with_witness::<_, SpinGlass<SimpleGraph, f64>>(
252 source,
253 SolutionPair {
254 source_config: serde_json::json!(vec![
255 true, false, true, true, true, false, true, false, false, true
256 ]),
257 target_config: serde_json::json!(vec![1, -1, 1, 1, 1, -1, 1, -1, -1, 1]),
258 },
259 )
260 },
261 },
262 crate::example_db::specs::RuleExampleSpec {
263 id: "spinglass_to_qubo",
264 build: || {
265 let (n, edges) = crate::topology::small_graphs::petersen();
266 let couplings: Vec<((usize, usize), f64)> = edges
267 .iter()
268 .enumerate()
269 .map(|(i, &(u, v))| ((u, v), if i % 2 == 0 { 1.0 } else { -1.0 }))
270 .collect();
271 let source = SpinGlass::new(n, couplings, vec![0.0; n]).unwrap();
272 crate::example_db::specs::rule_example_with_witness::<_, QUBO<f64>>(
273 source,
274 SolutionPair {
275 source_config: serde_json::json!(vec![1, -1, 1, 1, 1, -1, 1, -1, -1, 1]),
276 target_config: serde_json::json!(vec![
277 true, false, true, true, true, false, true, false, false, true
278 ]),
279 },
280 )
281 },
282 },
283 crate::example_db::specs::RuleExampleSpec {
284 id: "integer_spinglass_to_qubo",
285 build: || {
286 let source =
287 SpinGlass::<SimpleGraph, i64>::new(2, vec![((0, 1), 1)], vec![0, 0]).unwrap();
288 crate::example_db::specs::rule_example_with_witness::<_, QUBO<i64>>(
289 source,
290 SolutionPair {
291 source_config: serde_json::json!([1, -1]),
292 target_config: serde_json::json!([true, false]),
293 },
294 )
295 },
296 },
297 ]
298}
299
300#[cfg(test)]
301#[path = "../unit_tests/rules/spinglass_qubo.rs"]
302mod tests;