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)]
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 AnnealingSchedule::Linear => s,
59 AnnealingSchedule::Exponential { rate } => 1.0 - (-rate * s).exp(),
60 AnnealingSchedule::Polynomial { power } => s.powf(*power),
61 AnnealingSchedule::Trigonometric => (PI * s / 2.0).sin().powi(2),
62 AnnealingSchedule::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 pub fn with_linear_terms(mut self, linear: Array1<f64>) -> QuantRS2Result<Self> {
108 if linear.len() != self.num_vars {
109 return Err(QuantRS2Error::InvalidInput(
110 "Linear terms size mismatch".to_string(),
111 ));
112 }
113 self.linear_terms = Some(linear);
114 Ok(self)
115 }
116
117 pub fn with_offset(mut self, offset: f64) -> Self {
119 self.offset = offset;
120 self
121 }
122
123 pub fn evaluate(&self, solution: &[u8]) -> QuantRS2Result<f64> {
125 if solution.len() != self.num_vars {
126 return Err(QuantRS2Error::InvalidInput(
127 "Solution size mismatch".to_string(),
128 ));
129 }
130
131 let mut objective = self.offset;
132
133 for i in 0..self.num_vars {
135 for j in 0..self.num_vars {
136 objective += self.q_matrix[[i, j]] * solution[i] as f64 * solution[j] as f64;
137 }
138 }
139
140 if let Some(ref linear) = self.linear_terms {
142 for i in 0..self.num_vars {
143 objective += linear[i] * solution[i] as f64;
144 }
145 }
146
147 Ok(objective)
148 }
149
150 pub fn to_ising(&self) -> IsingProblem {
152 let mut h = Array1::zeros(self.num_vars);
153 let mut j = Array2::zeros((self.num_vars, self.num_vars));
154 let mut offset = self.offset;
155
156 for i in 0..self.num_vars {
158 h[i] = self.q_matrix[[i, i]] / 4.0;
160 offset += self.q_matrix[[i, i]] / 4.0;
161
162 if let Some(ref linear) = self.linear_terms {
163 h[i] += linear[i] / 2.0;
164 offset += linear[i] / 2.0;
165 }
166
167 for k in 0..self.num_vars {
169 if i != k {
170 j[[i, k]] = self.q_matrix[[i, k]] / 4.0;
171 h[i] += self.q_matrix[[i, k]] / 4.0;
172 h[k] += self.q_matrix[[i, k]] / 4.0;
173 offset += self.q_matrix[[i, k]] / 4.0;
174 }
175 }
176 }
177
178 IsingProblem {
179 num_spins: self.num_vars,
180 h_fields: h,
181 j_couplings: j,
182 offset,
183 }
184 }
185}
186
187#[derive(Debug, Clone)]
189pub struct IsingProblem {
190 pub num_spins: usize,
192 pub h_fields: Array1<f64>,
194 pub j_couplings: Array2<f64>,
196 pub offset: f64,
198}
199
200impl IsingProblem {
201 pub fn new(h_fields: Array1<f64>, j_couplings: Array2<f64>) -> QuantRS2Result<Self> {
203 let num_spins = h_fields.len();
204 let (rows, cols) = j_couplings.dim();
205
206 if rows != num_spins || cols != num_spins {
207 return Err(QuantRS2Error::InvalidInput(
208 "Coupling matrix size mismatch".to_string(),
209 ));
210 }
211
212 Ok(Self {
213 num_spins,
214 h_fields,
215 j_couplings,
216 offset: 0.0,
217 })
218 }
219
220 pub fn evaluate(&self, spins: &[i8]) -> QuantRS2Result<f64> {
222 if spins.len() != self.num_spins {
223 return Err(QuantRS2Error::InvalidInput(
224 "Spin configuration size mismatch".to_string(),
225 ));
226 }
227
228 let mut energy = self.offset;
229
230 for i in 0..self.num_spins {
232 energy -= self.h_fields[i] * spins[i] as f64;
233 }
234
235 for i in 0..self.num_spins {
237 for j in i + 1..self.num_spins {
238 energy -= self.j_couplings[[i, j]] * spins[i] as f64 * spins[j] as f64;
239 }
240 }
241
242 Ok(energy)
243 }
244
245 pub fn hamiltonian(&self) -> Array2<Complex64> {
247 let dim = 1 << self.num_spins;
248 let mut hamiltonian = Array2::zeros((dim, dim));
249
250 for state in 0..dim {
251 let spins = self.state_to_spins(state);
252 let energy = self.evaluate(&spins).unwrap_or(0.0);
253 hamiltonian[[state, state]] = Complex64::new(energy, 0.0);
254 }
255
256 hamiltonian
257 }
258
259 fn state_to_spins(&self, state: usize) -> Vec<i8> {
261 (0..self.num_spins)
262 .map(|i| if (state >> i) & 1 == 0 { -1 } else { 1 })
263 .collect()
264 }
265
266 fn spins_to_state(&self, spins: &[i8]) -> usize {
268 spins.iter().enumerate().fold(0, |acc, (i, &spin)| {
269 acc | (if spin == 1 { 1 } else { 0 }) << i
270 })
271 }
272}
273
274pub struct AdiabaticQuantumComputer {
276 problem_hamiltonian: Array2<Complex64>,
278 initial_hamiltonian: Array2<Complex64>,
280 state: Array1<Complex64>,
282 num_qubits: usize,
284 total_time: f64,
286 current_time: f64,
288 time_step: f64,
290 schedule: AnnealingSchedule,
292}
293
294impl AdiabaticQuantumComputer {
295 pub fn new(
297 problem: &IsingProblem,
298 total_time: f64,
299 time_step: f64,
300 schedule: AnnealingSchedule,
301 ) -> Self {
302 let num_qubits = problem.num_spins;
303 let dim = 1 << num_qubits;
304
305 let problem_hamiltonian = problem.hamiltonian();
307
308 let mut initial_hamiltonian = Array2::zeros((dim, dim));
310 for i in 0..num_qubits {
311 let pauli_x = Self::pauli_x_tensor(i, num_qubits);
312 initial_hamiltonian = initial_hamiltonian + pauli_x;
313 }
314 initial_hamiltonian = initial_hamiltonian.mapv(|x: Complex64| -x);
315
316 let mut state = Array1::zeros(dim);
318 let amplitude = Complex64::new(1.0 / (dim as f64).sqrt(), 0.0);
319 state.fill(amplitude);
320
321 Self {
322 problem_hamiltonian,
323 initial_hamiltonian,
324 state,
325 num_qubits,
326 total_time,
327 current_time: 0.0,
328 time_step,
329 schedule,
330 }
331 }
332
333 fn pauli_x_tensor(target_qubit: usize, num_qubits: usize) -> Array2<Complex64> {
335 let dim = 1 << num_qubits;
336 let mut result = Array2::zeros((dim, dim));
337
338 for state in 0..dim {
339 let flipped_state = state ^ (1 << target_qubit);
341 result[[state, flipped_state]] = Complex64::new(1.0, 0.0);
342 }
343
344 result
345 }
346
347 pub fn current_hamiltonian(&self) -> Array2<Complex64> {
349 let s = self.schedule.evaluate(self.current_time, self.total_time);
350 let one_minus_s = 1.0 - s;
351
352 self.initial_hamiltonian.mapv(|x| x * one_minus_s)
353 + self.problem_hamiltonian.mapv(|x| x * s)
354 }
355
356 pub fn step(&mut self) -> QuantRS2Result<()> {
358 if self.current_time >= self.total_time {
359 return Ok(());
360 }
361
362 let hamiltonian = self.current_hamiltonian();
363
364 let dt = self.time_step.min(self.total_time - self.current_time);
367 let evolution_operator = self.compute_evolution_operator(&hamiltonian, dt)?;
368
369 self.state = evolution_operator.dot(&self.state);
370 self.current_time += dt;
371
372 let norm = self.state.iter().map(|c| c.norm_sqr()).sum::<f64>().sqrt();
374 if norm > 0.0 {
375 self.state.mapv_inplace(|c| c / norm);
376 }
377
378 Ok(())
379 }
380
381 fn compute_evolution_operator(
383 &self,
384 hamiltonian: &Array2<Complex64>,
385 dt: f64,
386 ) -> QuantRS2Result<Array2<Complex64>> {
387 let dim = hamiltonian.nrows();
388 let mut evolution = Array2::eye(dim);
389
390 let mut term = Array2::eye(dim);
392 let h_dt = hamiltonian.mapv(|h| Complex64::new(0.0, -dt) * h);
393
394 for n in 1..20 {
395 term = term.dot(&h_dt);
397 let coefficient = 1.0 / (1..=n).fold(1.0, |acc, i| acc * i as f64);
398 evolution = evolution + term.mapv(|t| t * coefficient);
399
400 let term_norm = term.iter().map(|c| c.norm_sqr()).sum::<f64>().sqrt();
402 if term_norm * coefficient < 1e-12 {
403 break;
404 }
405 }
406
407 Ok(evolution)
408 }
409
410 pub fn run(&mut self) -> QuantRS2Result<()> {
412 while self.current_time < self.total_time {
413 self.step()?;
414 }
415 Ok(())
416 }
417
418 pub fn measurement_probabilities(&self) -> Vec<f64> {
420 self.state.iter().map(|c| c.norm_sqr()).collect()
421 }
422
423 pub fn measure(&self) -> usize {
425 let mut rng = thread_rng();
426 let probs = self.measurement_probabilities();
427
428 let random_value: f64 = rng.gen();
429 let mut cumulative = 0.0;
430
431 for (state, prob) in probs.iter().enumerate() {
432 cumulative += prob;
433 if random_value <= cumulative {
434 return state;
435 }
436 }
437
438 probs.len() - 1 }
440
441 pub fn energy_gap(&self) -> QuantRS2Result<f64> {
443 let hamiltonian = self.current_hamiltonian();
444
445 if self.num_qubits <= 8 {
447 let eigenvalues = self.compute_eigenvalues(&hamiltonian)?;
448 let ground_energy = eigenvalues[0];
449 let first_excited = eigenvalues[1];
450 Ok(first_excited - ground_energy)
451 } else {
452 Ok(self.estimate_gap(&hamiltonian))
454 }
455 }
456
457 fn compute_eigenvalues(&self, hamiltonian: &Array2<Complex64>) -> QuantRS2Result<Vec<f64>> {
459 let dim = hamiltonian.nrows();
461 let mut eigenvalues: Vec<f64> = (0..dim).map(|i| hamiltonian[[i, i]].re).collect();
462 eigenvalues.sort_by(|a, b| {
463 a.partial_cmp(b)
464 .expect("Failed to compare eigenvalues in compute_eigenvalues")
465 });
466 Ok(eigenvalues)
467 }
468
469 fn estimate_gap(&self, _hamiltonian: &Array2<Complex64>) -> f64 {
471 let s = self.schedule.evaluate(self.current_time, self.total_time);
473 (PI * s).sin() * 0.1 }
476
477 pub fn adiabatic_condition_satisfied(&self) -> QuantRS2Result<bool> {
479 let gap = self.energy_gap()?;
480 let s_dot = self.schedule.derivative(self.current_time, self.total_time);
481
482 Ok(gap > 10.0 * s_dot.abs())
485 }
486
487 pub fn reset(&mut self) {
489 let dim = 1 << self.num_qubits;
490 let amplitude = Complex64::new(1.0 / (dim as f64).sqrt(), 0.0);
491 self.state.fill(amplitude);
492 self.current_time = 0.0;
493 }
494
495 pub fn state(&self) -> &Array1<Complex64> {
497 &self.state
498 }
499
500 pub fn annealing_parameter(&self) -> f64 {
502 self.schedule.evaluate(self.current_time, self.total_time)
503 }
504
505 pub fn progress(&self) -> f64 {
507 (self.current_time / self.total_time).min(1.0) * 100.0
508 }
509}
510
511pub struct QuantumAnnealer {
513 computer: AdiabaticQuantumComputer,
514 problem: IsingProblem,
515 best_solution: Option<(Vec<i8>, f64)>,
516 history: Vec<QuantumAnnealingSnapshot>,
517}
518
519#[derive(Debug, Clone)]
521pub struct QuantumAnnealingSnapshot {
522 pub time: f64,
523 pub annealing_parameter: f64,
524 pub energy_gap: f64,
525 pub ground_state_probability: f64,
526 pub expected_energy: f64,
527}
528
529impl QuantumAnnealer {
530 pub fn new(
532 problem: IsingProblem,
533 total_time: f64,
534 time_step: f64,
535 schedule: AnnealingSchedule,
536 ) -> Self {
537 let computer = AdiabaticQuantumComputer::new(&problem, total_time, time_step, schedule);
538
539 Self {
540 computer,
541 problem,
542 best_solution: None,
543 history: Vec::new(),
544 }
545 }
546
547 pub fn optimize(&mut self) -> QuantRS2Result<(Vec<i8>, f64)> {
549 self.computer.reset();
550 self.history.clear();
551
552 self.record_snapshot()?;
554
555 while self.computer.current_time < self.computer.total_time {
557 self.computer.step()?;
558
559 if self.history.len() % 10 == 0 {
561 self.record_snapshot()?;
562 }
563 }
564
565 let measurement = self.computer.measure();
567 let spins = self.state_to_spins(measurement);
568 let energy = self.problem.evaluate(&spins)?;
569
570 self.best_solution = Some((spins.clone(), energy));
571
572 Ok((spins, energy))
573 }
574
575 pub fn optimize_multiple_runs(&mut self, num_runs: usize) -> QuantRS2Result<(Vec<i8>, f64)> {
577 let mut best_energy = f64::INFINITY;
578 let mut best_spins = vec![];
579
580 for _ in 0..num_runs {
581 let (spins, energy) = self.optimize()?;
582 if energy < best_energy {
583 best_energy = energy;
584 best_spins = spins;
585 }
586 }
587
588 Ok((best_spins, best_energy))
589 }
590
591 fn record_snapshot(&mut self) -> QuantRS2Result<()> {
593 let time = self.computer.current_time;
594 let annealing_parameter = self.computer.annealing_parameter();
595 let energy_gap = self.computer.energy_gap()?;
596
597 let probs = self.computer.measurement_probabilities();
599 let ground_state_probability = probs[0]; let mut expected_energy = 0.0;
602 for (state_idx, &prob) in probs.iter().enumerate() {
603 let spins = self.state_to_spins(state_idx);
604 let energy = self.problem.evaluate(&spins).unwrap_or(0.0);
605 expected_energy += prob * energy;
606 }
607
608 self.history.push(QuantumAnnealingSnapshot {
609 time,
610 annealing_parameter,
611 energy_gap,
612 ground_state_probability,
613 expected_energy,
614 });
615
616 Ok(())
617 }
618
619 fn state_to_spins(&self, state: usize) -> Vec<i8> {
621 (0..self.problem.num_spins)
622 .map(|i| if (state >> i) & 1 == 0 { -1 } else { 1 })
623 .collect()
624 }
625
626 pub fn history(&self) -> &[QuantumAnnealingSnapshot] {
628 &self.history
629 }
630
631 pub fn best_solution(&self) -> Option<&(Vec<i8>, f64)> {
633 self.best_solution.as_ref()
634 }
635
636 pub fn minimum_gap(&self) -> f64 {
638 self.history
639 .iter()
640 .map(|snapshot| snapshot.energy_gap)
641 .fold(f64::INFINITY, f64::min)
642 }
643
644 pub fn success_probability(&self) -> f64 {
646 self.history
647 .last()
648 .map(|snapshot| snapshot.ground_state_probability)
649 .unwrap_or(0.0)
650 }
651}
652
653pub struct ProblemGenerator;
655
656impl ProblemGenerator {
657 pub fn random_qubo(num_vars: usize, density: f64) -> QUBOProblem {
659 let mut rng = thread_rng();
660
661 let mut q_matrix = Array2::zeros((num_vars, num_vars));
662
663 for i in 0..num_vars {
664 for j in i..num_vars {
665 if rng.gen::<f64>() < density {
666 let value = rng.gen_range(-1.0..=1.0);
667 q_matrix[[i, j]] = value;
668 if i != j {
669 q_matrix[[j, i]] = value; }
671 }
672 }
673 }
674
675 QUBOProblem::new(q_matrix)
676 .expect("Failed to create QUBO problem in random_qubo (matrix should be square)")
677 }
678
679 pub fn max_cut(num_vertices: usize, edge_probability: f64) -> IsingProblem {
681 let mut rng = thread_rng();
682
683 let h_fields = Array1::zeros(num_vertices);
684 let mut j_couplings = Array2::zeros((num_vertices, num_vertices));
685
686 for i in 0..num_vertices {
688 for j in i + 1..num_vertices {
689 if rng.gen::<f64>() < edge_probability {
690 let coupling = 1.0; j_couplings[[i, j]] = coupling;
692 j_couplings[[j, i]] = coupling;
693 }
694 }
695 }
696
697 IsingProblem::new(h_fields, j_couplings)
698 .expect("Failed to create Ising problem in max_cut (matrix dimensions should match)")
699 }
700
701 pub fn number_partitioning(numbers: Vec<f64>) -> IsingProblem {
703 let n = numbers.len();
704 let h_fields = Array1::zeros(n);
705 let mut j_couplings = Array2::zeros((n, n));
706
707 for i in 0..n {
709 for j in i + 1..n {
710 let coupling = numbers[i] * numbers[j];
711 j_couplings[[i, j]] = coupling;
712 j_couplings[[j, i]] = coupling;
713 }
714 }
715
716 IsingProblem::new(h_fields, j_couplings).expect("Failed to create Ising problem in number_partitioning (matrix dimensions should match)")
717 }
718}
719
720#[cfg(test)]
721mod tests {
722 use super::*;
723
724 #[test]
725 fn test_annealing_schedules() {
726 let linear = AnnealingSchedule::Linear;
727 assert_eq!(linear.evaluate(0.0, 10.0), 0.0);
728 assert_eq!(linear.evaluate(5.0, 10.0), 0.5);
729 assert_eq!(linear.evaluate(10.0, 10.0), 1.0);
730
731 let exp = AnnealingSchedule::Exponential { rate: 1.0 };
732 assert!(exp.evaluate(0.0, 1.0) < 0.1);
733 assert!(exp.evaluate(1.0, 1.0) > 0.6);
734
735 let poly = AnnealingSchedule::Polynomial { power: 2.0 };
736 assert_eq!(poly.evaluate(10.0, 10.0), 1.0);
737 assert!(poly.evaluate(5.0, 10.0) < linear.evaluate(5.0, 10.0));
738 }
739
740 #[test]
741 fn test_qubo_problem() {
742 let mut q_matrix = Array2::zeros((2, 2));
743 q_matrix[[0, 0]] = 1.0;
744 q_matrix[[1, 1]] = 1.0;
745 q_matrix[[0, 1]] = -2.0;
746 q_matrix[[1, 0]] = -2.0;
747
748 let qubo = QUBOProblem::new(q_matrix).expect("Failed to create QUBO in test_qubo_problem");
749
750 assert_eq!(
757 qubo.evaluate(&[0, 0])
758 .expect("Failed to evaluate [0,0] in test_qubo_problem"),
759 0.0
760 );
761 assert_eq!(
762 qubo.evaluate(&[1, 1])
763 .expect("Failed to evaluate [1,1] in test_qubo_problem"),
764 -2.0
765 );
766 assert_eq!(
767 qubo.evaluate(&[1, 0])
768 .expect("Failed to evaluate [1,0] in test_qubo_problem"),
769 1.0
770 );
771 assert_eq!(
772 qubo.evaluate(&[0, 1])
773 .expect("Failed to evaluate [0,1] in test_qubo_problem"),
774 1.0
775 );
776 }
777
778 #[test]
779 fn test_ising_problem() {
780 let h_fields = Array1::from(vec![0.5, -0.5]);
781 let mut j_couplings = Array2::zeros((2, 2));
782 j_couplings[[0, 1]] = 1.0;
783 j_couplings[[1, 0]] = 1.0;
784
785 let ising = IsingProblem::new(h_fields, j_couplings)
786 .expect("Failed to create Ising problem in test_ising_problem");
787
788 assert_eq!(
792 ising
793 .evaluate(&[-1, -1])
794 .expect("Failed to evaluate [-1,-1] in test_ising_problem"),
795 -1.0
796 ); assert_eq!(
798 ising
799 .evaluate(&[1, 1])
800 .expect("Failed to evaluate [1,1] in test_ising_problem"),
801 -1.0
802 ); assert_eq!(
804 ising
805 .evaluate(&[1, -1])
806 .expect("Failed to evaluate [1,-1] in test_ising_problem"),
807 0.0
808 ); assert_eq!(
810 ising
811 .evaluate(&[-1, 1])
812 .expect("Failed to evaluate [-1,1] in test_ising_problem"),
813 2.0
814 ); }
816
817 #[test]
818 fn test_qubo_to_ising_conversion() {
819 let mut q_matrix = Array2::zeros((2, 2));
820 q_matrix[[0, 1]] = 1.0;
821 q_matrix[[1, 0]] = 1.0;
822
823 let qubo = QUBOProblem::new(q_matrix)
824 .expect("Failed to create QUBO in test_qubo_to_ising_conversion");
825 let ising = qubo.to_ising();
826
827 assert_eq!(ising.num_spins, 2);
832 assert!(ising.h_fields.len() == 2);
833 assert!(ising.j_couplings.dim() == (2, 2));
834 }
835
836 #[test]
837 fn test_adiabatic_computer_initialization() {
838 let h_fields = Array1::from(vec![0.0, 0.0]);
839 let j_couplings = Array2::eye(2);
840 let ising = IsingProblem::new(h_fields, j_couplings)
841 .expect("Failed to create Ising problem in test_adiabatic_computer_initialization");
842
843 let adiabatic = AdiabaticQuantumComputer::new(
844 &ising,
845 10.0, 0.1, AnnealingSchedule::Linear,
848 );
849
850 assert_eq!(adiabatic.num_qubits, 2);
851 assert_eq!(adiabatic.current_time, 0.0);
852 assert_eq!(adiabatic.state.len(), 4); let expected_amplitude = 1.0 / 2.0; for amplitude in adiabatic.state.iter() {
857 assert!((amplitude.norm() - expected_amplitude).abs() < 1e-10);
858 }
859 }
860
861 #[test]
862 fn test_adiabatic_evolution_step() {
863 let h_fields = Array1::zeros(1);
864 let j_couplings = Array2::zeros((1, 1));
865 let ising = IsingProblem::new(h_fields, j_couplings)
866 .expect("Failed to create Ising problem in test_adiabatic_evolution_step");
867
868 let mut adiabatic = AdiabaticQuantumComputer::new(
869 &ising,
870 1.0, 0.1, AnnealingSchedule::Linear,
873 );
874
875 let initial_time = adiabatic.current_time;
876 adiabatic
877 .step()
878 .expect("Failed to step adiabatic evolution in test_adiabatic_evolution_step");
879
880 assert!(adiabatic.current_time > initial_time);
881 assert!(adiabatic.current_time <= adiabatic.total_time);
882
883 let norm = adiabatic.state.iter().map(|c| c.norm_sqr()).sum::<f64>();
885 assert!((norm - 1.0).abs() < 1e-10);
886 }
887
888 #[test]
889 fn test_quantum_annealer() {
890 let problem = ProblemGenerator::max_cut(3, 0.5);
891 let mut annealer = QuantumAnnealer::new(
892 problem,
893 5.0, 0.1, AnnealingSchedule::Linear,
896 );
897
898 let (solution, energy) = annealer
899 .optimize()
900 .expect("Failed to optimize in test_quantum_annealer");
901
902 assert_eq!(solution.len(), 3);
903 assert!(solution.iter().all(|&s| s == -1 || s == 1));
904 assert!(energy.is_finite());
905
906 assert!(!annealer.history().is_empty());
908
909 assert!(annealer.best_solution().is_some());
911 }
912
913 #[test]
914 fn test_problem_generators() {
915 let qubo = ProblemGenerator::random_qubo(3, 0.5);
916 assert_eq!(qubo.num_vars, 3);
917 assert_eq!(qubo.q_matrix.dim(), (3, 3));
918
919 let max_cut = ProblemGenerator::max_cut(4, 0.6);
920 assert_eq!(max_cut.num_spins, 4);
921 assert_eq!(max_cut.h_fields.len(), 4);
922 assert_eq!(max_cut.j_couplings.dim(), (4, 4));
923
924 let numbers = vec![1.0, 2.0, 3.0];
925 let num_part = ProblemGenerator::number_partitioning(numbers);
926 assert_eq!(num_part.num_spins, 3);
927 }
928
929 #[test]
930 fn test_multiple_annealing_runs() {
931 let problem = ProblemGenerator::random_qubo(2, 1.0).to_ising();
932 let mut annealer = QuantumAnnealer::new(
933 problem,
934 2.0, 0.2, AnnealingSchedule::Linear,
937 );
938
939 let (solution, energy) = annealer
940 .optimize_multiple_runs(3)
941 .expect("Failed to optimize multiple runs in test_multiple_annealing_runs");
942
943 assert_eq!(solution.len(), 2);
944 assert!(solution.iter().all(|&s| s == -1 || s == 1));
945 assert!(energy.is_finite());
946 }
947
948 #[test]
949 fn test_annealing_parameter_progression() {
950 let problem = ProblemGenerator::max_cut(2, 1.0);
951 let mut computer = AdiabaticQuantumComputer::new(
952 &problem,
953 1.0, 0.25, AnnealingSchedule::Linear,
956 );
957
958 assert_eq!(computer.annealing_parameter(), 0.0);
960
961 computer
962 .step()
963 .expect("Failed to step (1) in test_annealing_parameter_progression");
964 assert!((computer.annealing_parameter() - 0.25).abs() < 1e-10);
965
966 computer
967 .step()
968 .expect("Failed to step (2) in test_annealing_parameter_progression");
969 assert!((computer.annealing_parameter() - 0.5).abs() < 1e-10);
970
971 computer
972 .step()
973 .expect("Failed to step (3) in test_annealing_parameter_progression");
974 assert!((computer.annealing_parameter() - 0.75).abs() < 1e-10);
975
976 computer
977 .step()
978 .expect("Failed to step (4) in test_annealing_parameter_progression");
979 assert!((computer.annealing_parameter() - 1.0).abs() < 1e-10);
980 }
981
982 #[test]
983 fn test_energy_gap_calculation() {
984 let h_fields = Array1::from(vec![1.0]);
985 let j_couplings = Array2::zeros((1, 1));
986 let ising = IsingProblem::new(h_fields, j_couplings)
987 .expect("Failed to create Ising problem in test_energy_gap_calculation");
988
989 let computer = AdiabaticQuantumComputer::new(&ising, 1.0, 0.1, AnnealingSchedule::Linear);
990
991 let gap = computer
992 .energy_gap()
993 .expect("Failed to calculate energy gap in test_energy_gap_calculation");
994 assert!(gap >= 0.0);
995 }
996
997 #[test]
998 fn test_measurement_probabilities() {
999 let h_fields = Array1::zeros(1);
1000 let j_couplings = Array2::zeros((1, 1));
1001 let ising = IsingProblem::new(h_fields, j_couplings)
1002 .expect("Failed to create Ising problem in test_measurement_probabilities");
1003
1004 let computer = AdiabaticQuantumComputer::new(&ising, 1.0, 0.1, AnnealingSchedule::Linear);
1005
1006 let probs = computer.measurement_probabilities();
1007 assert_eq!(probs.len(), 2); let total: f64 = probs.iter().sum();
1011 assert!((total - 1.0).abs() < 1e-10);
1012
1013 assert!(probs.iter().all(|&p| p >= 0.0));
1015 }
1016}