1#![allow(clippy::fn_address_comparisons)]
12
13use crate::error::{QuantRS2Error, QuantRS2Result};
14use ndarray::{Array1, Array2};
15use num_complex::Complex64;
16use std::f64::consts::PI;
17
18#[derive(Debug, Clone, Copy, PartialEq)]
20pub enum ProblemType {
21 QUBO,
23 Ising,
25 MaxCut,
27 GraphColoring,
29 TSP,
31 NumberPartitioning,
33 Custom,
35}
36
37#[derive(Debug, Clone, Copy)]
39pub enum AnnealingSchedule {
40 Linear,
42 Exponential { rate: f64 },
44 Polynomial { power: f64 },
46 Trigonometric,
48 Custom(fn(f64, f64) -> f64),
50}
51
52impl AnnealingSchedule {
53 pub fn evaluate(&self, t: f64, total_time: f64) -> f64 {
55 let s = t / total_time;
56 match self {
57 AnnealingSchedule::Linear => s,
58 AnnealingSchedule::Exponential { rate } => 1.0 - (-rate * s).exp(),
59 AnnealingSchedule::Polynomial { power } => s.powf(*power),
60 AnnealingSchedule::Trigonometric => (PI * s / 2.0).sin().powi(2),
61 AnnealingSchedule::Custom(func) => func(t, total_time),
62 }
63 }
64
65 pub fn derivative(&self, t: f64, total_time: f64) -> f64 {
67 let dt = 1e-8;
68 let s1 = self.evaluate(t + dt, total_time);
69 let s0 = self.evaluate(t, total_time);
70 (s1 - s0) / dt
71 }
72}
73
74#[derive(Debug, Clone)]
76pub struct QUBOProblem {
77 pub num_vars: usize,
79 pub q_matrix: Array2<f64>,
81 pub linear_terms: Option<Array1<f64>>,
83 pub offset: f64,
85}
86
87impl QUBOProblem {
88 pub fn new(q_matrix: Array2<f64>) -> QuantRS2Result<Self> {
90 let (rows, cols) = q_matrix.dim();
91 if rows != cols {
92 return Err(QuantRS2Error::InvalidInput(
93 "Q matrix must be square".to_string(),
94 ));
95 }
96
97 Ok(Self {
98 num_vars: rows,
99 q_matrix,
100 linear_terms: None,
101 offset: 0.0,
102 })
103 }
104
105 pub fn with_linear_terms(mut self, linear: Array1<f64>) -> QuantRS2Result<Self> {
107 if linear.len() != self.num_vars {
108 return Err(QuantRS2Error::InvalidInput(
109 "Linear terms size mismatch".to_string(),
110 ));
111 }
112 self.linear_terms = Some(linear);
113 Ok(self)
114 }
115
116 pub fn with_offset(mut self, offset: f64) -> Self {
118 self.offset = offset;
119 self
120 }
121
122 pub fn evaluate(&self, solution: &[u8]) -> QuantRS2Result<f64> {
124 if solution.len() != self.num_vars {
125 return Err(QuantRS2Error::InvalidInput(
126 "Solution size mismatch".to_string(),
127 ));
128 }
129
130 let mut objective = self.offset;
131
132 for i in 0..self.num_vars {
134 for j in 0..self.num_vars {
135 objective += self.q_matrix[[i, j]] * solution[i] as f64 * solution[j] as f64;
136 }
137 }
138
139 if let Some(ref linear) = self.linear_terms {
141 for i in 0..self.num_vars {
142 objective += linear[i] * solution[i] as f64;
143 }
144 }
145
146 Ok(objective)
147 }
148
149 pub fn to_ising(&self) -> IsingProblem {
151 let mut h = Array1::zeros(self.num_vars);
152 let mut j = Array2::zeros((self.num_vars, self.num_vars));
153 let mut offset = self.offset;
154
155 for i in 0..self.num_vars {
157 h[i] = self.q_matrix[[i, i]] / 4.0;
159 offset += self.q_matrix[[i, i]] / 4.0;
160
161 if let Some(ref linear) = self.linear_terms {
162 h[i] += linear[i] / 2.0;
163 offset += linear[i] / 2.0;
164 }
165
166 for k in 0..self.num_vars {
168 if i != k {
169 j[[i, k]] = self.q_matrix[[i, k]] / 4.0;
170 h[i] += self.q_matrix[[i, k]] / 4.0;
171 h[k] += self.q_matrix[[i, k]] / 4.0;
172 offset += self.q_matrix[[i, k]] / 4.0;
173 }
174 }
175 }
176
177 IsingProblem {
178 num_spins: self.num_vars,
179 h_fields: h,
180 j_couplings: j,
181 offset,
182 }
183 }
184}
185
186#[derive(Debug, Clone)]
188pub struct IsingProblem {
189 pub num_spins: usize,
191 pub h_fields: Array1<f64>,
193 pub j_couplings: Array2<f64>,
195 pub offset: f64,
197}
198
199impl IsingProblem {
200 pub fn new(h_fields: Array1<f64>, j_couplings: Array2<f64>) -> QuantRS2Result<Self> {
202 let num_spins = h_fields.len();
203 let (rows, cols) = j_couplings.dim();
204
205 if rows != num_spins || cols != num_spins {
206 return Err(QuantRS2Error::InvalidInput(
207 "Coupling matrix size mismatch".to_string(),
208 ));
209 }
210
211 Ok(Self {
212 num_spins,
213 h_fields,
214 j_couplings,
215 offset: 0.0,
216 })
217 }
218
219 pub fn evaluate(&self, spins: &[i8]) -> QuantRS2Result<f64> {
221 if spins.len() != self.num_spins {
222 return Err(QuantRS2Error::InvalidInput(
223 "Spin configuration size mismatch".to_string(),
224 ));
225 }
226
227 let mut energy = self.offset;
228
229 for i in 0..self.num_spins {
231 energy -= self.h_fields[i] * spins[i] as f64;
232 }
233
234 for i in 0..self.num_spins {
236 for j in i + 1..self.num_spins {
237 energy -= self.j_couplings[[i, j]] * spins[i] as f64 * spins[j] as f64;
238 }
239 }
240
241 Ok(energy)
242 }
243
244 pub fn hamiltonian(&self) -> Array2<Complex64> {
246 let dim = 1 << self.num_spins;
247 let mut hamiltonian = Array2::zeros((dim, dim));
248
249 for state in 0..dim {
250 let spins = self.state_to_spins(state);
251 let energy = self.evaluate(&spins).unwrap_or(0.0);
252 hamiltonian[[state, state]] = Complex64::new(energy, 0.0);
253 }
254
255 hamiltonian
256 }
257
258 fn state_to_spins(&self, state: usize) -> Vec<i8> {
260 (0..self.num_spins)
261 .map(|i| if (state >> i) & 1 == 0 { -1 } else { 1 })
262 .collect()
263 }
264
265 fn spins_to_state(&self, spins: &[i8]) -> usize {
267 spins.iter().enumerate().fold(0, |acc, (i, &spin)| {
268 acc | (if spin == 1 { 1 } else { 0 }) << i
269 })
270 }
271}
272
273pub struct AdiabaticQuantumComputer {
275 problem_hamiltonian: Array2<Complex64>,
277 initial_hamiltonian: Array2<Complex64>,
279 state: Array1<Complex64>,
281 num_qubits: usize,
283 total_time: f64,
285 current_time: f64,
287 time_step: f64,
289 schedule: AnnealingSchedule,
291}
292
293impl AdiabaticQuantumComputer {
294 pub fn new(
296 problem: &IsingProblem,
297 total_time: f64,
298 time_step: f64,
299 schedule: AnnealingSchedule,
300 ) -> Self {
301 let num_qubits = problem.num_spins;
302 let dim = 1 << num_qubits;
303
304 let problem_hamiltonian = problem.hamiltonian();
306
307 let mut initial_hamiltonian = Array2::zeros((dim, dim));
309 for i in 0..num_qubits {
310 let pauli_x = Self::pauli_x_tensor(i, num_qubits);
311 initial_hamiltonian = initial_hamiltonian + pauli_x;
312 }
313 initial_hamiltonian = initial_hamiltonian.mapv(|x: Complex64| -x);
314
315 let mut state = Array1::zeros(dim);
317 let amplitude = Complex64::new(1.0 / (dim as f64).sqrt(), 0.0);
318 state.fill(amplitude);
319
320 Self {
321 problem_hamiltonian,
322 initial_hamiltonian,
323 state,
324 num_qubits,
325 total_time,
326 current_time: 0.0,
327 time_step,
328 schedule,
329 }
330 }
331
332 fn pauli_x_tensor(target_qubit: usize, num_qubits: usize) -> Array2<Complex64> {
334 let dim = 1 << num_qubits;
335 let mut result = Array2::zeros((dim, dim));
336
337 for state in 0..dim {
338 let flipped_state = state ^ (1 << target_qubit);
340 result[[state, flipped_state]] = Complex64::new(1.0, 0.0);
341 }
342
343 result
344 }
345
346 pub fn current_hamiltonian(&self) -> Array2<Complex64> {
348 let s = self.schedule.evaluate(self.current_time, self.total_time);
349 let one_minus_s = 1.0 - s;
350
351 self.initial_hamiltonian.mapv(|x| x * one_minus_s)
352 + self.problem_hamiltonian.mapv(|x| x * s)
353 }
354
355 pub fn step(&mut self) -> QuantRS2Result<()> {
357 if self.current_time >= self.total_time {
358 return Ok(());
359 }
360
361 let hamiltonian = self.current_hamiltonian();
362
363 let dt = self.time_step.min(self.total_time - self.current_time);
366 let evolution_operator = self.compute_evolution_operator(&hamiltonian, dt)?;
367
368 self.state = evolution_operator.dot(&self.state);
369 self.current_time += dt;
370
371 let norm = self.state.iter().map(|c| c.norm_sqr()).sum::<f64>().sqrt();
373 if norm > 0.0 {
374 self.state.mapv_inplace(|c| c / norm);
375 }
376
377 Ok(())
378 }
379
380 fn compute_evolution_operator(
382 &self,
383 hamiltonian: &Array2<Complex64>,
384 dt: f64,
385 ) -> QuantRS2Result<Array2<Complex64>> {
386 let dim = hamiltonian.nrows();
387 let mut evolution = Array2::eye(dim);
388
389 let mut term = Array2::eye(dim);
391 let h_dt = hamiltonian.mapv(|h| Complex64::new(0.0, -dt) * h);
392
393 for n in 1..20 {
394 term = term.dot(&h_dt);
396 let coefficient = 1.0 / (1..=n).fold(1.0, |acc, i| acc * i as f64);
397 evolution = evolution + term.mapv(|t| t * coefficient);
398
399 let term_norm = term.iter().map(|c| c.norm_sqr()).sum::<f64>().sqrt();
401 if term_norm * coefficient < 1e-12 {
402 break;
403 }
404 }
405
406 Ok(evolution)
407 }
408
409 pub fn run(&mut self) -> QuantRS2Result<()> {
411 while self.current_time < self.total_time {
412 self.step()?;
413 }
414 Ok(())
415 }
416
417 pub fn measurement_probabilities(&self) -> Vec<f64> {
419 self.state.iter().map(|c| c.norm_sqr()).collect()
420 }
421
422 pub fn measure(&self) -> usize {
424 use rand::Rng;
425 let mut rng = rand::rng();
426 let probs = self.measurement_probabilities();
427
428 let random_value: f64 = rng.random();
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| a.partial_cmp(b).unwrap());
463 Ok(eigenvalues)
464 }
465
466 fn estimate_gap(&self, _hamiltonian: &Array2<Complex64>) -> f64 {
468 let s = self.schedule.evaluate(self.current_time, self.total_time);
470 (PI * s).sin() * 0.1 }
473
474 pub fn adiabatic_condition_satisfied(&self) -> QuantRS2Result<bool> {
476 let gap = self.energy_gap()?;
477 let s_dot = self.schedule.derivative(self.current_time, self.total_time);
478
479 Ok(gap > 10.0 * s_dot.abs())
482 }
483
484 pub fn reset(&mut self) {
486 let dim = 1 << self.num_qubits;
487 let amplitude = Complex64::new(1.0 / (dim as f64).sqrt(), 0.0);
488 self.state.fill(amplitude);
489 self.current_time = 0.0;
490 }
491
492 pub fn state(&self) -> &Array1<Complex64> {
494 &self.state
495 }
496
497 pub fn annealing_parameter(&self) -> f64 {
499 self.schedule.evaluate(self.current_time, self.total_time)
500 }
501
502 pub fn progress(&self) -> f64 {
504 (self.current_time / self.total_time).min(1.0) * 100.0
505 }
506}
507
508pub struct QuantumAnnealer {
510 computer: AdiabaticQuantumComputer,
511 problem: IsingProblem,
512 best_solution: Option<(Vec<i8>, f64)>,
513 history: Vec<QuantumAnnealingSnapshot>,
514}
515
516#[derive(Debug, Clone)]
518pub struct QuantumAnnealingSnapshot {
519 pub time: f64,
520 pub annealing_parameter: f64,
521 pub energy_gap: f64,
522 pub ground_state_probability: f64,
523 pub expected_energy: f64,
524}
525
526impl QuantumAnnealer {
527 pub fn new(
529 problem: IsingProblem,
530 total_time: f64,
531 time_step: f64,
532 schedule: AnnealingSchedule,
533 ) -> Self {
534 let computer = AdiabaticQuantumComputer::new(&problem, total_time, time_step, schedule);
535
536 Self {
537 computer,
538 problem,
539 best_solution: None,
540 history: Vec::new(),
541 }
542 }
543
544 pub fn optimize(&mut self) -> QuantRS2Result<(Vec<i8>, f64)> {
546 self.computer.reset();
547 self.history.clear();
548
549 self.record_snapshot()?;
551
552 while self.computer.current_time < self.computer.total_time {
554 self.computer.step()?;
555
556 if self.history.len() % 10 == 0 {
558 self.record_snapshot()?;
559 }
560 }
561
562 let measurement = self.computer.measure();
564 let spins = self.state_to_spins(measurement);
565 let energy = self.problem.evaluate(&spins)?;
566
567 self.best_solution = Some((spins.clone(), energy));
568
569 Ok((spins, energy))
570 }
571
572 pub fn optimize_multiple_runs(&mut self, num_runs: usize) -> QuantRS2Result<(Vec<i8>, f64)> {
574 let mut best_energy = f64::INFINITY;
575 let mut best_spins = vec![];
576
577 for _ in 0..num_runs {
578 let (spins, energy) = self.optimize()?;
579 if energy < best_energy {
580 best_energy = energy;
581 best_spins = spins;
582 }
583 }
584
585 Ok((best_spins, best_energy))
586 }
587
588 fn record_snapshot(&mut self) -> QuantRS2Result<()> {
590 let time = self.computer.current_time;
591 let annealing_parameter = self.computer.annealing_parameter();
592 let energy_gap = self.computer.energy_gap()?;
593
594 let probs = self.computer.measurement_probabilities();
596 let ground_state_probability = probs[0]; let mut expected_energy = 0.0;
599 for (state_idx, &prob) in probs.iter().enumerate() {
600 let spins = self.state_to_spins(state_idx);
601 let energy = self.problem.evaluate(&spins).unwrap_or(0.0);
602 expected_energy += prob * energy;
603 }
604
605 self.history.push(QuantumAnnealingSnapshot {
606 time,
607 annealing_parameter,
608 energy_gap,
609 ground_state_probability,
610 expected_energy,
611 });
612
613 Ok(())
614 }
615
616 fn state_to_spins(&self, state: usize) -> Vec<i8> {
618 (0..self.problem.num_spins)
619 .map(|i| if (state >> i) & 1 == 0 { -1 } else { 1 })
620 .collect()
621 }
622
623 pub fn history(&self) -> &[QuantumAnnealingSnapshot] {
625 &self.history
626 }
627
628 pub fn best_solution(&self) -> Option<&(Vec<i8>, f64)> {
630 self.best_solution.as_ref()
631 }
632
633 pub fn minimum_gap(&self) -> f64 {
635 self.history
636 .iter()
637 .map(|snapshot| snapshot.energy_gap)
638 .fold(f64::INFINITY, f64::min)
639 }
640
641 pub fn success_probability(&self) -> f64 {
643 self.history
644 .last()
645 .map(|snapshot| snapshot.ground_state_probability)
646 .unwrap_or(0.0)
647 }
648}
649
650pub struct ProblemGenerator;
652
653impl ProblemGenerator {
654 pub fn random_qubo(num_vars: usize, density: f64) -> QUBOProblem {
656 use rand::Rng;
657 let mut rng = rand::rng();
658
659 let mut q_matrix = Array2::zeros((num_vars, num_vars));
660
661 for i in 0..num_vars {
662 for j in i..num_vars {
663 if rng.random::<f64>() < density {
664 let value = rng.random_range(-1.0..=1.0);
665 q_matrix[[i, j]] = value;
666 if i != j {
667 q_matrix[[j, i]] = value; }
669 }
670 }
671 }
672
673 QUBOProblem::new(q_matrix).unwrap()
674 }
675
676 pub fn max_cut(num_vertices: usize, edge_probability: f64) -> IsingProblem {
678 use rand::Rng;
679 let mut rng = rand::rng();
680
681 let h_fields = Array1::zeros(num_vertices);
682 let mut j_couplings = Array2::zeros((num_vertices, num_vertices));
683
684 for i in 0..num_vertices {
686 for j in i + 1..num_vertices {
687 if rng.random::<f64>() < edge_probability {
688 let coupling = 1.0; j_couplings[[i, j]] = coupling;
690 j_couplings[[j, i]] = coupling;
691 }
692 }
693 }
694
695 IsingProblem::new(h_fields, j_couplings).unwrap()
696 }
697
698 pub fn number_partitioning(numbers: Vec<f64>) -> IsingProblem {
700 let n = numbers.len();
701 let h_fields = Array1::zeros(n);
702 let mut j_couplings = Array2::zeros((n, n));
703
704 for i in 0..n {
706 for j in i + 1..n {
707 let coupling = numbers[i] * numbers[j];
708 j_couplings[[i, j]] = coupling;
709 j_couplings[[j, i]] = coupling;
710 }
711 }
712
713 IsingProblem::new(h_fields, j_couplings).unwrap()
714 }
715}
716
717#[cfg(test)]
718mod tests {
719 use super::*;
720
721 #[test]
722 fn test_annealing_schedules() {
723 let linear = AnnealingSchedule::Linear;
724 assert_eq!(linear.evaluate(0.0, 10.0), 0.0);
725 assert_eq!(linear.evaluate(5.0, 10.0), 0.5);
726 assert_eq!(linear.evaluate(10.0, 10.0), 1.0);
727
728 let exp = AnnealingSchedule::Exponential { rate: 1.0 };
729 assert!(exp.evaluate(0.0, 1.0) < 0.1);
730 assert!(exp.evaluate(1.0, 1.0) > 0.6);
731
732 let poly = AnnealingSchedule::Polynomial { power: 2.0 };
733 assert_eq!(poly.evaluate(10.0, 10.0), 1.0);
734 assert!(poly.evaluate(5.0, 10.0) < linear.evaluate(5.0, 10.0));
735 }
736
737 #[test]
738 fn test_qubo_problem() {
739 let mut q_matrix = Array2::zeros((2, 2));
740 q_matrix[[0, 0]] = 1.0;
741 q_matrix[[1, 1]] = 1.0;
742 q_matrix[[0, 1]] = -2.0;
743 q_matrix[[1, 0]] = -2.0;
744
745 let qubo = QUBOProblem::new(q_matrix).unwrap();
746
747 assert_eq!(qubo.evaluate(&[0, 0]).unwrap(), 0.0);
754 assert_eq!(qubo.evaluate(&[1, 1]).unwrap(), -2.0);
755 assert_eq!(qubo.evaluate(&[1, 0]).unwrap(), 1.0);
756 assert_eq!(qubo.evaluate(&[0, 1]).unwrap(), 1.0);
757 }
758
759 #[test]
760 fn test_ising_problem() {
761 let h_fields = Array1::from(vec![0.5, -0.5]);
762 let mut j_couplings = Array2::zeros((2, 2));
763 j_couplings[[0, 1]] = 1.0;
764 j_couplings[[1, 0]] = 1.0;
765
766 let ising = IsingProblem::new(h_fields, j_couplings).unwrap();
767
768 assert_eq!(ising.evaluate(&[-1, -1]).unwrap(), -1.0); assert_eq!(ising.evaluate(&[1, 1]).unwrap(), -1.0); assert_eq!(ising.evaluate(&[1, -1]).unwrap(), 0.0); assert_eq!(ising.evaluate(&[-1, 1]).unwrap(), 2.0); }
776
777 #[test]
778 fn test_qubo_to_ising_conversion() {
779 let mut q_matrix = Array2::zeros((2, 2));
780 q_matrix[[0, 1]] = 1.0;
781 q_matrix[[1, 0]] = 1.0;
782
783 let qubo = QUBOProblem::new(q_matrix).unwrap();
784 let ising = qubo.to_ising();
785
786 assert_eq!(ising.num_spins, 2);
791 assert!(ising.h_fields.len() == 2);
792 assert!(ising.j_couplings.dim() == (2, 2));
793 }
794
795 #[test]
796 fn test_adiabatic_computer_initialization() {
797 let h_fields = Array1::from(vec![0.0, 0.0]);
798 let j_couplings = Array2::eye(2);
799 let ising = IsingProblem::new(h_fields, j_couplings).unwrap();
800
801 let adiabatic = AdiabaticQuantumComputer::new(
802 &ising,
803 10.0, 0.1, AnnealingSchedule::Linear,
806 );
807
808 assert_eq!(adiabatic.num_qubits, 2);
809 assert_eq!(adiabatic.current_time, 0.0);
810 assert_eq!(adiabatic.state.len(), 4); let expected_amplitude = 1.0 / 2.0; for amplitude in adiabatic.state.iter() {
815 assert!((amplitude.norm() - expected_amplitude).abs() < 1e-10);
816 }
817 }
818
819 #[test]
820 fn test_adiabatic_evolution_step() {
821 let h_fields = Array1::zeros(1);
822 let j_couplings = Array2::zeros((1, 1));
823 let ising = IsingProblem::new(h_fields, j_couplings).unwrap();
824
825 let mut adiabatic = AdiabaticQuantumComputer::new(
826 &ising,
827 1.0, 0.1, AnnealingSchedule::Linear,
830 );
831
832 let initial_time = adiabatic.current_time;
833 adiabatic.step().unwrap();
834
835 assert!(adiabatic.current_time > initial_time);
836 assert!(adiabatic.current_time <= adiabatic.total_time);
837
838 let norm = adiabatic.state.iter().map(|c| c.norm_sqr()).sum::<f64>();
840 assert!((norm - 1.0).abs() < 1e-10);
841 }
842
843 #[test]
844 fn test_quantum_annealer() {
845 let problem = ProblemGenerator::max_cut(3, 0.5);
846 let mut annealer = QuantumAnnealer::new(
847 problem,
848 5.0, 0.1, AnnealingSchedule::Linear,
851 );
852
853 let (solution, energy) = annealer.optimize().unwrap();
854
855 assert_eq!(solution.len(), 3);
856 assert!(solution.iter().all(|&s| s == -1 || s == 1));
857 assert!(energy.is_finite());
858
859 assert!(!annealer.history().is_empty());
861
862 assert!(annealer.best_solution().is_some());
864 }
865
866 #[test]
867 fn test_problem_generators() {
868 let qubo = ProblemGenerator::random_qubo(3, 0.5);
869 assert_eq!(qubo.num_vars, 3);
870 assert_eq!(qubo.q_matrix.dim(), (3, 3));
871
872 let max_cut = ProblemGenerator::max_cut(4, 0.6);
873 assert_eq!(max_cut.num_spins, 4);
874 assert_eq!(max_cut.h_fields.len(), 4);
875 assert_eq!(max_cut.j_couplings.dim(), (4, 4));
876
877 let numbers = vec![1.0, 2.0, 3.0];
878 let num_part = ProblemGenerator::number_partitioning(numbers);
879 assert_eq!(num_part.num_spins, 3);
880 }
881
882 #[test]
883 fn test_multiple_annealing_runs() {
884 let problem = ProblemGenerator::random_qubo(2, 1.0).to_ising();
885 let mut annealer = QuantumAnnealer::new(
886 problem,
887 2.0, 0.2, AnnealingSchedule::Linear,
890 );
891
892 let (solution, energy) = annealer.optimize_multiple_runs(3).unwrap();
893
894 assert_eq!(solution.len(), 2);
895 assert!(solution.iter().all(|&s| s == -1 || s == 1));
896 assert!(energy.is_finite());
897 }
898
899 #[test]
900 fn test_annealing_parameter_progression() {
901 let problem = ProblemGenerator::max_cut(2, 1.0);
902 let mut computer = AdiabaticQuantumComputer::new(
903 &problem,
904 1.0, 0.25, AnnealingSchedule::Linear,
907 );
908
909 assert_eq!(computer.annealing_parameter(), 0.0);
911
912 computer.step().unwrap();
913 assert!((computer.annealing_parameter() - 0.25).abs() < 1e-10);
914
915 computer.step().unwrap();
916 assert!((computer.annealing_parameter() - 0.5).abs() < 1e-10);
917
918 computer.step().unwrap();
919 assert!((computer.annealing_parameter() - 0.75).abs() < 1e-10);
920
921 computer.step().unwrap();
922 assert!((computer.annealing_parameter() - 1.0).abs() < 1e-10);
923 }
924
925 #[test]
926 fn test_energy_gap_calculation() {
927 let h_fields = Array1::from(vec![1.0]);
928 let j_couplings = Array2::zeros((1, 1));
929 let ising = IsingProblem::new(h_fields, j_couplings).unwrap();
930
931 let computer = AdiabaticQuantumComputer::new(&ising, 1.0, 0.1, AnnealingSchedule::Linear);
932
933 let gap = computer.energy_gap().unwrap();
934 assert!(gap >= 0.0);
935 }
936
937 #[test]
938 fn test_measurement_probabilities() {
939 let h_fields = Array1::zeros(1);
940 let j_couplings = Array2::zeros((1, 1));
941 let ising = IsingProblem::new(h_fields, j_couplings).unwrap();
942
943 let computer = AdiabaticQuantumComputer::new(&ising, 1.0, 0.1, AnnealingSchedule::Linear);
944
945 let probs = computer.measurement_probabilities();
946 assert_eq!(probs.len(), 2); let total: f64 = probs.iter().sum();
950 assert!((total - 1.0).abs() < 1e-10);
951
952 assert!(probs.iter().all(|&p| p >= 0.0));
954 }
955}