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