problemreductions/solvers/
brute_force.rs1use crate::config::ConfigIterator;
4use crate::solvers::Solver;
5use crate::traits::Problem;
6use crate::types::SolutionSize;
7
8#[derive(Debug, Clone)]
13pub struct BruteForce {
14 pub atol: f64,
16 pub rtol: f64,
18 pub valid_only: bool,
20}
21
22impl Default for BruteForce {
23 fn default() -> Self {
24 Self {
25 atol: 1e-10,
26 rtol: 1e-10,
27 valid_only: true,
28 }
29 }
30}
31
32impl BruteForce {
33 pub fn new() -> Self {
35 Self::default()
36 }
37
38 pub fn with_tolerance(atol: f64, rtol: f64) -> Self {
40 Self {
41 atol,
42 rtol,
43 valid_only: true,
44 }
45 }
46
47 pub fn valid_only(mut self, valid_only: bool) -> Self {
49 self.valid_only = valid_only;
50 self
51 }
52
53 fn approx_equal(&self, a: f64, b: f64) -> bool {
55 let diff = (a - b).abs();
56 diff <= self.atol || diff <= self.rtol * b.abs().max(a.abs())
57 }
58}
59
60impl Solver for BruteForce {
61 fn find_best<P: Problem>(&self, problem: &P) -> Vec<Vec<usize>> {
62 self.find_best_with_size(problem)
63 .into_iter()
64 .map(|(config, _)| config)
65 .collect()
66 }
67
68 fn find_best_with_size<P: Problem>(
69 &self,
70 problem: &P,
71 ) -> Vec<(Vec<usize>, SolutionSize<P::Size>)> {
72 let num_variables = problem.num_variables();
73 let num_flavors = problem.num_flavors();
74
75 if num_variables == 0 {
76 return vec![];
77 }
78
79 let iter = ConfigIterator::new(num_variables, num_flavors);
80 let energy_mode = problem.energy_mode();
81
82 let mut best_solutions: Vec<(Vec<usize>, SolutionSize<P::Size>)> = vec![];
83 let mut best_size: Option<P::Size> = None;
84
85 for config in iter {
86 let solution = problem.solution_size(&config);
87
88 if self.valid_only && !solution.is_valid {
90 continue;
91 }
92
93 let is_new_best = match &best_size {
94 None => true,
95 Some(current_best) => energy_mode.is_better(&solution.size, current_best),
96 };
97
98 if is_new_best {
99 best_size = Some(solution.size.clone());
100 best_solutions.clear();
101 best_solutions.push((config, solution));
102 } else if let Some(current_best) = &best_size {
103 if self.is_equal_size(&solution.size, current_best) {
105 best_solutions.push((config, solution));
106 }
107 }
108 }
109
110 best_solutions
111 }
112}
113
114impl BruteForce {
115 #[allow(clippy::neg_cmp_op_on_partial_ord)]
117 fn is_equal_size<T: PartialOrd + Clone>(&self, a: &T, b: &T) -> bool {
118 matches!(a.partial_cmp(b), Some(std::cmp::Ordering::Equal))
121 }
122}
123
124pub trait BruteForceFloat {
126 fn find_best_float<P: Problem<Size = f64>>(
128 &self,
129 problem: &P,
130 ) -> Vec<(Vec<usize>, SolutionSize<f64>)>;
131}
132
133impl BruteForceFloat for BruteForce {
134 fn find_best_float<P: Problem<Size = f64>>(
135 &self,
136 problem: &P,
137 ) -> Vec<(Vec<usize>, SolutionSize<f64>)> {
138 let num_variables = problem.num_variables();
139 let num_flavors = problem.num_flavors();
140
141 if num_variables == 0 {
142 return vec![];
143 }
144
145 let iter = ConfigIterator::new(num_variables, num_flavors);
146 let energy_mode = problem.energy_mode();
147
148 let mut best_solutions: Vec<(Vec<usize>, SolutionSize<f64>)> = vec![];
149 let mut best_size: Option<f64> = None;
150
151 for config in iter {
152 let solution = problem.solution_size(&config);
153
154 if self.valid_only && !solution.is_valid {
155 continue;
156 }
157
158 let is_new_best = match &best_size {
159 None => true,
160 Some(current_best) => energy_mode.is_better(&solution.size, current_best),
161 };
162
163 if is_new_best {
164 best_size = Some(solution.size);
165 best_solutions.clear();
166 best_solutions.push((config, solution));
167 } else if let Some(current_best) = &best_size {
168 if self.approx_equal(solution.size, *current_best) {
169 best_solutions.push((config, solution));
170 }
171 }
172 }
173
174 best_solutions
175 }
176}
177
178#[cfg(test)]
179mod tests {
180 use super::*;
181 use crate::graph_types::SimpleGraph;
182 use crate::types::{EnergyMode, ProblemSize};
183
184 #[derive(Clone)]
186 struct MaxSumProblem {
187 weights: Vec<i32>,
188 }
189
190 impl Problem for MaxSumProblem {
191 const NAME: &'static str = "MaxSumProblem";
192 type GraphType = SimpleGraph;
193 type Weight = i32;
194 type Size = i32;
195
196 fn num_variables(&self) -> usize {
197 self.weights.len()
198 }
199
200 fn num_flavors(&self) -> usize {
201 2
202 }
203
204 fn problem_size(&self) -> ProblemSize {
205 ProblemSize::new(vec![("variables", self.weights.len())])
206 }
207
208 fn energy_mode(&self) -> EnergyMode {
209 EnergyMode::LargerSizeIsBetter
210 }
211
212 fn solution_size(&self, config: &[usize]) -> SolutionSize<Self::Size> {
213 let sum: i32 = config
214 .iter()
215 .zip(&self.weights)
216 .map(|(&c, &w)| if c == 1 { w } else { 0 })
217 .sum();
218 SolutionSize::valid(sum)
219 }
220 }
221
222 #[derive(Clone)]
224 struct MinSumProblem {
225 weights: Vec<i32>,
226 }
227
228 impl Problem for MinSumProblem {
229 const NAME: &'static str = "MinSumProblem";
230 type GraphType = SimpleGraph;
231 type Weight = i32;
232 type Size = i32;
233
234 fn num_variables(&self) -> usize {
235 self.weights.len()
236 }
237
238 fn num_flavors(&self) -> usize {
239 2
240 }
241
242 fn problem_size(&self) -> ProblemSize {
243 ProblemSize::new(vec![("variables", self.weights.len())])
244 }
245
246 fn energy_mode(&self) -> EnergyMode {
247 EnergyMode::SmallerSizeIsBetter
248 }
249
250 fn solution_size(&self, config: &[usize]) -> SolutionSize<Self::Size> {
251 let sum: i32 = config
252 .iter()
253 .zip(&self.weights)
254 .map(|(&c, &w)| if c == 1 { w } else { 0 })
255 .sum();
256 SolutionSize::valid(sum)
257 }
258 }
259
260 #[derive(Clone)]
262 struct SelectAtMostOneProblem {
263 weights: Vec<i32>,
264 }
265
266 impl Problem for SelectAtMostOneProblem {
267 const NAME: &'static str = "SelectAtMostOneProblem";
268 type GraphType = SimpleGraph;
269 type Weight = i32;
270 type Size = i32;
271
272 fn num_variables(&self) -> usize {
273 self.weights.len()
274 }
275
276 fn num_flavors(&self) -> usize {
277 2
278 }
279
280 fn problem_size(&self) -> ProblemSize {
281 ProblemSize::new(vec![("variables", self.weights.len())])
282 }
283
284 fn energy_mode(&self) -> EnergyMode {
285 EnergyMode::LargerSizeIsBetter
286 }
287
288 fn solution_size(&self, config: &[usize]) -> SolutionSize<Self::Size> {
289 let selected: usize = config.iter().sum();
290 let sum: i32 = config
291 .iter()
292 .zip(&self.weights)
293 .map(|(&c, &w)| if c == 1 { w } else { 0 })
294 .sum();
295 SolutionSize::new(sum, selected <= 1)
296 }
297 }
298
299 #[test]
300 fn test_brute_force_maximization() {
301 let problem = MaxSumProblem {
302 weights: vec![1, 2, 3],
303 };
304 let solver = BruteForce::new();
305
306 let best = solver.find_best(&problem);
307 assert_eq!(best.len(), 1);
308 assert_eq!(best[0], vec![1, 1, 1]); }
310
311 #[test]
312 fn test_brute_force_minimization() {
313 let problem = MinSumProblem {
314 weights: vec![1, 2, 3],
315 };
316 let solver = BruteForce::new();
317
318 let best = solver.find_best(&problem);
319 assert_eq!(best.len(), 1);
320 assert_eq!(best[0], vec![0, 0, 0]); }
322
323 #[test]
324 fn test_brute_force_with_validity() {
325 let problem = SelectAtMostOneProblem {
326 weights: vec![1, 5, 3],
327 };
328 let solver = BruteForce::new();
329
330 let best = solver.find_best(&problem);
331 assert_eq!(best.len(), 1);
332 assert_eq!(best[0], vec![0, 1, 0]); }
334
335 #[test]
336 fn test_brute_force_multiple_optimal() {
337 let problem = MaxSumProblem {
338 weights: vec![1, 1, 1],
339 };
340 let solver = BruteForce::new();
341
342 let best = solver.find_best(&problem);
343 assert_eq!(best.len(), 1);
344 assert_eq!(best[0], vec![1, 1, 1]); let problem2 = SelectAtMostOneProblem {
348 weights: vec![5, 5, 3],
349 };
350 let best2 = solver.find_best(&problem2);
351 assert_eq!(best2.len(), 2); }
353
354 #[test]
355 fn test_brute_force_with_size() {
356 let problem = MaxSumProblem {
357 weights: vec![1, 2, 3],
358 };
359 let solver = BruteForce::new();
360
361 let best = solver.find_best_with_size(&problem);
362 assert_eq!(best.len(), 1);
363 assert_eq!(best[0].0, vec![1, 1, 1]);
364 assert_eq!(best[0].1.size, 6);
365 assert!(best[0].1.is_valid);
366 }
367
368 #[test]
369 fn test_brute_force_empty_problem() {
370 let problem = MaxSumProblem { weights: vec![] };
371 let solver = BruteForce::new();
372
373 let best = solver.find_best(&problem);
374 assert!(best.is_empty());
375 }
376
377 #[test]
378 fn test_brute_force_valid_only_false() {
379 let problem = SelectAtMostOneProblem {
380 weights: vec![1, 2, 3],
381 };
382 let solver = BruteForce::new().valid_only(false);
383
384 let best = solver.find_best(&problem);
385 assert_eq!(best.len(), 1);
387 assert_eq!(best[0], vec![1, 1, 1]);
388 }
389
390 #[test]
391 fn test_brute_force_with_tolerance() {
392 let solver = BruteForce::with_tolerance(0.01, 0.01);
393 assert_eq!(solver.atol, 0.01);
394 assert_eq!(solver.rtol, 0.01);
395 }
396
397 #[derive(Clone)]
399 struct FloatProblem {
400 weights: Vec<f64>,
401 }
402
403 impl Problem for FloatProblem {
404 const NAME: &'static str = "FloatProblem";
405 type GraphType = SimpleGraph;
406 type Weight = f64;
407 type Size = f64;
408
409 fn num_variables(&self) -> usize {
410 self.weights.len()
411 }
412
413 fn num_flavors(&self) -> usize {
414 2
415 }
416
417 fn problem_size(&self) -> ProblemSize {
418 ProblemSize::new(vec![("variables", self.weights.len())])
419 }
420
421 fn energy_mode(&self) -> EnergyMode {
422 EnergyMode::LargerSizeIsBetter
423 }
424
425 fn solution_size(&self, config: &[usize]) -> SolutionSize<Self::Size> {
426 let sum: f64 = config
427 .iter()
428 .zip(&self.weights)
429 .map(|(&c, &w)| if c == 1 { w } else { 0.0 })
430 .sum();
431 SolutionSize::valid(sum)
432 }
433 }
434
435 #[test]
436 fn test_brute_force_float() {
437 use super::BruteForceFloat;
438
439 let problem = FloatProblem {
440 weights: vec![1.0, 2.0, 3.0],
441 };
442 let solver = BruteForce::new();
443
444 let best = solver.find_best_float(&problem);
445 assert_eq!(best.len(), 1);
446 assert_eq!(best[0].0, vec![1, 1, 1]);
447 assert!((best[0].1.size - 6.0).abs() < 1e-10);
448 }
449
450 #[test]
451 fn test_brute_force_float_tolerance() {
452 use super::BruteForceFloat;
453
454 #[derive(Clone)]
456 struct NearlyEqualProblem;
457
458 impl Problem for NearlyEqualProblem {
459 const NAME: &'static str = "NearlyEqualProblem";
460 type GraphType = SimpleGraph;
461 type Weight = f64;
462 type Size = f64;
463
464 fn num_variables(&self) -> usize {
465 2
466 }
467
468 fn num_flavors(&self) -> usize {
469 2
470 }
471
472 fn problem_size(&self) -> ProblemSize {
473 ProblemSize::new(vec![("variables", 2)])
474 }
475
476 fn energy_mode(&self) -> EnergyMode {
477 EnergyMode::LargerSizeIsBetter
478 }
479
480 fn solution_size(&self, config: &[usize]) -> SolutionSize<Self::Size> {
481 let size = match (config.first(), config.get(1)) {
482 (Some(1), Some(0)) => 10.0,
483 (Some(0), Some(1)) => 10.0 + 1e-12, _ => 0.0,
485 };
486 SolutionSize::valid(size)
487 }
488 }
489
490 let problem = NearlyEqualProblem;
491 let solver = BruteForce::with_tolerance(1e-10, 1e-10);
492
493 let best = solver.find_best_float(&problem);
494 assert_eq!(best.len(), 2);
496 }
497
498 #[test]
499 fn test_brute_force_float_empty() {
500 use super::BruteForceFloat;
501
502 let problem = FloatProblem { weights: vec![] };
503 let solver = BruteForce::new();
504
505 let best = solver.find_best_float(&problem);
506 assert!(best.is_empty());
507 }
508}