1#![allow(dead_code)]
7
8use quantrs2_core::platform::PlatformCapabilities;
9use scirs2_core::ndarray::{Array1, Array2, ArrayView1};
10use scirs2_core::simd_ops;
12use std::sync::Arc;
13
14pub struct OptimizedQUBOEvaluator {
16 qubo: Arc<Array2<f64>>,
18 cache: Vec<f64>,
20 use_simd: bool,
22 parallel_threshold: usize,
24}
25
26impl OptimizedQUBOEvaluator {
27 pub fn new(qubo: Array2<f64>) -> Self {
29 let n = qubo.shape()[0];
30
31 let cache: Vec<f64> = (0..n).map(|i| qubo[[i, i]]).collect();
33
34 Self {
35 qubo: Arc::new(qubo),
36 cache,
37 use_simd: {
38 let platform = PlatformCapabilities::detect();
39 platform.cpu.simd.avx2 || platform.cpu.simd.avx512
40 },
41 parallel_threshold: 1000,
42 }
43 }
44
45 pub fn evaluate(&self, x: &ArrayView1<u8>) -> f64 {
47 let n = x.len();
48
49 if n > self.parallel_threshold {
50 self.evaluate_parallel(x)
51 } else if self.use_simd && n >= 8 {
52 unsafe { self.evaluate_simd(x) }
53 } else {
54 self.evaluate_scalar(x)
55 }
56 }
57
58 fn evaluate_scalar(&self, x: &ArrayView1<u8>) -> f64 {
60 let n = x.len();
61 let mut energy = 0.0;
62
63 for i in 0..n {
65 if x[i] == 1 {
66 energy += self.cache[i];
67 }
68 }
69
70 for i in 0..n {
72 if x[i] == 1 {
73 for j in i + 1..n {
74 if x[j] == 1 {
75 energy += 2.0 * self.qubo[[i, j]];
76 }
77 }
78 }
79 }
80
81 energy
82 }
83
84 unsafe fn evaluate_simd(&self, x: &ArrayView1<u8>) -> f64 {
86 let n = x.len();
87 let mut energy = 0.0;
88
89 let x_float: Vec<f64> = x.iter().map(|&v| v as f64).collect();
91 let x_view = ArrayView1::from(&x_float);
92 let cache_view = ArrayView1::from(&self.cache[..n]);
93
94 let products = simd_ops::SimdUnifiedOps::simd_mul(&x_view, &cache_view);
96
97 energy = products.sum();
99
100 for i in 0..n {
102 if x[i] == 1 {
103 let row_start = i + 1;
105 if row_start < n {
106 let row_len = n - row_start;
107 let x_subset = ArrayView1::from(&x_float[row_start..n]);
108
109 let mut qubo_row = vec![0.0; row_len];
111 for (j, coeff) in qubo_row.iter_mut().enumerate() {
112 *coeff = self.qubo[[i, row_start + j]];
113 }
114 let qubo_row_view = ArrayView1::from(&qubo_row);
115
116 let row_products =
118 simd_ops::SimdUnifiedOps::simd_mul(&x_subset, &qubo_row_view);
119 energy += 2.0 * row_products.sum();
120 }
121 }
122 }
123
124 energy
125 }
126
127 #[cfg(not(feature = "simd"))]
129 unsafe fn evaluate_simd_fallback(&self, x: &ArrayView1<u8>) -> f64 {
130 self.evaluate_scalar(x)
131 }
132
133 fn evaluate_parallel(&self, x: &ArrayView1<u8>) -> f64 {
135 let n = x.len();
136
137 let linear_energy: f64 = (0..n).filter(|&i| x[i] == 1).map(|i| self.cache[i]).sum();
139
140 let block_size = (n as f64).sqrt() as usize + 1;
142 let quadratic_energy: f64 = (0..n)
143 .step_by(block_size)
144 .map(|block_start| {
145 let block_end = (block_start + block_size).min(n);
146 let mut local_sum = 0.0;
147
148 for i in block_start..block_end {
149 if x[i] == 1 {
150 for j in i + 1..n {
151 if x[j] == 1 {
152 local_sum += self.qubo[[i, j]];
153 }
154 }
155 }
156 }
157
158 local_sum
159 })
160 .sum();
161
162 2.0f64.mul_add(quadratic_energy, linear_energy)
163 }
164
165 pub fn delta_energy(&self, x: &ArrayView1<u8>, bit: usize) -> f64 {
167 let n = x.len();
168 let current_val = x[bit];
169 let new_val = 1 - current_val;
170
171 let mut delta = 0.0;
172
173 delta += (new_val as f64 - current_val as f64) * self.cache[bit];
175
176 for j in 0..n {
178 if j != bit && x[j] == 1 {
179 let coupling = if bit < j {
180 self.qubo[[bit, j]]
181 } else {
182 self.qubo[[j, bit]]
183 };
184 delta += 2.0 * (new_val as f64 - current_val as f64) * coupling;
185 }
186 }
187
188 delta
189 }
190}
191
192pub struct OptimizedSA {
194 evaluator: OptimizedQUBOEvaluator,
196 schedule: AnnealingSchedule,
198 parallel_moves: bool,
200}
201
202#[derive(Debug, Clone)]
203pub enum AnnealingSchedule {
204 Geometric { t0: f64, alpha: f64 },
206 Linear { t0: f64, max_iter: usize },
208 Adaptive { t0: f64, target_rate: f64 },
210 Custom(Vec<f64>),
212}
213
214impl OptimizedSA {
215 pub fn new(qubo: Array2<f64>) -> Self {
217 Self {
218 evaluator: OptimizedQUBOEvaluator::new(qubo),
219 schedule: AnnealingSchedule::Geometric {
220 t0: 1.0,
221 alpha: 0.99,
222 },
223 parallel_moves: false,
224 }
225 }
226
227 pub fn with_schedule(mut self, schedule: AnnealingSchedule) -> Self {
229 self.schedule = schedule;
230 self
231 }
232
233 pub const fn with_parallel_moves(mut self, parallel: bool) -> Self {
235 self.parallel_moves = parallel;
236 self
237 }
238
239 pub fn anneal(
241 &self,
242 initial: Array1<u8>,
243 iterations: usize,
244 rng: &mut impl scirs2_core::random::Rng,
245 ) -> (Array1<u8>, f64) {
246 let n = initial.len();
247 let mut current = initial;
248 let mut current_energy = self.evaluator.evaluate(¤t.view());
249
250 let mut best = current.clone();
251 let mut best_energy = current_energy;
252
253 let temperatures = self.generate_schedule(iterations);
255
256 for &temp in &temperatures {
257 if self.parallel_moves && n > 100 {
258 self.parallel_step(
260 &mut current,
261 &mut current_energy,
262 &mut best,
263 &mut best_energy,
264 temp,
265 rng,
266 );
267 } else {
268 for _ in 0..n {
270 let bit = rng.gen_range(0..n);
271 let delta = self.evaluator.delta_energy(¤t.view(), bit);
272
273 if delta < 0.0 || rng.gen::<f64>() < (-delta / temp).exp() {
274 current[bit] = 1 - current[bit];
275 current_energy += delta;
276
277 if current_energy < best_energy {
278 best = current.clone();
279 best_energy = current_energy;
280 }
281 }
282 }
283 }
284 }
285
286 (best, best_energy)
287 }
288
289 fn generate_schedule(&self, iterations: usize) -> Vec<f64> {
291 match &self.schedule {
292 AnnealingSchedule::Geometric { t0, alpha } => {
293 (0..iterations).map(|k| t0 * alpha.powi(k as i32)).collect()
294 }
295 AnnealingSchedule::Linear { t0, max_iter } => (0..iterations)
296 .map(|k| t0 * (1.0 - k as f64 / *max_iter as f64).max(0.0))
297 .collect(),
298 AnnealingSchedule::Adaptive { t0, .. } => {
299 vec![*t0; iterations]
301 }
302 AnnealingSchedule::Custom(schedule) => schedule.clone(),
303 }
304 }
305
306 fn parallel_step(
308 &self,
309 current: &mut Array1<u8>,
310 current_energy: &mut f64,
311 best: &mut Array1<u8>,
312 best_energy: &mut f64,
313 temp: f64,
314 rng: &mut impl scirs2_core::random::Rng,
315 ) {
316 let n = current.len();
317
318 let deltas: Vec<_> = (0..n)
320 .map(|bit| {
321 let delta = self.evaluator.delta_energy(¤t.view(), bit);
322 (bit, delta)
323 })
324 .collect();
325
326 let mut accepted = Vec::new();
328 for (bit, delta) in deltas {
329 if delta < 0.0 || rng.gen::<f64>() < (-delta / temp).exp() {
330 accepted.push((bit, delta));
331 }
332 }
333
334 let mut applied_energy = 0.0;
336 for (bit, delta) in accepted {
337 if applied_energy + delta < temp {
339 current[bit] = 1 - current[bit];
340 applied_energy += delta;
341 }
342 }
343
344 *current_energy += applied_energy;
345
346 if *current_energy < *best_energy {
347 *best = current.clone();
348 *best_energy = *current_energy;
349 }
350 }
351}
352
353pub mod matrix_ops {
355 use super::*;
356
357 pub fn sparse_qubo_multiply(
359 qubo: &Array2<f64>,
360 x: &ArrayView1<u8>,
361 _threshold: f64,
362 ) -> Array1<f64> {
363 let n = x.len();
364 let mut result = Array1::zeros(n);
365
366 let active: Vec<usize> = (0..n).filter(|&i| x[i] == 1).collect();
368
369 if active.len() < n / 4 {
370 for &i in &active {
372 result[i] += qubo[[i, i]];
373 for &j in &active {
374 if i != j {
375 result[i] += qubo[[i, j]];
376 }
377 }
378 }
379 } else {
380 for i in 0..n {
382 if x[i] == 1 {
383 for j in 0..n {
384 if x[j] == 1 {
385 result[i] += qubo[[i, j]];
386 }
387 }
388 }
389 }
390 }
391
392 result
393 }
394
395 pub fn block_qubo_eval(qubo: &Array2<f64>, x: &ArrayView1<u8>, block_size: usize) -> f64 {
397 let n = x.len();
398 let num_blocks = n.div_ceil(block_size);
399
400 let mut energy = 0.0;
401
402 for bi in 0..num_blocks {
404 for bj in bi..num_blocks {
405 let i_start = bi * block_size;
406 let i_end = ((bi + 1) * block_size).min(n);
407 let j_start = bj * block_size;
408 let j_end = ((bj + 1) * block_size).min(n);
409
410 for i in i_start..i_end {
412 if x[i] == 1 {
413 let j_begin = if bi == bj { i } else { j_start };
414 for j in j_begin..j_end {
415 if x[j] == 1 {
416 let factor = if i == j { 1.0 } else { 2.0 };
417 energy += factor * qubo[[i, j]];
418 }
419 }
420 }
421 }
422 }
423 }
424
425 energy
426 }
427}
428
429pub struct MemoryPool<T> {
431 buffers: Vec<Vec<T>>,
433 size: usize,
435}
436
437impl<T: Clone + Default> MemoryPool<T> {
438 pub fn new(size: usize, capacity: usize) -> Self {
440 let buffers = (0..capacity).map(|_| vec![T::default(); size]).collect();
441
442 Self { buffers, size }
443 }
444
445 pub fn get(&mut self) -> Option<Vec<T>> {
447 self.buffers.pop()
448 }
449
450 pub fn put(&mut self, mut buffer: Vec<T>) {
452 if buffer.len() == self.size {
453 for item in &mut buffer {
455 *item = T::default();
456 }
457 self.buffers.push(buffer);
458 }
459 }
460}
461
462#[cfg(test)]
463mod tests {
464 use super::*;
465 use scirs2_core::ndarray::array;
466 use scirs2_core::random::prelude::*;
467
468 #[test]
469 #[ignore]
470 fn test_optimized_evaluator() {
471 let mut qubo = array![[1.0, -2.0, 0.0], [-2.0, 3.0, -1.0], [0.0, -1.0, 2.0]];
472
473 let evaluator = OptimizedQUBOEvaluator::new(qubo);
474
475 let mut x = array![1, 0, 1];
476 let mut energy = evaluator.evaluate(&x.view());
477
478 assert!((energy - 1.0).abs() < 1e-6);
480
481 let delta = evaluator.delta_energy(&x.view(), 1);
483 assert!((delta - 2.0).abs() < 1e-6); }
485
486 #[test]
487 fn test_optimized_sa() {
488 let mut qubo = array![[0.0, -1.0], [-1.0, 0.0]];
489
490 let sa = OptimizedSA::new(qubo).with_schedule(AnnealingSchedule::Geometric {
491 t0: 1.0,
492 alpha: 0.95,
493 });
494
495 let initial = array![0, 0];
496 let mut rng = thread_rng();
497
498 let (solution, energy) = sa.anneal(initial, 100, &mut rng);
499
500 assert!(
502 (solution == array![0, 1] && (energy - 0.0).abs() < 1e-6)
503 || (solution == array![1, 0] && (energy - 0.0).abs() < 1e-6)
504 || (solution == array![1, 1] && (energy - (-2.0)).abs() < 1e-6)
505 );
506 }
507}