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