problemreductions/models/optimization/
qubo.rs1use crate::graph_types::SimpleGraph;
6use crate::traits::Problem;
7use crate::types::{EnergyMode, ProblemSize, SolutionSize};
8use serde::{Deserialize, Serialize};
9
10#[derive(Debug, Clone, Serialize, Deserialize)]
41pub struct QUBO<W = f64> {
42 num_vars: usize,
44 matrix: Vec<Vec<W>>,
47}
48
49impl<W: Clone + Default> QUBO<W> {
50 pub fn from_matrix(matrix: Vec<Vec<W>>) -> Self {
55 let num_vars = matrix.len();
56 Self { num_vars, matrix }
57 }
58
59 pub fn new(linear: Vec<W>, quadratic: Vec<((usize, usize), W)>) -> Self
65 where
66 W: num_traits::Zero,
67 {
68 let num_vars = linear.len();
69 let mut matrix = vec![vec![W::zero(); num_vars]; num_vars];
70
71 for (i, val) in linear.into_iter().enumerate() {
73 matrix[i][i] = val;
74 }
75
76 for ((i, j), val) in quadratic {
78 if i < j {
79 matrix[i][j] = val;
80 } else {
81 matrix[j][i] = val;
82 }
83 }
84
85 Self { num_vars, matrix }
86 }
87
88 pub fn num_vars(&self) -> usize {
90 self.num_vars
91 }
92
93 pub fn matrix(&self) -> &[Vec<W>] {
95 &self.matrix
96 }
97
98 pub fn get(&self, i: usize, j: usize) -> Option<&W> {
100 self.matrix.get(i).and_then(|row| row.get(j))
101 }
102}
103
104impl<W> Problem for QUBO<W>
105where
106 W: Clone
107 + Default
108 + PartialOrd
109 + num_traits::Num
110 + num_traits::Zero
111 + std::ops::AddAssign
112 + std::ops::Mul<Output = W>
113 + 'static,
114{
115 const NAME: &'static str = "QUBO";
116 type GraphType = SimpleGraph;
117 type Weight = W;
118 type Size = W;
119
120 fn num_variables(&self) -> usize {
121 self.num_vars
122 }
123
124 fn num_flavors(&self) -> usize {
125 2 }
127
128 fn problem_size(&self) -> ProblemSize {
129 ProblemSize::new(vec![("num_vars", self.num_vars)])
130 }
131
132 fn energy_mode(&self) -> EnergyMode {
133 EnergyMode::SmallerSizeIsBetter }
135
136 fn solution_size(&self, config: &[usize]) -> SolutionSize<Self::Size> {
137 let value = self.evaluate(config);
138 SolutionSize::valid(value)
139 }
140}
141
142impl<W> QUBO<W>
143where
144 W: Clone + num_traits::Zero + std::ops::AddAssign + std::ops::Mul<Output = W>,
145{
146 pub fn evaluate(&self, config: &[usize]) -> W {
148 let mut value = W::zero();
149
150 for i in 0..self.num_vars {
151 let x_i = config.get(i).copied().unwrap_or(0);
152 if x_i == 0 {
153 continue;
154 }
155
156 for j in i..self.num_vars {
157 let x_j = config.get(j).copied().unwrap_or(0);
158 if x_j == 0 {
159 continue;
160 }
161
162 if let Some(q_ij) = self.matrix.get(i).and_then(|row| row.get(j)) {
163 value += q_ij.clone();
164 }
165 }
166 }
167
168 value
169 }
170}
171
172#[cfg(test)]
173mod tests {
174 use super::*;
175 use crate::solvers::{BruteForce, Solver};
176
177 #[test]
178 fn test_qubo_from_matrix() {
179 let problem = QUBO::from_matrix(vec![vec![1.0, 2.0], vec![0.0, 3.0]]);
180 assert_eq!(problem.num_vars(), 2);
181 assert_eq!(problem.get(0, 0), Some(&1.0));
182 assert_eq!(problem.get(0, 1), Some(&2.0));
183 assert_eq!(problem.get(1, 1), Some(&3.0));
184 }
185
186 #[test]
187 fn test_qubo_new() {
188 let problem = QUBO::new(vec![1.0, 2.0], vec![((0, 1), 3.0)]);
189 assert_eq!(problem.get(0, 0), Some(&1.0));
190 assert_eq!(problem.get(1, 1), Some(&2.0));
191 assert_eq!(problem.get(0, 1), Some(&3.0));
192 }
193
194 #[test]
195 fn test_evaluate() {
196 let problem = QUBO::from_matrix(vec![vec![1.0, 2.0], vec![0.0, 3.0]]);
199
200 assert_eq!(problem.evaluate(&[0, 0]), 0.0);
201 assert_eq!(problem.evaluate(&[1, 0]), 1.0);
202 assert_eq!(problem.evaluate(&[0, 1]), 3.0);
203 assert_eq!(problem.evaluate(&[1, 1]), 6.0); }
205
206 #[test]
207 fn test_solution_size() {
208 let problem = QUBO::from_matrix(vec![vec![1.0, 2.0], vec![0.0, 3.0]]);
209
210 let sol = problem.solution_size(&[0, 0]);
211 assert!(sol.is_valid);
212 assert_eq!(sol.size, 0.0);
213
214 let sol = problem.solution_size(&[1, 1]);
215 assert_eq!(sol.size, 6.0);
216 }
217
218 #[test]
219 fn test_brute_force_minimize() {
220 let problem = QUBO::from_matrix(vec![vec![1.0, 0.0], vec![0.0, -2.0]]);
224 let solver = BruteForce::new();
225
226 let solutions = solver.find_best(&problem);
227 assert_eq!(solutions.len(), 1);
228 assert_eq!(solutions[0], vec![0, 1]);
229 assert_eq!(problem.solution_size(&solutions[0]).size, -2.0);
230 }
231
232 #[test]
233 fn test_brute_force_with_interaction() {
234 let problem = QUBO::from_matrix(vec![vec![-1.0, 2.0], vec![0.0, -1.0]]);
238 let solver = BruteForce::new();
239
240 let solutions = solver.find_best(&problem);
241 assert_eq!(solutions.len(), 2);
243 for sol in &solutions {
244 assert_eq!(problem.solution_size(sol).size, -1.0);
245 }
246 }
247
248 #[test]
249 fn test_energy_mode() {
250 let problem = QUBO::<f64>::from_matrix(vec![vec![1.0]]);
251 assert!(problem.energy_mode().is_minimization());
252 }
253
254 #[test]
255 fn test_num_variables_flavors() {
256 let problem = QUBO::<f64>::from_matrix(vec![vec![0.0; 5]; 5]);
257 assert_eq!(problem.num_variables(), 5);
258 assert_eq!(problem.num_flavors(), 2);
259 }
260
261 #[test]
262 fn test_problem_size() {
263 let problem = QUBO::<f64>::from_matrix(vec![vec![0.0; 3]; 3]);
264 let size = problem.problem_size();
265 assert_eq!(size.get("num_vars"), Some(3));
266 }
267
268 #[test]
269 fn test_matrix_access() {
270 let problem = QUBO::from_matrix(vec![
271 vec![1.0, 2.0, 3.0],
272 vec![0.0, 4.0, 5.0],
273 vec![0.0, 0.0, 6.0],
274 ]);
275 let matrix = problem.matrix();
276 assert_eq!(matrix.len(), 3);
277 assert_eq!(matrix[0], vec![1.0, 2.0, 3.0]);
278 }
279
280 #[test]
281 fn test_empty_qubo() {
282 let problem = QUBO::<f64>::from_matrix(vec![]);
283 assert_eq!(problem.num_vars(), 0);
284 assert_eq!(problem.evaluate(&[]), 0.0);
285 }
286
287 #[test]
288 fn test_single_variable() {
289 let problem = QUBO::from_matrix(vec![vec![-5.0]]);
290 let solver = BruteForce::new();
291
292 let solutions = solver.find_best(&problem);
293 assert_eq!(solutions.len(), 1);
294 assert_eq!(solutions[0], vec![1]); }
296
297 #[test]
298 fn test_qubo_new_reverse_indices() {
299 let problem = QUBO::new(vec![1.0, 2.0], vec![((1, 0), 3.0)]); assert_eq!(problem.get(0, 1), Some(&3.0)); }
303
304 #[test]
305 fn test_get_out_of_bounds() {
306 let problem = QUBO::from_matrix(vec![vec![1.0, 2.0], vec![0.0, 3.0]]);
307 assert_eq!(problem.get(5, 5), None);
308 assert_eq!(problem.get(0, 5), None);
309 }
310}