1#![allow(unpredictable_function_pointer_comparisons)]
12
13use crate::error::{QuantRS2Error, QuantRS2Result};
14use scirs2_core::ndarray::{Array1, Array2};
15use scirs2_core::random::prelude::*;
16use scirs2_core::Complex64;
17use std::f64::consts::PI;
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21pub enum ProblemType {
22 QUBO,
24 Ising,
26 MaxCut,
28 GraphColoring,
30 TSP,
32 NumberPartitioning,
34 Custom,
36}
37
38#[derive(Debug, Clone, Copy)]
40pub enum AnnealingSchedule {
41 Linear,
43 Exponential { rate: f64 },
45 Polynomial { power: f64 },
47 Trigonometric,
49 Custom(fn(f64, f64) -> f64),
51}
52
53impl AnnealingSchedule {
54 pub fn evaluate(&self, t: f64, total_time: f64) -> f64 {
56 let s = t / total_time;
57 match self {
58 Self::Linear => s,
59 Self::Exponential { rate } => 1.0 - (-rate * s).exp(),
60 Self::Polynomial { power } => s.powf(*power),
61 Self::Trigonometric => (PI * s / 2.0).sin().powi(2),
62 Self::Custom(func) => func(t, total_time),
63 }
64 }
65
66 pub fn derivative(&self, t: f64, total_time: f64) -> f64 {
68 let dt = 1e-8;
69 let s1 = self.evaluate(t + dt, total_time);
70 let s0 = self.evaluate(t, total_time);
71 (s1 - s0) / dt
72 }
73}
74
75#[derive(Debug, Clone)]
77pub struct QUBOProblem {
78 pub num_vars: usize,
80 pub q_matrix: Array2<f64>,
82 pub linear_terms: Option<Array1<f64>>,
84 pub offset: f64,
86}
87
88impl QUBOProblem {
89 pub fn new(q_matrix: Array2<f64>) -> QuantRS2Result<Self> {
91 let (rows, cols) = q_matrix.dim();
92 if rows != cols {
93 return Err(QuantRS2Error::InvalidInput(
94 "Q matrix must be square".to_string(),
95 ));
96 }
97
98 Ok(Self {
99 num_vars: rows,
100 q_matrix,
101 linear_terms: None,
102 offset: 0.0,
103 })
104 }
105
106 #[must_use]
108 pub fn with_linear_terms(mut self, linear: Array1<f64>) -> QuantRS2Result<Self> {
109 if linear.len() != self.num_vars {
110 return Err(QuantRS2Error::InvalidInput(
111 "Linear terms size mismatch".to_string(),
112 ));
113 }
114 self.linear_terms = Some(linear);
115 Ok(self)
116 }
117
118 #[must_use]
120 pub const fn with_offset(mut self, offset: f64) -> Self {
121 self.offset = offset;
122 self
123 }
124
125 pub fn evaluate(&self, solution: &[u8]) -> QuantRS2Result<f64> {
127 if solution.len() != self.num_vars {
128 return Err(QuantRS2Error::InvalidInput(
129 "Solution size mismatch".to_string(),
130 ));
131 }
132
133 let mut objective = self.offset;
134
135 for i in 0..self.num_vars {
137 for j in 0..self.num_vars {
138 objective += self.q_matrix[[i, j]] * solution[i] as f64 * solution[j] as f64;
139 }
140 }
141
142 if let Some(ref linear) = self.linear_terms {
144 for i in 0..self.num_vars {
145 objective += linear[i] * solution[i] as f64;
146 }
147 }
148
149 Ok(objective)
150 }
151
152 pub fn to_ising(&self) -> IsingProblem {
154 let mut h = Array1::zeros(self.num_vars);
155 let mut j = Array2::zeros((self.num_vars, self.num_vars));
156 let mut offset = self.offset;
157
158 for i in 0..self.num_vars {
160 h[i] = self.q_matrix[[i, i]] / 4.0;
162 offset += self.q_matrix[[i, i]] / 4.0;
163
164 if let Some(ref linear) = self.linear_terms {
165 h[i] += linear[i] / 2.0;
166 offset += linear[i] / 2.0;
167 }
168
169 for k in 0..self.num_vars {
171 if i != k {
172 j[[i, k]] = self.q_matrix[[i, k]] / 4.0;
173 h[i] += self.q_matrix[[i, k]] / 4.0;
174 h[k] += self.q_matrix[[i, k]] / 4.0;
175 offset += self.q_matrix[[i, k]] / 4.0;
176 }
177 }
178 }
179
180 IsingProblem {
181 num_spins: self.num_vars,
182 h_fields: h,
183 j_couplings: j,
184 offset,
185 }
186 }
187}
188
189#[derive(Debug, Clone)]
191pub struct IsingProblem {
192 pub num_spins: usize,
194 pub h_fields: Array1<f64>,
196 pub j_couplings: Array2<f64>,
198 pub offset: f64,
200}
201
202impl IsingProblem {
203 pub fn new(h_fields: Array1<f64>, j_couplings: Array2<f64>) -> QuantRS2Result<Self> {
205 let num_spins = h_fields.len();
206 let (rows, cols) = j_couplings.dim();
207
208 if rows != num_spins || cols != num_spins {
209 return Err(QuantRS2Error::InvalidInput(
210 "Coupling matrix size mismatch".to_string(),
211 ));
212 }
213
214 Ok(Self {
215 num_spins,
216 h_fields,
217 j_couplings,
218 offset: 0.0,
219 })
220 }
221
222 pub fn evaluate(&self, spins: &[i8]) -> QuantRS2Result<f64> {
224 if spins.len() != self.num_spins {
225 return Err(QuantRS2Error::InvalidInput(
226 "Spin configuration size mismatch".to_string(),
227 ));
228 }
229
230 let mut energy = self.offset;
231
232 for i in 0..self.num_spins {
234 energy -= self.h_fields[i] * spins[i] as f64;
235 }
236
237 for i in 0..self.num_spins {
239 for j in i + 1..self.num_spins {
240 energy -= self.j_couplings[[i, j]] * spins[i] as f64 * spins[j] as f64;
241 }
242 }
243
244 Ok(energy)
245 }
246
247 pub fn hamiltonian(&self) -> Array2<Complex64> {
249 let dim = 1 << self.num_spins;
250 let mut hamiltonian = Array2::zeros((dim, dim));
251
252 for state in 0..dim {
253 let spins = self.state_to_spins(state);
254 let energy = self.evaluate(&spins).unwrap_or(0.0);
255 hamiltonian[[state, state]] = Complex64::new(energy, 0.0);
256 }
257
258 hamiltonian
259 }
260
261 fn state_to_spins(&self, state: usize) -> Vec<i8> {
263 (0..self.num_spins)
264 .map(|i| if (state >> i) & 1 == 0 { -1 } else { 1 })
265 .collect()
266 }
267
268 fn spins_to_state(spins: &[i8]) -> usize {
270 spins
271 .iter()
272 .enumerate()
273 .fold(0, |acc, (i, &spin)| acc | usize::from(spin == 1) << i)
274 }
275}
276
277pub struct AdiabaticQuantumComputer {
279 problem_hamiltonian: Array2<Complex64>,
281 initial_hamiltonian: Array2<Complex64>,
283 state: Array1<Complex64>,
285 num_qubits: usize,
287 total_time: f64,
289 current_time: f64,
291 time_step: f64,
293 schedule: AnnealingSchedule,
295}
296
297impl AdiabaticQuantumComputer {
298 pub fn new(
300 problem: &IsingProblem,
301 total_time: f64,
302 time_step: f64,
303 schedule: AnnealingSchedule,
304 ) -> Self {
305 let num_qubits = problem.num_spins;
306 let dim = 1 << num_qubits;
307
308 let problem_hamiltonian = problem.hamiltonian();
310
311 let mut initial_hamiltonian = Array2::zeros((dim, dim));
313 for i in 0..num_qubits {
314 let pauli_x = Self::pauli_x_tensor(i, num_qubits);
315 initial_hamiltonian = initial_hamiltonian + pauli_x;
316 }
317 initial_hamiltonian = initial_hamiltonian.mapv(|x: Complex64| -x);
318
319 let mut state = Array1::zeros(dim);
321 let amplitude = Complex64::new(1.0 / (dim as f64).sqrt(), 0.0);
322 state.fill(amplitude);
323
324 Self {
325 problem_hamiltonian,
326 initial_hamiltonian,
327 state,
328 num_qubits,
329 total_time,
330 current_time: 0.0,
331 time_step,
332 schedule,
333 }
334 }
335
336 fn pauli_x_tensor(target_qubit: usize, num_qubits: usize) -> Array2<Complex64> {
338 let dim = 1 << num_qubits;
339 let mut result = Array2::zeros((dim, dim));
340
341 for state in 0..dim {
342 let flipped_state = state ^ (1 << target_qubit);
344 result[[state, flipped_state]] = Complex64::new(1.0, 0.0);
345 }
346
347 result
348 }
349
350 pub fn current_hamiltonian(&self) -> Array2<Complex64> {
352 let s = self.schedule.evaluate(self.current_time, self.total_time);
353 let one_minus_s = 1.0 - s;
354
355 self.initial_hamiltonian.mapv(|x| x * one_minus_s)
356 + self.problem_hamiltonian.mapv(|x| x * s)
357 }
358
359 pub fn step(&mut self) -> QuantRS2Result<()> {
361 if self.current_time >= self.total_time {
362 return Ok(());
363 }
364
365 let hamiltonian = self.current_hamiltonian();
366
367 let dt = self.time_step.min(self.total_time - self.current_time);
370 let evolution_operator = Self::compute_evolution_operator(&hamiltonian, dt)?;
371
372 self.state = evolution_operator.dot(&self.state);
373 self.current_time += dt;
374
375 let norm = self.state.iter().map(|c| c.norm_sqr()).sum::<f64>().sqrt();
377 if norm > 0.0 {
378 self.state.mapv_inplace(|c| c / norm);
379 }
380
381 Ok(())
382 }
383
384 fn compute_evolution_operator(
386 hamiltonian: &Array2<Complex64>,
387 dt: f64,
388 ) -> QuantRS2Result<Array2<Complex64>> {
389 let dim = hamiltonian.nrows();
390 let mut evolution = Array2::eye(dim);
391
392 let mut term = Array2::eye(dim);
394 let h_dt = hamiltonian.mapv(|h| Complex64::new(0.0, -dt) * h);
395
396 for n in 1..20 {
397 term = term.dot(&h_dt);
399 let coefficient = 1.0 / (1..=n).fold(1.0, |acc, i| acc * i as f64);
400 evolution = evolution + term.mapv(|t| t * coefficient);
401
402 let term_norm = term.iter().map(|c| c.norm_sqr()).sum::<f64>().sqrt();
404 if term_norm * coefficient < 1e-12 {
405 break;
406 }
407 }
408
409 Ok(evolution)
410 }
411
412 pub fn run(&mut self) -> QuantRS2Result<()> {
414 while self.current_time < self.total_time {
415 self.step()?;
416 }
417 Ok(())
418 }
419
420 pub fn measurement_probabilities(&self) -> Vec<f64> {
422 self.state.iter().map(|c| c.norm_sqr()).collect()
423 }
424
425 pub fn measure(&self) -> usize {
427 let mut rng = thread_rng();
428 let probs = self.measurement_probabilities();
429
430 let random_value: f64 = rng.random();
431 let mut cumulative = 0.0;
432
433 for (state, prob) in probs.iter().enumerate() {
434 cumulative += prob;
435 if random_value <= cumulative {
436 return state;
437 }
438 }
439
440 probs.len() - 1 }
442
443 pub fn energy_gap(&self) -> QuantRS2Result<f64> {
445 let hamiltonian = self.current_hamiltonian();
446
447 if self.num_qubits <= 8 {
449 let eigenvalues = Self::compute_eigenvalues(&hamiltonian)?;
450 let ground_energy = eigenvalues[0];
451 let first_excited = eigenvalues[1];
452 Ok(first_excited - ground_energy)
453 } else {
454 Ok(self.estimate_gap(&hamiltonian))
456 }
457 }
458
459 fn compute_eigenvalues(hamiltonian: &Array2<Complex64>) -> QuantRS2Result<Vec<f64>> {
470 let dim = hamiltonian.nrows();
471 if dim == 0 {
472 return Ok(vec![]);
473 }
474 if dim == 1 {
475 return Ok(vec![hamiltonian[[0, 0]].re]);
476 }
477
478 let trace: f64 = (0..dim).map(|i| hamiltonian[[i, i]].re).sum();
480 let trace_mean = trace / dim as f64;
481
482 let frob_sq: f64 = hamiltonian.iter().map(|z| z.norm_sqr()).sum();
484 let frob = frob_sq.sqrt();
485 let sigma = trace_mean - frob / (dim as f64).sqrt();
487
488 let num_wanted = dim.min(dim); let mut eigenvalues: Vec<f64> = Vec::with_capacity(num_wanted);
491 let mut found_vecs: Vec<Vec<Complex64>> = Vec::new();
493
494 for ev_idx in 0..num_wanted {
495 let current_shift = if ev_idx == 0 {
497 sigma
498 } else {
499 eigenvalues[ev_idx - 1] - 0.1 * (1.0 + eigenvalues[ev_idx - 1].abs())
500 };
501
502 let inv_sqrt_dim = 1.0 / (dim as f64).sqrt();
505 let mut v: Vec<Complex64> = (0..dim)
506 .map(|i| {
507 if i % 2 == 0 {
508 Complex64::new(inv_sqrt_dim, 0.0)
509 } else {
510 Complex64::new(-inv_sqrt_dim, 0.0)
511 }
512 })
513 .collect();
514
515 Self::orthogonalise_against(&mut v, &found_vecs);
517 Self::normalize_vec(&mut v);
518
519 let mut rayleigh = Self::rayleigh_quotient(hamiltonian, &v);
520
521 for _iter in 0..150 {
522 let shifted = Self::build_shifted_matrix(hamiltonian, current_shift);
524 let w = match Self::gaussian_elimination_complex(&shifted, &v) {
525 Ok(sol) => sol,
526 Err(_) => {
527 break;
529 }
530 };
531
532 let mut w = w;
534 Self::orthogonalise_against(&mut w, &found_vecs);
535 let norm_w = Self::vec_norm(&w);
536 if norm_w < 1e-14 {
537 break;
538 }
539 for x in &mut w {
540 *x = *x / norm_w;
541 }
542
543 let new_rayleigh = Self::rayleigh_quotient(hamiltonian, &w);
544 let delta = (new_rayleigh - rayleigh).abs();
545 v = w;
546 rayleigh = new_rayleigh;
547
548 if delta < 1e-10 {
549 break;
550 }
551 }
552
553 eigenvalues.push(rayleigh);
554 found_vecs.push(v);
556 }
557
558 eigenvalues.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
559 Ok(eigenvalues)
560 }
561
562 fn build_shifted_matrix(h: &Array2<Complex64>, sigma: f64) -> Array2<Complex64> {
564 let dim = h.nrows();
565 let mut m = h.to_owned();
566 for i in 0..dim {
567 m[[i, i]] = m[[i, i]] - Complex64::new(sigma, 0.0);
568 }
569 m
570 }
571
572 fn rayleigh_quotient(h: &Array2<Complex64>, v: &[Complex64]) -> f64 {
574 let dim = v.len();
575 let mut hv = vec![Complex64::new(0.0, 0.0); dim];
576 for i in 0..dim {
577 for j in 0..dim {
578 hv[i] = hv[i] + h[[i, j]] * v[j];
579 }
580 }
581 let vhv: Complex64 = v
583 .iter()
584 .zip(hv.iter())
585 .map(|(vi, hvi)| vi.conj() * hvi)
586 .sum();
587 let vv: f64 = v.iter().map(|vi| vi.norm_sqr()).sum();
589 if vv < 1e-30 {
590 0.0
591 } else {
592 vhv.re / vv
593 }
594 }
595
596 fn orthogonalise_against(v: &mut Vec<Complex64>, basis: &[Vec<Complex64>]) {
598 for u in basis {
599 let proj: Complex64 = u.iter().zip(v.iter()).map(|(ui, vi)| ui.conj() * vi).sum();
601 for (vi, ui) in v.iter_mut().zip(u.iter()) {
602 *vi = *vi - proj * ui;
603 }
604 }
605 }
606
607 fn vec_norm(v: &[Complex64]) -> f64 {
609 v.iter().map(|z| z.norm_sqr()).sum::<f64>().sqrt()
610 }
611
612 fn normalize_vec(v: &mut Vec<Complex64>) {
614 let n = Self::vec_norm(v);
615 if n > 1e-14 {
616 for x in v.iter_mut() {
617 *x = *x / n;
618 }
619 }
620 }
621
622 fn gaussian_elimination_complex(
625 a: &Array2<Complex64>,
626 b: &[Complex64],
627 ) -> QuantRS2Result<Vec<Complex64>> {
628 let n = a.nrows();
629 let mut aug: Vec<Vec<Complex64>> = (0..n)
631 .map(|i| {
632 let mut row: Vec<Complex64> = (0..n).map(|j| a[[i, j]]).collect();
633 row.push(b[i]);
634 row
635 })
636 .collect();
637
638 for col in 0..n {
639 let max_row = (col..n)
641 .max_by(|&r1, &r2| {
642 aug[r1][col]
643 .norm()
644 .partial_cmp(&aug[r2][col].norm())
645 .unwrap_or(std::cmp::Ordering::Equal)
646 })
647 .unwrap_or(col);
648
649 aug.swap(col, max_row);
650
651 let pivot = aug[col][col];
652 if pivot.norm() < 1e-14 {
653 return Err(QuantRS2Error::ComputationError(
654 "Singular matrix in Gaussian elimination".to_string(),
655 ));
656 }
657
658 for row in (col + 1)..n {
660 let factor = aug[row][col] / pivot;
661 for j in col..=n {
662 let sub = factor * aug[col][j];
663 aug[row][j] = aug[row][j] - sub;
664 }
665 }
666 }
667
668 let mut x = vec![Complex64::new(0.0, 0.0); n];
670 for i in (0..n).rev() {
671 let mut sum = aug[i][n];
672 for j in (i + 1)..n {
673 sum = sum - aug[i][j] * x[j];
674 }
675 if aug[i][i].norm() < 1e-14 {
676 return Err(QuantRS2Error::ComputationError(
677 "Singular matrix in back substitution".to_string(),
678 ));
679 }
680 x[i] = sum / aug[i][i];
681 }
682
683 Ok(x)
684 }
685
686 fn estimate_gap(&self, _hamiltonian: &Array2<Complex64>) -> f64 {
688 let s = self.schedule.evaluate(self.current_time, self.total_time);
690 (PI * s).sin() * 0.1 }
693
694 pub fn adiabatic_condition_satisfied(&self) -> QuantRS2Result<bool> {
696 let gap = self.energy_gap()?;
697 let s_dot = self.schedule.derivative(self.current_time, self.total_time);
698
699 Ok(gap > 10.0 * s_dot.abs())
702 }
703
704 pub fn reset(&mut self) {
706 let dim = 1 << self.num_qubits;
707 let amplitude = Complex64::new(1.0 / (dim as f64).sqrt(), 0.0);
708 self.state.fill(amplitude);
709 self.current_time = 0.0;
710 }
711
712 pub const fn state(&self) -> &Array1<Complex64> {
714 &self.state
715 }
716
717 pub fn annealing_parameter(&self) -> f64 {
719 self.schedule.evaluate(self.current_time, self.total_time)
720 }
721
722 pub fn progress(&self) -> f64 {
724 (self.current_time / self.total_time).min(1.0) * 100.0
725 }
726}
727
728pub struct QuantumAnnealer {
730 computer: AdiabaticQuantumComputer,
731 problem: IsingProblem,
732 best_solution: Option<(Vec<i8>, f64)>,
733 history: Vec<QuantumAnnealingSnapshot>,
734}
735
736#[derive(Debug, Clone)]
738pub struct QuantumAnnealingSnapshot {
739 pub time: f64,
740 pub annealing_parameter: f64,
741 pub energy_gap: f64,
742 pub ground_state_probability: f64,
743 pub expected_energy: f64,
744}
745
746impl QuantumAnnealer {
747 pub fn new(
749 problem: IsingProblem,
750 total_time: f64,
751 time_step: f64,
752 schedule: AnnealingSchedule,
753 ) -> Self {
754 let computer = AdiabaticQuantumComputer::new(&problem, total_time, time_step, schedule);
755
756 Self {
757 computer,
758 problem,
759 best_solution: None,
760 history: Vec::new(),
761 }
762 }
763
764 pub fn optimize(&mut self) -> QuantRS2Result<(Vec<i8>, f64)> {
766 self.computer.reset();
767 self.history.clear();
768
769 self.record_snapshot()?;
771
772 while self.computer.current_time < self.computer.total_time {
774 self.computer.step()?;
775
776 if self.history.len() % 10 == 0 {
778 self.record_snapshot()?;
779 }
780 }
781
782 let measurement = self.computer.measure();
784 let spins = self.state_to_spins(measurement);
785 let energy = self.problem.evaluate(&spins)?;
786
787 self.best_solution = Some((spins.clone(), energy));
788
789 Ok((spins, energy))
790 }
791
792 pub fn optimize_multiple_runs(&mut self, num_runs: usize) -> QuantRS2Result<(Vec<i8>, f64)> {
794 let mut best_energy = f64::INFINITY;
795 let mut best_spins = vec![];
796
797 for _ in 0..num_runs {
798 let (spins, energy) = self.optimize()?;
799 if energy < best_energy {
800 best_energy = energy;
801 best_spins = spins;
802 }
803 }
804
805 Ok((best_spins, best_energy))
806 }
807
808 fn record_snapshot(&mut self) -> QuantRS2Result<()> {
810 let time = self.computer.current_time;
811 let annealing_parameter = self.computer.annealing_parameter();
812 let energy_gap = self.computer.energy_gap()?;
813
814 let probs = self.computer.measurement_probabilities();
816 let ground_state_probability = probs[0]; let mut expected_energy = 0.0;
819 for (state_idx, &prob) in probs.iter().enumerate() {
820 let spins = self.state_to_spins(state_idx);
821 let energy = self.problem.evaluate(&spins).unwrap_or(0.0);
822 expected_energy += prob * energy;
823 }
824
825 self.history.push(QuantumAnnealingSnapshot {
826 time,
827 annealing_parameter,
828 energy_gap,
829 ground_state_probability,
830 expected_energy,
831 });
832
833 Ok(())
834 }
835
836 fn state_to_spins(&self, state: usize) -> Vec<i8> {
838 (0..self.problem.num_spins)
839 .map(|i| if (state >> i) & 1 == 0 { -1 } else { 1 })
840 .collect()
841 }
842
843 pub fn history(&self) -> &[QuantumAnnealingSnapshot] {
845 &self.history
846 }
847
848 pub const fn best_solution(&self) -> Option<&(Vec<i8>, f64)> {
850 self.best_solution.as_ref()
851 }
852
853 pub fn minimum_gap(&self) -> f64 {
855 self.history
856 .iter()
857 .map(|snapshot| snapshot.energy_gap)
858 .fold(f64::INFINITY, f64::min)
859 }
860
861 pub fn success_probability(&self) -> f64 {
863 self.history
864 .last()
865 .map_or(0.0, |snapshot| snapshot.ground_state_probability)
866 }
867}
868
869pub struct ProblemGenerator;
871
872impl ProblemGenerator {
873 pub fn random_qubo(num_vars: usize, density: f64) -> QUBOProblem {
875 let mut rng = thread_rng();
876
877 let mut q_matrix = Array2::zeros((num_vars, num_vars));
878
879 for i in 0..num_vars {
880 for j in i..num_vars {
881 if rng.random::<f64>() < density {
882 let value = rng.random_range(-1.0..=1.0);
883 q_matrix[[i, j]] = value;
884 if i != j {
885 q_matrix[[j, i]] = value; }
887 }
888 }
889 }
890
891 QUBOProblem::new(q_matrix)
892 .expect("Failed to create QUBO problem in random_qubo (matrix should be square)")
893 }
894
895 pub fn max_cut(num_vertices: usize, edge_probability: f64) -> IsingProblem {
897 let mut rng = thread_rng();
898
899 let h_fields = Array1::zeros(num_vertices);
900 let mut j_couplings = Array2::zeros((num_vertices, num_vertices));
901
902 for i in 0..num_vertices {
904 for j in i + 1..num_vertices {
905 if rng.random::<f64>() < edge_probability {
906 let coupling = 1.0; j_couplings[[i, j]] = coupling;
908 j_couplings[[j, i]] = coupling;
909 }
910 }
911 }
912
913 IsingProblem::new(h_fields, j_couplings)
914 .expect("Failed to create Ising problem in max_cut (matrix dimensions should match)")
915 }
916
917 pub fn number_partitioning(numbers: Vec<f64>) -> IsingProblem {
919 let n = numbers.len();
920 let h_fields = Array1::zeros(n);
921 let mut j_couplings = Array2::zeros((n, n));
922
923 for i in 0..n {
925 for j in i + 1..n {
926 let coupling = numbers[i] * numbers[j];
927 j_couplings[[i, j]] = coupling;
928 j_couplings[[j, i]] = coupling;
929 }
930 }
931
932 IsingProblem::new(h_fields, j_couplings).expect("Failed to create Ising problem in number_partitioning (matrix dimensions should match)")
933 }
934}
935
936#[cfg(test)]
937mod tests {
938 use super::*;
939
940 #[test]
941 fn test_annealing_schedules() {
942 let linear = AnnealingSchedule::Linear;
943 assert_eq!(linear.evaluate(0.0, 10.0), 0.0);
944 assert_eq!(linear.evaluate(5.0, 10.0), 0.5);
945 assert_eq!(linear.evaluate(10.0, 10.0), 1.0);
946
947 let exp = AnnealingSchedule::Exponential { rate: 1.0 };
948 assert!(exp.evaluate(0.0, 1.0) < 0.1);
949 assert!(exp.evaluate(1.0, 1.0) > 0.6);
950
951 let poly = AnnealingSchedule::Polynomial { power: 2.0 };
952 assert_eq!(poly.evaluate(10.0, 10.0), 1.0);
953 assert!(poly.evaluate(5.0, 10.0) < linear.evaluate(5.0, 10.0));
954 }
955
956 #[test]
957 fn test_qubo_problem() {
958 let mut q_matrix = Array2::zeros((2, 2));
959 q_matrix[[0, 0]] = 1.0;
960 q_matrix[[1, 1]] = 1.0;
961 q_matrix[[0, 1]] = -2.0;
962 q_matrix[[1, 0]] = -2.0;
963
964 let qubo = QUBOProblem::new(q_matrix).expect("Failed to create QUBO in test_qubo_problem");
965
966 assert_eq!(
973 qubo.evaluate(&[0, 0])
974 .expect("Failed to evaluate [0,0] in test_qubo_problem"),
975 0.0
976 );
977 assert_eq!(
978 qubo.evaluate(&[1, 1])
979 .expect("Failed to evaluate [1,1] in test_qubo_problem"),
980 -2.0
981 );
982 assert_eq!(
983 qubo.evaluate(&[1, 0])
984 .expect("Failed to evaluate [1,0] in test_qubo_problem"),
985 1.0
986 );
987 assert_eq!(
988 qubo.evaluate(&[0, 1])
989 .expect("Failed to evaluate [0,1] in test_qubo_problem"),
990 1.0
991 );
992 }
993
994 #[test]
995 fn test_ising_problem() {
996 let h_fields = Array1::from(vec![0.5, -0.5]);
997 let mut j_couplings = Array2::zeros((2, 2));
998 j_couplings[[0, 1]] = 1.0;
999 j_couplings[[1, 0]] = 1.0;
1000
1001 let ising = IsingProblem::new(h_fields, j_couplings)
1002 .expect("Failed to create Ising problem in test_ising_problem");
1003
1004 assert_eq!(
1008 ising
1009 .evaluate(&[-1, -1])
1010 .expect("Failed to evaluate [-1,-1] in test_ising_problem"),
1011 -1.0
1012 ); assert_eq!(
1014 ising
1015 .evaluate(&[1, 1])
1016 .expect("Failed to evaluate [1,1] in test_ising_problem"),
1017 -1.0
1018 ); assert_eq!(
1020 ising
1021 .evaluate(&[1, -1])
1022 .expect("Failed to evaluate [1,-1] in test_ising_problem"),
1023 0.0
1024 ); assert_eq!(
1026 ising
1027 .evaluate(&[-1, 1])
1028 .expect("Failed to evaluate [-1,1] in test_ising_problem"),
1029 2.0
1030 ); }
1032
1033 #[test]
1034 fn test_qubo_to_ising_conversion() {
1035 let mut q_matrix = Array2::zeros((2, 2));
1036 q_matrix[[0, 1]] = 1.0;
1037 q_matrix[[1, 0]] = 1.0;
1038
1039 let qubo = QUBOProblem::new(q_matrix)
1040 .expect("Failed to create QUBO in test_qubo_to_ising_conversion");
1041 let ising = qubo.to_ising();
1042
1043 assert_eq!(ising.num_spins, 2);
1048 assert!(ising.h_fields.len() == 2);
1049 assert!(ising.j_couplings.dim() == (2, 2));
1050 }
1051
1052 #[test]
1053 fn test_adiabatic_computer_initialization() {
1054 let h_fields = Array1::from(vec![0.0, 0.0]);
1055 let j_couplings = Array2::eye(2);
1056 let ising = IsingProblem::new(h_fields, j_couplings)
1057 .expect("Failed to create Ising problem in test_adiabatic_computer_initialization");
1058
1059 let adiabatic = AdiabaticQuantumComputer::new(
1060 &ising,
1061 10.0, 0.1, AnnealingSchedule::Linear,
1064 );
1065
1066 assert_eq!(adiabatic.num_qubits, 2);
1067 assert_eq!(adiabatic.current_time, 0.0);
1068 assert_eq!(adiabatic.state.len(), 4); let expected_amplitude = 1.0 / 2.0; for amplitude in adiabatic.state.iter() {
1073 assert!((amplitude.norm() - expected_amplitude).abs() < 1e-10);
1074 }
1075 }
1076
1077 #[test]
1078 fn test_adiabatic_evolution_step() {
1079 let h_fields = Array1::zeros(1);
1080 let j_couplings = Array2::zeros((1, 1));
1081 let ising = IsingProblem::new(h_fields, j_couplings)
1082 .expect("Failed to create Ising problem in test_adiabatic_evolution_step");
1083
1084 let mut adiabatic = AdiabaticQuantumComputer::new(
1085 &ising,
1086 1.0, 0.1, AnnealingSchedule::Linear,
1089 );
1090
1091 let initial_time = adiabatic.current_time;
1092 adiabatic
1093 .step()
1094 .expect("Failed to step adiabatic evolution in test_adiabatic_evolution_step");
1095
1096 assert!(adiabatic.current_time > initial_time);
1097 assert!(adiabatic.current_time <= adiabatic.total_time);
1098
1099 let norm = adiabatic.state.iter().map(|c| c.norm_sqr()).sum::<f64>();
1101 assert!((norm - 1.0).abs() < 1e-10);
1102 }
1103
1104 #[test]
1105 fn test_quantum_annealer() {
1106 let problem = ProblemGenerator::max_cut(3, 0.5);
1107 let mut annealer = QuantumAnnealer::new(
1108 problem,
1109 5.0, 0.1, AnnealingSchedule::Linear,
1112 );
1113
1114 let (solution, energy) = annealer
1115 .optimize()
1116 .expect("Failed to optimize in test_quantum_annealer");
1117
1118 assert_eq!(solution.len(), 3);
1119 assert!(solution.iter().all(|&s| s == -1 || s == 1));
1120 assert!(energy.is_finite());
1121
1122 assert!(!annealer.history().is_empty());
1124
1125 assert!(annealer.best_solution().is_some());
1127 }
1128
1129 #[test]
1130 fn test_problem_generators() {
1131 let qubo = ProblemGenerator::random_qubo(3, 0.5);
1132 assert_eq!(qubo.num_vars, 3);
1133 assert_eq!(qubo.q_matrix.dim(), (3, 3));
1134
1135 let max_cut = ProblemGenerator::max_cut(4, 0.6);
1136 assert_eq!(max_cut.num_spins, 4);
1137 assert_eq!(max_cut.h_fields.len(), 4);
1138 assert_eq!(max_cut.j_couplings.dim(), (4, 4));
1139
1140 let numbers = vec![1.0, 2.0, 3.0];
1141 let num_part = ProblemGenerator::number_partitioning(numbers);
1142 assert_eq!(num_part.num_spins, 3);
1143 }
1144
1145 #[test]
1146 fn test_multiple_annealing_runs() {
1147 let problem = ProblemGenerator::random_qubo(2, 1.0).to_ising();
1148 let mut annealer = QuantumAnnealer::new(
1149 problem,
1150 2.0, 0.2, AnnealingSchedule::Linear,
1153 );
1154
1155 let (solution, energy) = annealer
1156 .optimize_multiple_runs(3)
1157 .expect("Failed to optimize multiple runs in test_multiple_annealing_runs");
1158
1159 assert_eq!(solution.len(), 2);
1160 assert!(solution.iter().all(|&s| s == -1 || s == 1));
1161 assert!(energy.is_finite());
1162 }
1163
1164 #[test]
1165 fn test_annealing_parameter_progression() {
1166 let problem = ProblemGenerator::max_cut(2, 1.0);
1167 let mut computer = AdiabaticQuantumComputer::new(
1168 &problem,
1169 1.0, 0.25, AnnealingSchedule::Linear,
1172 );
1173
1174 assert_eq!(computer.annealing_parameter(), 0.0);
1176
1177 computer
1178 .step()
1179 .expect("Failed to step (1) in test_annealing_parameter_progression");
1180 assert!((computer.annealing_parameter() - 0.25).abs() < 1e-10);
1181
1182 computer
1183 .step()
1184 .expect("Failed to step (2) in test_annealing_parameter_progression");
1185 assert!((computer.annealing_parameter() - 0.5).abs() < 1e-10);
1186
1187 computer
1188 .step()
1189 .expect("Failed to step (3) in test_annealing_parameter_progression");
1190 assert!((computer.annealing_parameter() - 0.75).abs() < 1e-10);
1191
1192 computer
1193 .step()
1194 .expect("Failed to step (4) in test_annealing_parameter_progression");
1195 assert!((computer.annealing_parameter() - 1.0).abs() < 1e-10);
1196 }
1197
1198 #[test]
1199 fn test_energy_gap_calculation() {
1200 let h_fields = Array1::from(vec![1.0]);
1201 let j_couplings = Array2::zeros((1, 1));
1202 let ising = IsingProblem::new(h_fields, j_couplings)
1203 .expect("Failed to create Ising problem in test_energy_gap_calculation");
1204
1205 let computer = AdiabaticQuantumComputer::new(&ising, 1.0, 0.1, AnnealingSchedule::Linear);
1206
1207 let gap = computer
1208 .energy_gap()
1209 .expect("Failed to calculate energy gap in test_energy_gap_calculation");
1210 assert!(gap >= 0.0);
1211 }
1212
1213 #[test]
1214 fn test_measurement_probabilities() {
1215 let h_fields = Array1::zeros(1);
1216 let j_couplings = Array2::zeros((1, 1));
1217 let ising = IsingProblem::new(h_fields, j_couplings)
1218 .expect("Failed to create Ising problem in test_measurement_probabilities");
1219
1220 let computer = AdiabaticQuantumComputer::new(&ising, 1.0, 0.1, AnnealingSchedule::Linear);
1221
1222 let probs = computer.measurement_probabilities();
1223 assert_eq!(probs.len(), 2); let total: f64 = probs.iter().sum();
1227 assert!((total - 1.0).abs() < 1e-10);
1228
1229 assert!(probs.iter().all(|&p| p >= 0.0));
1231 }
1232}