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