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.gen();
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>> {
461 let dim = hamiltonian.nrows();
463 let mut eigenvalues: Vec<f64> = (0..dim).map(|i| hamiltonian[[i, i]].re).collect();
464 eigenvalues.sort_by(|a, b| {
465 a.partial_cmp(b)
466 .expect("Failed to compare eigenvalues in compute_eigenvalues")
467 });
468 Ok(eigenvalues)
469 }
470
471 fn estimate_gap(&self, _hamiltonian: &Array2<Complex64>) -> f64 {
473 let s = self.schedule.evaluate(self.current_time, self.total_time);
475 (PI * s).sin() * 0.1 }
478
479 pub fn adiabatic_condition_satisfied(&self) -> QuantRS2Result<bool> {
481 let gap = self.energy_gap()?;
482 let s_dot = self.schedule.derivative(self.current_time, self.total_time);
483
484 Ok(gap > 10.0 * s_dot.abs())
487 }
488
489 pub fn reset(&mut self) {
491 let dim = 1 << self.num_qubits;
492 let amplitude = Complex64::new(1.0 / (dim as f64).sqrt(), 0.0);
493 self.state.fill(amplitude);
494 self.current_time = 0.0;
495 }
496
497 pub const fn state(&self) -> &Array1<Complex64> {
499 &self.state
500 }
501
502 pub fn annealing_parameter(&self) -> f64 {
504 self.schedule.evaluate(self.current_time, self.total_time)
505 }
506
507 pub fn progress(&self) -> f64 {
509 (self.current_time / self.total_time).min(1.0) * 100.0
510 }
511}
512
513pub struct QuantumAnnealer {
515 computer: AdiabaticQuantumComputer,
516 problem: IsingProblem,
517 best_solution: Option<(Vec<i8>, f64)>,
518 history: Vec<QuantumAnnealingSnapshot>,
519}
520
521#[derive(Debug, Clone)]
523pub struct QuantumAnnealingSnapshot {
524 pub time: f64,
525 pub annealing_parameter: f64,
526 pub energy_gap: f64,
527 pub ground_state_probability: f64,
528 pub expected_energy: f64,
529}
530
531impl QuantumAnnealer {
532 pub fn new(
534 problem: IsingProblem,
535 total_time: f64,
536 time_step: f64,
537 schedule: AnnealingSchedule,
538 ) -> Self {
539 let computer = AdiabaticQuantumComputer::new(&problem, total_time, time_step, schedule);
540
541 Self {
542 computer,
543 problem,
544 best_solution: None,
545 history: Vec::new(),
546 }
547 }
548
549 pub fn optimize(&mut self) -> QuantRS2Result<(Vec<i8>, f64)> {
551 self.computer.reset();
552 self.history.clear();
553
554 self.record_snapshot()?;
556
557 while self.computer.current_time < self.computer.total_time {
559 self.computer.step()?;
560
561 if self.history.len() % 10 == 0 {
563 self.record_snapshot()?;
564 }
565 }
566
567 let measurement = self.computer.measure();
569 let spins = self.state_to_spins(measurement);
570 let energy = self.problem.evaluate(&spins)?;
571
572 self.best_solution = Some((spins.clone(), energy));
573
574 Ok((spins, energy))
575 }
576
577 pub fn optimize_multiple_runs(&mut self, num_runs: usize) -> QuantRS2Result<(Vec<i8>, f64)> {
579 let mut best_energy = f64::INFINITY;
580 let mut best_spins = vec![];
581
582 for _ in 0..num_runs {
583 let (spins, energy) = self.optimize()?;
584 if energy < best_energy {
585 best_energy = energy;
586 best_spins = spins;
587 }
588 }
589
590 Ok((best_spins, best_energy))
591 }
592
593 fn record_snapshot(&mut self) -> QuantRS2Result<()> {
595 let time = self.computer.current_time;
596 let annealing_parameter = self.computer.annealing_parameter();
597 let energy_gap = self.computer.energy_gap()?;
598
599 let probs = self.computer.measurement_probabilities();
601 let ground_state_probability = probs[0]; let mut expected_energy = 0.0;
604 for (state_idx, &prob) in probs.iter().enumerate() {
605 let spins = self.state_to_spins(state_idx);
606 let energy = self.problem.evaluate(&spins).unwrap_or(0.0);
607 expected_energy += prob * energy;
608 }
609
610 self.history.push(QuantumAnnealingSnapshot {
611 time,
612 annealing_parameter,
613 energy_gap,
614 ground_state_probability,
615 expected_energy,
616 });
617
618 Ok(())
619 }
620
621 fn state_to_spins(&self, state: usize) -> Vec<i8> {
623 (0..self.problem.num_spins)
624 .map(|i| if (state >> i) & 1 == 0 { -1 } else { 1 })
625 .collect()
626 }
627
628 pub fn history(&self) -> &[QuantumAnnealingSnapshot] {
630 &self.history
631 }
632
633 pub const fn best_solution(&self) -> Option<&(Vec<i8>, f64)> {
635 self.best_solution.as_ref()
636 }
637
638 pub fn minimum_gap(&self) -> f64 {
640 self.history
641 .iter()
642 .map(|snapshot| snapshot.energy_gap)
643 .fold(f64::INFINITY, f64::min)
644 }
645
646 pub fn success_probability(&self) -> f64 {
648 self.history
649 .last()
650 .map_or(0.0, |snapshot| snapshot.ground_state_probability)
651 }
652}
653
654pub struct ProblemGenerator;
656
657impl ProblemGenerator {
658 pub fn random_qubo(num_vars: usize, density: f64) -> QUBOProblem {
660 let mut rng = thread_rng();
661
662 let mut q_matrix = Array2::zeros((num_vars, num_vars));
663
664 for i in 0..num_vars {
665 for j in i..num_vars {
666 if rng.gen::<f64>() < density {
667 let value = rng.gen_range(-1.0..=1.0);
668 q_matrix[[i, j]] = value;
669 if i != j {
670 q_matrix[[j, i]] = value; }
672 }
673 }
674 }
675
676 QUBOProblem::new(q_matrix)
677 .expect("Failed to create QUBO problem in random_qubo (matrix should be square)")
678 }
679
680 pub fn max_cut(num_vertices: usize, edge_probability: f64) -> IsingProblem {
682 let mut rng = thread_rng();
683
684 let h_fields = Array1::zeros(num_vertices);
685 let mut j_couplings = Array2::zeros((num_vertices, num_vertices));
686
687 for i in 0..num_vertices {
689 for j in i + 1..num_vertices {
690 if rng.gen::<f64>() < edge_probability {
691 let coupling = 1.0; j_couplings[[i, j]] = coupling;
693 j_couplings[[j, i]] = coupling;
694 }
695 }
696 }
697
698 IsingProblem::new(h_fields, j_couplings)
699 .expect("Failed to create Ising problem in max_cut (matrix dimensions should match)")
700 }
701
702 pub fn number_partitioning(numbers: Vec<f64>) -> IsingProblem {
704 let n = numbers.len();
705 let h_fields = Array1::zeros(n);
706 let mut j_couplings = Array2::zeros((n, n));
707
708 for i in 0..n {
710 for j in i + 1..n {
711 let coupling = numbers[i] * numbers[j];
712 j_couplings[[i, j]] = coupling;
713 j_couplings[[j, i]] = coupling;
714 }
715 }
716
717 IsingProblem::new(h_fields, j_couplings).expect("Failed to create Ising problem in number_partitioning (matrix dimensions should match)")
718 }
719}
720
721#[cfg(test)]
722mod tests {
723 use super::*;
724
725 #[test]
726 fn test_annealing_schedules() {
727 let linear = AnnealingSchedule::Linear;
728 assert_eq!(linear.evaluate(0.0, 10.0), 0.0);
729 assert_eq!(linear.evaluate(5.0, 10.0), 0.5);
730 assert_eq!(linear.evaluate(10.0, 10.0), 1.0);
731
732 let exp = AnnealingSchedule::Exponential { rate: 1.0 };
733 assert!(exp.evaluate(0.0, 1.0) < 0.1);
734 assert!(exp.evaluate(1.0, 1.0) > 0.6);
735
736 let poly = AnnealingSchedule::Polynomial { power: 2.0 };
737 assert_eq!(poly.evaluate(10.0, 10.0), 1.0);
738 assert!(poly.evaluate(5.0, 10.0) < linear.evaluate(5.0, 10.0));
739 }
740
741 #[test]
742 fn test_qubo_problem() {
743 let mut q_matrix = Array2::zeros((2, 2));
744 q_matrix[[0, 0]] = 1.0;
745 q_matrix[[1, 1]] = 1.0;
746 q_matrix[[0, 1]] = -2.0;
747 q_matrix[[1, 0]] = -2.0;
748
749 let qubo = QUBOProblem::new(q_matrix).expect("Failed to create QUBO in test_qubo_problem");
750
751 assert_eq!(
758 qubo.evaluate(&[0, 0])
759 .expect("Failed to evaluate [0,0] in test_qubo_problem"),
760 0.0
761 );
762 assert_eq!(
763 qubo.evaluate(&[1, 1])
764 .expect("Failed to evaluate [1,1] in test_qubo_problem"),
765 -2.0
766 );
767 assert_eq!(
768 qubo.evaluate(&[1, 0])
769 .expect("Failed to evaluate [1,0] in test_qubo_problem"),
770 1.0
771 );
772 assert_eq!(
773 qubo.evaluate(&[0, 1])
774 .expect("Failed to evaluate [0,1] in test_qubo_problem"),
775 1.0
776 );
777 }
778
779 #[test]
780 fn test_ising_problem() {
781 let h_fields = Array1::from(vec![0.5, -0.5]);
782 let mut j_couplings = Array2::zeros((2, 2));
783 j_couplings[[0, 1]] = 1.0;
784 j_couplings[[1, 0]] = 1.0;
785
786 let ising = IsingProblem::new(h_fields, j_couplings)
787 .expect("Failed to create Ising problem in test_ising_problem");
788
789 assert_eq!(
793 ising
794 .evaluate(&[-1, -1])
795 .expect("Failed to evaluate [-1,-1] in test_ising_problem"),
796 -1.0
797 ); assert_eq!(
799 ising
800 .evaluate(&[1, 1])
801 .expect("Failed to evaluate [1,1] in test_ising_problem"),
802 -1.0
803 ); assert_eq!(
805 ising
806 .evaluate(&[1, -1])
807 .expect("Failed to evaluate [1,-1] in test_ising_problem"),
808 0.0
809 ); assert_eq!(
811 ising
812 .evaluate(&[-1, 1])
813 .expect("Failed to evaluate [-1,1] in test_ising_problem"),
814 2.0
815 ); }
817
818 #[test]
819 fn test_qubo_to_ising_conversion() {
820 let mut q_matrix = Array2::zeros((2, 2));
821 q_matrix[[0, 1]] = 1.0;
822 q_matrix[[1, 0]] = 1.0;
823
824 let qubo = QUBOProblem::new(q_matrix)
825 .expect("Failed to create QUBO in test_qubo_to_ising_conversion");
826 let ising = qubo.to_ising();
827
828 assert_eq!(ising.num_spins, 2);
833 assert!(ising.h_fields.len() == 2);
834 assert!(ising.j_couplings.dim() == (2, 2));
835 }
836
837 #[test]
838 fn test_adiabatic_computer_initialization() {
839 let h_fields = Array1::from(vec![0.0, 0.0]);
840 let j_couplings = Array2::eye(2);
841 let ising = IsingProblem::new(h_fields, j_couplings)
842 .expect("Failed to create Ising problem in test_adiabatic_computer_initialization");
843
844 let adiabatic = AdiabaticQuantumComputer::new(
845 &ising,
846 10.0, 0.1, AnnealingSchedule::Linear,
849 );
850
851 assert_eq!(adiabatic.num_qubits, 2);
852 assert_eq!(adiabatic.current_time, 0.0);
853 assert_eq!(adiabatic.state.len(), 4); let expected_amplitude = 1.0 / 2.0; for amplitude in adiabatic.state.iter() {
858 assert!((amplitude.norm() - expected_amplitude).abs() < 1e-10);
859 }
860 }
861
862 #[test]
863 fn test_adiabatic_evolution_step() {
864 let h_fields = Array1::zeros(1);
865 let j_couplings = Array2::zeros((1, 1));
866 let ising = IsingProblem::new(h_fields, j_couplings)
867 .expect("Failed to create Ising problem in test_adiabatic_evolution_step");
868
869 let mut adiabatic = AdiabaticQuantumComputer::new(
870 &ising,
871 1.0, 0.1, AnnealingSchedule::Linear,
874 );
875
876 let initial_time = adiabatic.current_time;
877 adiabatic
878 .step()
879 .expect("Failed to step adiabatic evolution in test_adiabatic_evolution_step");
880
881 assert!(adiabatic.current_time > initial_time);
882 assert!(adiabatic.current_time <= adiabatic.total_time);
883
884 let norm = adiabatic.state.iter().map(|c| c.norm_sqr()).sum::<f64>();
886 assert!((norm - 1.0).abs() < 1e-10);
887 }
888
889 #[test]
890 fn test_quantum_annealer() {
891 let problem = ProblemGenerator::max_cut(3, 0.5);
892 let mut annealer = QuantumAnnealer::new(
893 problem,
894 5.0, 0.1, AnnealingSchedule::Linear,
897 );
898
899 let (solution, energy) = annealer
900 .optimize()
901 .expect("Failed to optimize in test_quantum_annealer");
902
903 assert_eq!(solution.len(), 3);
904 assert!(solution.iter().all(|&s| s == -1 || s == 1));
905 assert!(energy.is_finite());
906
907 assert!(!annealer.history().is_empty());
909
910 assert!(annealer.best_solution().is_some());
912 }
913
914 #[test]
915 fn test_problem_generators() {
916 let qubo = ProblemGenerator::random_qubo(3, 0.5);
917 assert_eq!(qubo.num_vars, 3);
918 assert_eq!(qubo.q_matrix.dim(), (3, 3));
919
920 let max_cut = ProblemGenerator::max_cut(4, 0.6);
921 assert_eq!(max_cut.num_spins, 4);
922 assert_eq!(max_cut.h_fields.len(), 4);
923 assert_eq!(max_cut.j_couplings.dim(), (4, 4));
924
925 let numbers = vec![1.0, 2.0, 3.0];
926 let num_part = ProblemGenerator::number_partitioning(numbers);
927 assert_eq!(num_part.num_spins, 3);
928 }
929
930 #[test]
931 fn test_multiple_annealing_runs() {
932 let problem = ProblemGenerator::random_qubo(2, 1.0).to_ising();
933 let mut annealer = QuantumAnnealer::new(
934 problem,
935 2.0, 0.2, AnnealingSchedule::Linear,
938 );
939
940 let (solution, energy) = annealer
941 .optimize_multiple_runs(3)
942 .expect("Failed to optimize multiple runs in test_multiple_annealing_runs");
943
944 assert_eq!(solution.len(), 2);
945 assert!(solution.iter().all(|&s| s == -1 || s == 1));
946 assert!(energy.is_finite());
947 }
948
949 #[test]
950 fn test_annealing_parameter_progression() {
951 let problem = ProblemGenerator::max_cut(2, 1.0);
952 let mut computer = AdiabaticQuantumComputer::new(
953 &problem,
954 1.0, 0.25, AnnealingSchedule::Linear,
957 );
958
959 assert_eq!(computer.annealing_parameter(), 0.0);
961
962 computer
963 .step()
964 .expect("Failed to step (1) in test_annealing_parameter_progression");
965 assert!((computer.annealing_parameter() - 0.25).abs() < 1e-10);
966
967 computer
968 .step()
969 .expect("Failed to step (2) in test_annealing_parameter_progression");
970 assert!((computer.annealing_parameter() - 0.5).abs() < 1e-10);
971
972 computer
973 .step()
974 .expect("Failed to step (3) in test_annealing_parameter_progression");
975 assert!((computer.annealing_parameter() - 0.75).abs() < 1e-10);
976
977 computer
978 .step()
979 .expect("Failed to step (4) in test_annealing_parameter_progression");
980 assert!((computer.annealing_parameter() - 1.0).abs() < 1e-10);
981 }
982
983 #[test]
984 fn test_energy_gap_calculation() {
985 let h_fields = Array1::from(vec![1.0]);
986 let j_couplings = Array2::zeros((1, 1));
987 let ising = IsingProblem::new(h_fields, j_couplings)
988 .expect("Failed to create Ising problem in test_energy_gap_calculation");
989
990 let computer = AdiabaticQuantumComputer::new(&ising, 1.0, 0.1, AnnealingSchedule::Linear);
991
992 let gap = computer
993 .energy_gap()
994 .expect("Failed to calculate energy gap in test_energy_gap_calculation");
995 assert!(gap >= 0.0);
996 }
997
998 #[test]
999 fn test_measurement_probabilities() {
1000 let h_fields = Array1::zeros(1);
1001 let j_couplings = Array2::zeros((1, 1));
1002 let ising = IsingProblem::new(h_fields, j_couplings)
1003 .expect("Failed to create Ising problem in test_measurement_probabilities");
1004
1005 let computer = AdiabaticQuantumComputer::new(&ising, 1.0, 0.1, AnnealingSchedule::Linear);
1006
1007 let probs = computer.measurement_probabilities();
1008 assert_eq!(probs.len(), 2); let total: f64 = probs.iter().sum();
1012 assert!((total - 1.0).abs() < 1e-10);
1013
1014 assert!(probs.iter().all(|&p| p >= 0.0));
1016 }
1017}