1use crate::error::{QuantRS2Error, QuantRS2Result};
12use ndarray::{Array1, Array2};
13use num_complex::Complex64;
14use std::f64::consts::PI;
15
16#[derive(Debug, Clone, Copy, PartialEq)]
18pub enum ProblemType {
19 QUBO,
21 Ising,
23 MaxCut,
25 GraphColoring,
27 TSP,
29 NumberPartitioning,
31 Custom,
33}
34
35#[derive(Debug, Clone, Copy, PartialEq)]
37pub enum AnnealingSchedule {
38 Linear,
40 Exponential { rate: f64 },
42 Polynomial { power: f64 },
44 Trigonometric,
46 Custom(fn(f64, f64) -> f64),
48}
49
50impl AnnealingSchedule {
51 pub fn evaluate(&self, t: f64, total_time: f64) -> f64 {
53 let s = t / total_time;
54 match self {
55 AnnealingSchedule::Linear => s,
56 AnnealingSchedule::Exponential { rate } => 1.0 - (-rate * s).exp(),
57 AnnealingSchedule::Polynomial { power } => s.powf(*power),
58 AnnealingSchedule::Trigonometric => (PI * s / 2.0).sin().powi(2),
59 AnnealingSchedule::Custom(func) => func(t, total_time),
60 }
61 }
62
63 pub fn derivative(&self, t: f64, total_time: f64) -> f64 {
65 let dt = 1e-8;
66 let s1 = self.evaluate(t + dt, total_time);
67 let s0 = self.evaluate(t, total_time);
68 (s1 - s0) / dt
69 }
70}
71
72#[derive(Debug, Clone)]
74pub struct QUBOProblem {
75 pub num_vars: usize,
77 pub q_matrix: Array2<f64>,
79 pub linear_terms: Option<Array1<f64>>,
81 pub offset: f64,
83}
84
85impl QUBOProblem {
86 pub fn new(q_matrix: Array2<f64>) -> QuantRS2Result<Self> {
88 let (rows, cols) = q_matrix.dim();
89 if rows != cols {
90 return Err(QuantRS2Error::InvalidInput(
91 "Q matrix must be square".to_string(),
92 ));
93 }
94
95 Ok(Self {
96 num_vars: rows,
97 q_matrix,
98 linear_terms: None,
99 offset: 0.0,
100 })
101 }
102
103 pub fn with_linear_terms(mut self, linear: Array1<f64>) -> QuantRS2Result<Self> {
105 if linear.len() != self.num_vars {
106 return Err(QuantRS2Error::InvalidInput(
107 "Linear terms size mismatch".to_string(),
108 ));
109 }
110 self.linear_terms = Some(linear);
111 Ok(self)
112 }
113
114 pub fn with_offset(mut self, offset: f64) -> Self {
116 self.offset = offset;
117 self
118 }
119
120 pub fn evaluate(&self, solution: &[u8]) -> QuantRS2Result<f64> {
122 if solution.len() != self.num_vars {
123 return Err(QuantRS2Error::InvalidInput(
124 "Solution size mismatch".to_string(),
125 ));
126 }
127
128 let mut objective = self.offset;
129
130 for i in 0..self.num_vars {
132 for j in 0..self.num_vars {
133 objective += self.q_matrix[[i, j]] * solution[i] as f64 * solution[j] as f64;
134 }
135 }
136
137 if let Some(ref linear) = self.linear_terms {
139 for i in 0..self.num_vars {
140 objective += linear[i] * solution[i] as f64;
141 }
142 }
143
144 Ok(objective)
145 }
146
147 pub fn to_ising(&self) -> IsingProblem {
149 let mut h = Array1::zeros(self.num_vars);
150 let mut j = Array2::zeros((self.num_vars, self.num_vars));
151 let mut offset = self.offset;
152
153 for i in 0..self.num_vars {
155 h[i] = self.q_matrix[[i, i]] / 4.0;
157 offset += self.q_matrix[[i, i]] / 4.0;
158
159 if let Some(ref linear) = self.linear_terms {
160 h[i] += linear[i] / 2.0;
161 offset += linear[i] / 2.0;
162 }
163
164 for k in 0..self.num_vars {
166 if i != k {
167 j[[i, k]] = self.q_matrix[[i, k]] / 4.0;
168 h[i] += self.q_matrix[[i, k]] / 4.0;
169 h[k] += self.q_matrix[[i, k]] / 4.0;
170 offset += self.q_matrix[[i, k]] / 4.0;
171 }
172 }
173 }
174
175 IsingProblem {
176 num_spins: self.num_vars,
177 h_fields: h,
178 j_couplings: j,
179 offset,
180 }
181 }
182}
183
184#[derive(Debug, Clone)]
186pub struct IsingProblem {
187 pub num_spins: usize,
189 pub h_fields: Array1<f64>,
191 pub j_couplings: Array2<f64>,
193 pub offset: f64,
195}
196
197impl IsingProblem {
198 pub fn new(h_fields: Array1<f64>, j_couplings: Array2<f64>) -> QuantRS2Result<Self> {
200 let num_spins = h_fields.len();
201 let (rows, cols) = j_couplings.dim();
202
203 if rows != num_spins || cols != num_spins {
204 return Err(QuantRS2Error::InvalidInput(
205 "Coupling matrix size mismatch".to_string(),
206 ));
207 }
208
209 Ok(Self {
210 num_spins,
211 h_fields,
212 j_couplings,
213 offset: 0.0,
214 })
215 }
216
217 pub fn evaluate(&self, spins: &[i8]) -> QuantRS2Result<f64> {
219 if spins.len() != self.num_spins {
220 return Err(QuantRS2Error::InvalidInput(
221 "Spin configuration size mismatch".to_string(),
222 ));
223 }
224
225 let mut energy = self.offset;
226
227 for i in 0..self.num_spins {
229 energy -= self.h_fields[i] * spins[i] as f64;
230 }
231
232 for i in 0..self.num_spins {
234 for j in i + 1..self.num_spins {
235 energy -= self.j_couplings[[i, j]] * spins[i] as f64 * spins[j] as f64;
236 }
237 }
238
239 Ok(energy)
240 }
241
242 pub fn hamiltonian(&self) -> Array2<Complex64> {
244 let dim = 1 << self.num_spins;
245 let mut hamiltonian = Array2::zeros((dim, dim));
246
247 for state in 0..dim {
248 let spins = self.state_to_spins(state);
249 let energy = self.evaluate(&spins).unwrap_or(0.0);
250 hamiltonian[[state, state]] = Complex64::new(energy, 0.0);
251 }
252
253 hamiltonian
254 }
255
256 fn state_to_spins(&self, state: usize) -> Vec<i8> {
258 (0..self.num_spins)
259 .map(|i| if (state >> i) & 1 == 0 { -1 } else { 1 })
260 .collect()
261 }
262
263 fn spins_to_state(&self, spins: &[i8]) -> usize {
265 spins.iter().enumerate().fold(0, |acc, (i, &spin)| {
266 acc | (if spin == 1 { 1 } else { 0 }) << i
267 })
268 }
269}
270
271pub struct AdiabaticQuantumComputer {
273 problem_hamiltonian: Array2<Complex64>,
275 initial_hamiltonian: Array2<Complex64>,
277 state: Array1<Complex64>,
279 num_qubits: usize,
281 total_time: f64,
283 current_time: f64,
285 time_step: f64,
287 schedule: AnnealingSchedule,
289}
290
291impl AdiabaticQuantumComputer {
292 pub fn new(
294 problem: &IsingProblem,
295 total_time: f64,
296 time_step: f64,
297 schedule: AnnealingSchedule,
298 ) -> Self {
299 let num_qubits = problem.num_spins;
300 let dim = 1 << num_qubits;
301
302 let problem_hamiltonian = problem.hamiltonian();
304
305 let mut initial_hamiltonian = Array2::zeros((dim, dim));
307 for i in 0..num_qubits {
308 let pauli_x = Self::pauli_x_tensor(i, num_qubits);
309 initial_hamiltonian = initial_hamiltonian + pauli_x;
310 }
311 initial_hamiltonian = initial_hamiltonian.mapv(|x: Complex64| -x);
312
313 let mut state = Array1::zeros(dim);
315 let amplitude = Complex64::new(1.0 / (dim as f64).sqrt(), 0.0);
316 state.fill(amplitude);
317
318 Self {
319 problem_hamiltonian,
320 initial_hamiltonian,
321 state,
322 num_qubits,
323 total_time,
324 current_time: 0.0,
325 time_step,
326 schedule,
327 }
328 }
329
330 fn pauli_x_tensor(target_qubit: usize, num_qubits: usize) -> Array2<Complex64> {
332 let dim = 1 << num_qubits;
333 let mut result = Array2::zeros((dim, dim));
334
335 for state in 0..dim {
336 let flipped_state = state ^ (1 << target_qubit);
338 result[[state, flipped_state]] = Complex64::new(1.0, 0.0);
339 }
340
341 result
342 }
343
344 pub fn current_hamiltonian(&self) -> Array2<Complex64> {
346 let s = self.schedule.evaluate(self.current_time, self.total_time);
347 let one_minus_s = 1.0 - s;
348
349 self.initial_hamiltonian.mapv(|x| x * one_minus_s)
350 + self.problem_hamiltonian.mapv(|x| x * s)
351 }
352
353 pub fn step(&mut self) -> QuantRS2Result<()> {
355 if self.current_time >= self.total_time {
356 return Ok(());
357 }
358
359 let hamiltonian = self.current_hamiltonian();
360
361 let dt = self.time_step.min(self.total_time - self.current_time);
364 let evolution_operator = self.compute_evolution_operator(&hamiltonian, dt)?;
365
366 self.state = evolution_operator.dot(&self.state);
367 self.current_time += dt;
368
369 let norm = self.state.iter().map(|c| c.norm_sqr()).sum::<f64>().sqrt();
371 if norm > 0.0 {
372 self.state.mapv_inplace(|c| c / norm);
373 }
374
375 Ok(())
376 }
377
378 fn compute_evolution_operator(
380 &self,
381 hamiltonian: &Array2<Complex64>,
382 dt: f64,
383 ) -> QuantRS2Result<Array2<Complex64>> {
384 let dim = hamiltonian.nrows();
385 let mut evolution = Array2::eye(dim);
386
387 let mut term = Array2::eye(dim);
389 let h_dt = hamiltonian.mapv(|h| Complex64::new(0.0, -dt) * h);
390
391 for n in 1..20 {
392 term = term.dot(&h_dt);
394 let coefficient = 1.0 / (1..=n).fold(1.0, |acc, i| acc * i as f64);
395 evolution = evolution + term.mapv(|t| t * coefficient);
396
397 let term_norm = term.iter().map(|c| c.norm_sqr()).sum::<f64>().sqrt();
399 if term_norm * coefficient < 1e-12 {
400 break;
401 }
402 }
403
404 Ok(evolution)
405 }
406
407 pub fn run(&mut self) -> QuantRS2Result<()> {
409 while self.current_time < self.total_time {
410 self.step()?;
411 }
412 Ok(())
413 }
414
415 pub fn measurement_probabilities(&self) -> Vec<f64> {
417 self.state.iter().map(|c| c.norm_sqr()).collect()
418 }
419
420 pub fn measure(&self) -> usize {
422 use rand::Rng;
423 let mut rng = rand::rng();
424 let probs = self.measurement_probabilities();
425
426 let random_value: f64 = rng.random();
427 let mut cumulative = 0.0;
428
429 for (state, prob) in probs.iter().enumerate() {
430 cumulative += prob;
431 if random_value <= cumulative {
432 return state;
433 }
434 }
435
436 probs.len() - 1 }
438
439 pub fn energy_gap(&self) -> QuantRS2Result<f64> {
441 let hamiltonian = self.current_hamiltonian();
442
443 if self.num_qubits <= 8 {
445 let eigenvalues = self.compute_eigenvalues(&hamiltonian)?;
446 let ground_energy = eigenvalues[0];
447 let first_excited = eigenvalues[1];
448 Ok(first_excited - ground_energy)
449 } else {
450 Ok(self.estimate_gap(&hamiltonian))
452 }
453 }
454
455 fn compute_eigenvalues(&self, hamiltonian: &Array2<Complex64>) -> QuantRS2Result<Vec<f64>> {
457 let dim = hamiltonian.nrows();
459 let mut eigenvalues: Vec<f64> = (0..dim).map(|i| hamiltonian[[i, i]].re).collect();
460 eigenvalues.sort_by(|a, b| a.partial_cmp(b).unwrap());
461 Ok(eigenvalues)
462 }
463
464 fn estimate_gap(&self, _hamiltonian: &Array2<Complex64>) -> f64 {
466 let s = self.schedule.evaluate(self.current_time, self.total_time);
468 (PI * s).sin() * 0.1 }
471
472 pub fn adiabatic_condition_satisfied(&self) -> QuantRS2Result<bool> {
474 let gap = self.energy_gap()?;
475 let s_dot = self.schedule.derivative(self.current_time, self.total_time);
476
477 Ok(gap > 10.0 * s_dot.abs())
480 }
481
482 pub fn reset(&mut self) {
484 let dim = 1 << self.num_qubits;
485 let amplitude = Complex64::new(1.0 / (dim as f64).sqrt(), 0.0);
486 self.state.fill(amplitude);
487 self.current_time = 0.0;
488 }
489
490 pub fn state(&self) -> &Array1<Complex64> {
492 &self.state
493 }
494
495 pub fn annealing_parameter(&self) -> f64 {
497 self.schedule.evaluate(self.current_time, self.total_time)
498 }
499
500 pub fn progress(&self) -> f64 {
502 (self.current_time / self.total_time).min(1.0) * 100.0
503 }
504}
505
506pub struct QuantumAnnealer {
508 computer: AdiabaticQuantumComputer,
509 problem: IsingProblem,
510 best_solution: Option<(Vec<i8>, f64)>,
511 history: Vec<QuantumAnnealingSnapshot>,
512}
513
514#[derive(Debug, Clone)]
516pub struct QuantumAnnealingSnapshot {
517 pub time: f64,
518 pub annealing_parameter: f64,
519 pub energy_gap: f64,
520 pub ground_state_probability: f64,
521 pub expected_energy: f64,
522}
523
524impl QuantumAnnealer {
525 pub fn new(
527 problem: IsingProblem,
528 total_time: f64,
529 time_step: f64,
530 schedule: AnnealingSchedule,
531 ) -> Self {
532 let computer = AdiabaticQuantumComputer::new(&problem, total_time, time_step, schedule);
533
534 Self {
535 computer,
536 problem,
537 best_solution: None,
538 history: Vec::new(),
539 }
540 }
541
542 pub fn optimize(&mut self) -> QuantRS2Result<(Vec<i8>, f64)> {
544 self.computer.reset();
545 self.history.clear();
546
547 self.record_snapshot()?;
549
550 while self.computer.current_time < self.computer.total_time {
552 self.computer.step()?;
553
554 if self.history.len() % 10 == 0 {
556 self.record_snapshot()?;
557 }
558 }
559
560 let measurement = self.computer.measure();
562 let spins = self.state_to_spins(measurement);
563 let energy = self.problem.evaluate(&spins)?;
564
565 self.best_solution = Some((spins.clone(), energy));
566
567 Ok((spins, energy))
568 }
569
570 pub fn optimize_multiple_runs(&mut self, num_runs: usize) -> QuantRS2Result<(Vec<i8>, f64)> {
572 let mut best_energy = f64::INFINITY;
573 let mut best_spins = vec![];
574
575 for _ in 0..num_runs {
576 let (spins, energy) = self.optimize()?;
577 if energy < best_energy {
578 best_energy = energy;
579 best_spins = spins;
580 }
581 }
582
583 Ok((best_spins, best_energy))
584 }
585
586 fn record_snapshot(&mut self) -> QuantRS2Result<()> {
588 let time = self.computer.current_time;
589 let annealing_parameter = self.computer.annealing_parameter();
590 let energy_gap = self.computer.energy_gap()?;
591
592 let probs = self.computer.measurement_probabilities();
594 let ground_state_probability = probs[0]; let mut expected_energy = 0.0;
597 for (state_idx, &prob) in probs.iter().enumerate() {
598 let spins = self.state_to_spins(state_idx);
599 let energy = self.problem.evaluate(&spins).unwrap_or(0.0);
600 expected_energy += prob * energy;
601 }
602
603 self.history.push(QuantumAnnealingSnapshot {
604 time,
605 annealing_parameter,
606 energy_gap,
607 ground_state_probability,
608 expected_energy,
609 });
610
611 Ok(())
612 }
613
614 fn state_to_spins(&self, state: usize) -> Vec<i8> {
616 (0..self.problem.num_spins)
617 .map(|i| if (state >> i) & 1 == 0 { -1 } else { 1 })
618 .collect()
619 }
620
621 pub fn history(&self) -> &[QuantumAnnealingSnapshot] {
623 &self.history
624 }
625
626 pub fn best_solution(&self) -> Option<&(Vec<i8>, f64)> {
628 self.best_solution.as_ref()
629 }
630
631 pub fn minimum_gap(&self) -> f64 {
633 self.history
634 .iter()
635 .map(|snapshot| snapshot.energy_gap)
636 .fold(f64::INFINITY, f64::min)
637 }
638
639 pub fn success_probability(&self) -> f64 {
641 self.history
642 .last()
643 .map(|snapshot| snapshot.ground_state_probability)
644 .unwrap_or(0.0)
645 }
646}
647
648pub struct ProblemGenerator;
650
651impl ProblemGenerator {
652 pub fn random_qubo(num_vars: usize, density: f64) -> QUBOProblem {
654 use rand::Rng;
655 let mut rng = rand::rng();
656
657 let mut q_matrix = Array2::zeros((num_vars, num_vars));
658
659 for i in 0..num_vars {
660 for j in i..num_vars {
661 if rng.random::<f64>() < density {
662 let value = rng.random_range(-1.0..=1.0);
663 q_matrix[[i, j]] = value;
664 if i != j {
665 q_matrix[[j, i]] = value; }
667 }
668 }
669 }
670
671 QUBOProblem::new(q_matrix).unwrap()
672 }
673
674 pub fn max_cut(num_vertices: usize, edge_probability: f64) -> IsingProblem {
676 use rand::Rng;
677 let mut rng = rand::rng();
678
679 let h_fields = Array1::zeros(num_vertices);
680 let mut j_couplings = Array2::zeros((num_vertices, num_vertices));
681
682 for i in 0..num_vertices {
684 for j in i + 1..num_vertices {
685 if rng.random::<f64>() < edge_probability {
686 let coupling = 1.0; j_couplings[[i, j]] = coupling;
688 j_couplings[[j, i]] = coupling;
689 }
690 }
691 }
692
693 IsingProblem::new(h_fields, j_couplings).unwrap()
694 }
695
696 pub fn number_partitioning(numbers: Vec<f64>) -> IsingProblem {
698 let n = numbers.len();
699 let h_fields = Array1::zeros(n);
700 let mut j_couplings = Array2::zeros((n, n));
701
702 for i in 0..n {
704 for j in i + 1..n {
705 let coupling = numbers[i] * numbers[j];
706 j_couplings[[i, j]] = coupling;
707 j_couplings[[j, i]] = coupling;
708 }
709 }
710
711 IsingProblem::new(h_fields, j_couplings).unwrap()
712 }
713}
714
715#[cfg(test)]
716mod tests {
717 use super::*;
718
719 #[test]
720 fn test_annealing_schedules() {
721 let linear = AnnealingSchedule::Linear;
722 assert_eq!(linear.evaluate(0.0, 10.0), 0.0);
723 assert_eq!(linear.evaluate(5.0, 10.0), 0.5);
724 assert_eq!(linear.evaluate(10.0, 10.0), 1.0);
725
726 let exp = AnnealingSchedule::Exponential { rate: 1.0 };
727 assert!(exp.evaluate(0.0, 1.0) < 0.1);
728 assert!(exp.evaluate(1.0, 1.0) > 0.6);
729
730 let poly = AnnealingSchedule::Polynomial { power: 2.0 };
731 assert_eq!(poly.evaluate(10.0, 10.0), 1.0);
732 assert!(poly.evaluate(5.0, 10.0) < linear.evaluate(5.0, 10.0));
733 }
734
735 #[test]
736 fn test_qubo_problem() {
737 let mut q_matrix = Array2::zeros((2, 2));
738 q_matrix[[0, 0]] = 1.0;
739 q_matrix[[1, 1]] = 1.0;
740 q_matrix[[0, 1]] = -2.0;
741 q_matrix[[1, 0]] = -2.0;
742
743 let qubo = QUBOProblem::new(q_matrix).unwrap();
744
745 assert_eq!(qubo.evaluate(&[0, 0]).unwrap(), 0.0);
752 assert_eq!(qubo.evaluate(&[1, 1]).unwrap(), -2.0);
753 assert_eq!(qubo.evaluate(&[1, 0]).unwrap(), 1.0);
754 assert_eq!(qubo.evaluate(&[0, 1]).unwrap(), 1.0);
755 }
756
757 #[test]
758 fn test_ising_problem() {
759 let h_fields = Array1::from(vec![0.5, -0.5]);
760 let mut j_couplings = Array2::zeros((2, 2));
761 j_couplings[[0, 1]] = 1.0;
762 j_couplings[[1, 0]] = 1.0;
763
764 let ising = IsingProblem::new(h_fields, j_couplings).unwrap();
765
766 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); }
774
775 #[test]
776 fn test_qubo_to_ising_conversion() {
777 let mut q_matrix = Array2::zeros((2, 2));
778 q_matrix[[0, 1]] = 1.0;
779 q_matrix[[1, 0]] = 1.0;
780
781 let qubo = QUBOProblem::new(q_matrix).unwrap();
782 let ising = qubo.to_ising();
783
784 assert_eq!(ising.num_spins, 2);
789 assert!(ising.h_fields.len() == 2);
790 assert!(ising.j_couplings.dim() == (2, 2));
791 }
792
793 #[test]
794 fn test_adiabatic_computer_initialization() {
795 let h_fields = Array1::from(vec![0.0, 0.0]);
796 let j_couplings = Array2::eye(2);
797 let ising = IsingProblem::new(h_fields, j_couplings).unwrap();
798
799 let adiabatic = AdiabaticQuantumComputer::new(
800 &ising,
801 10.0, 0.1, AnnealingSchedule::Linear,
804 );
805
806 assert_eq!(adiabatic.num_qubits, 2);
807 assert_eq!(adiabatic.current_time, 0.0);
808 assert_eq!(adiabatic.state.len(), 4); let expected_amplitude = 1.0 / 2.0; for amplitude in adiabatic.state.iter() {
813 assert!((amplitude.norm() - expected_amplitude).abs() < 1e-10);
814 }
815 }
816
817 #[test]
818 fn test_adiabatic_evolution_step() {
819 let h_fields = Array1::zeros(1);
820 let j_couplings = Array2::zeros((1, 1));
821 let ising = IsingProblem::new(h_fields, j_couplings).unwrap();
822
823 let mut adiabatic = AdiabaticQuantumComputer::new(
824 &ising,
825 1.0, 0.1, AnnealingSchedule::Linear,
828 );
829
830 let initial_time = adiabatic.current_time;
831 adiabatic.step().unwrap();
832
833 assert!(adiabatic.current_time > initial_time);
834 assert!(adiabatic.current_time <= adiabatic.total_time);
835
836 let norm = adiabatic.state.iter().map(|c| c.norm_sqr()).sum::<f64>();
838 assert!((norm - 1.0).abs() < 1e-10);
839 }
840
841 #[test]
842 fn test_quantum_annealer() {
843 let problem = ProblemGenerator::max_cut(3, 0.5);
844 let mut annealer = QuantumAnnealer::new(
845 problem,
846 5.0, 0.1, AnnealingSchedule::Linear,
849 );
850
851 let (solution, energy) = annealer.optimize().unwrap();
852
853 assert_eq!(solution.len(), 3);
854 assert!(solution.iter().all(|&s| s == -1 || s == 1));
855 assert!(energy.is_finite());
856
857 assert!(!annealer.history().is_empty());
859
860 assert!(annealer.best_solution().is_some());
862 }
863
864 #[test]
865 fn test_problem_generators() {
866 let qubo = ProblemGenerator::random_qubo(3, 0.5);
867 assert_eq!(qubo.num_vars, 3);
868 assert_eq!(qubo.q_matrix.dim(), (3, 3));
869
870 let max_cut = ProblemGenerator::max_cut(4, 0.6);
871 assert_eq!(max_cut.num_spins, 4);
872 assert_eq!(max_cut.h_fields.len(), 4);
873 assert_eq!(max_cut.j_couplings.dim(), (4, 4));
874
875 let numbers = vec![1.0, 2.0, 3.0];
876 let num_part = ProblemGenerator::number_partitioning(numbers);
877 assert_eq!(num_part.num_spins, 3);
878 }
879
880 #[test]
881 fn test_multiple_annealing_runs() {
882 let problem = ProblemGenerator::random_qubo(2, 1.0).to_ising();
883 let mut annealer = QuantumAnnealer::new(
884 problem,
885 2.0, 0.2, AnnealingSchedule::Linear,
888 );
889
890 let (solution, energy) = annealer.optimize_multiple_runs(3).unwrap();
891
892 assert_eq!(solution.len(), 2);
893 assert!(solution.iter().all(|&s| s == -1 || s == 1));
894 assert!(energy.is_finite());
895 }
896
897 #[test]
898 fn test_annealing_parameter_progression() {
899 let problem = ProblemGenerator::max_cut(2, 1.0);
900 let mut computer = AdiabaticQuantumComputer::new(
901 &problem,
902 1.0, 0.25, AnnealingSchedule::Linear,
905 );
906
907 assert_eq!(computer.annealing_parameter(), 0.0);
909
910 computer.step().unwrap();
911 assert!((computer.annealing_parameter() - 0.25).abs() < 1e-10);
912
913 computer.step().unwrap();
914 assert!((computer.annealing_parameter() - 0.5).abs() < 1e-10);
915
916 computer.step().unwrap();
917 assert!((computer.annealing_parameter() - 0.75).abs() < 1e-10);
918
919 computer.step().unwrap();
920 assert!((computer.annealing_parameter() - 1.0).abs() < 1e-10);
921 }
922
923 #[test]
924 fn test_energy_gap_calculation() {
925 let h_fields = Array1::from(vec![1.0]);
926 let j_couplings = Array2::zeros((1, 1));
927 let ising = IsingProblem::new(h_fields, j_couplings).unwrap();
928
929 let computer = AdiabaticQuantumComputer::new(&ising, 1.0, 0.1, AnnealingSchedule::Linear);
930
931 let gap = computer.energy_gap().unwrap();
932 assert!(gap >= 0.0);
933 }
934
935 #[test]
936 fn test_measurement_probabilities() {
937 let h_fields = Array1::zeros(1);
938 let j_couplings = Array2::zeros((1, 1));
939 let ising = IsingProblem::new(h_fields, j_couplings).unwrap();
940
941 let computer = AdiabaticQuantumComputer::new(&ising, 1.0, 0.1, AnnealingSchedule::Linear);
942
943 let probs = computer.measurement_probabilities();
944 assert_eq!(probs.len(), 2); let total: f64 = probs.iter().sum();
948 assert!((total - 1.0).abs() < 1e-10);
949
950 assert!(probs.iter().all(|&p| p >= 0.0));
952 }
953}