1use crate::complex_ext::QuantumComplexExt;
7use crate::simd_ops;
8use scirs2_core::ndarray::Array2;
9use scirs2_core::Complex64;
10use std::f64::consts::PI;
11
12#[derive(Debug, Clone)]
14pub struct QAOAParams {
15 pub layers: usize,
17 pub beta: Vec<f64>,
19 pub gamma: Vec<f64>,
21}
22
23impl QAOAParams {
24 pub fn new(layers: usize) -> Self {
26 Self {
27 layers,
28 beta: vec![0.0; layers],
29 gamma: vec![0.0; layers],
30 }
31 }
32
33 pub fn random(layers: usize) -> Self {
35 let mut beta = Vec::with_capacity(layers);
36 let mut gamma = Vec::with_capacity(layers);
37
38 for i in 0..layers {
39 let rand_val = f64::midpoint((i as f64).mul_add(1.234, 5.678).sin(), 1.0);
41 beta.push(rand_val * PI);
42 gamma.push(rand_val * 2.0 * PI);
43 }
44
45 Self {
46 layers,
47 beta,
48 gamma,
49 }
50 }
51
52 pub fn update(&mut self, new_beta: Vec<f64>, new_gamma: Vec<f64>) {
54 assert_eq!(new_beta.len(), self.layers);
55 assert_eq!(new_gamma.len(), self.layers);
56 self.beta = new_beta;
57 self.gamma = new_gamma;
58 }
59}
60
61#[derive(Clone)]
63pub enum CostHamiltonian {
64 MaxCut(Vec<(usize, usize)>),
66 WeightedMaxCut(Vec<(usize, usize, f64)>),
68 Ising {
70 h: Vec<f64>,
71 j: Vec<((usize, usize), f64)>,
72 },
73}
74
75impl std::fmt::Debug for CostHamiltonian {
76 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
77 match self {
78 Self::MaxCut(edges) => f.debug_tuple("MaxCut").field(edges).finish(),
79 Self::WeightedMaxCut(edges) => f.debug_tuple("WeightedMaxCut").field(edges).finish(),
80 Self::Ising { h, j } => f.debug_struct("Ising").field("h", h).field("j", j).finish(),
81 }
82 }
83}
84
85#[derive(Debug, Clone)]
87pub enum MixerHamiltonian {
88 TransverseField,
90 Custom(Vec<Array2<Complex64>>),
92}
93
94#[derive(Debug, Clone)]
96pub struct QAOACircuit {
97 pub num_qubits: usize,
98 cost_hamiltonian: CostHamiltonian,
99 mixer_hamiltonian: MixerHamiltonian,
100 params: QAOAParams,
101}
102
103impl QAOACircuit {
104 pub const fn new(
106 num_qubits: usize,
107 cost_hamiltonian: CostHamiltonian,
108 mixer_hamiltonian: MixerHamiltonian,
109 params: QAOAParams,
110 ) -> Self {
111 Self {
112 num_qubits,
113 cost_hamiltonian,
114 mixer_hamiltonian,
115 params,
116 }
117 }
118
119 pub fn prepare_initial_state(&self, state: &mut [Complex64]) {
121 let n = state.len();
122 let amplitude = Complex64::new(1.0 / (n as f64).sqrt(), 0.0);
123 state.fill(amplitude);
124 }
125
126 pub fn apply_cost_evolution(&self, state: &mut [Complex64], gamma: f64) {
128 match &self.cost_hamiltonian {
129 CostHamiltonian::MaxCut(edges) => {
130 for &(i, j) in edges {
131 self.apply_zz_rotation(state, i, j, gamma);
132 }
133 }
134 CostHamiltonian::WeightedMaxCut(weighted_edges) => {
135 for &(i, j, weight) in weighted_edges {
136 self.apply_zz_rotation(state, i, j, gamma * weight);
137 }
138 }
139 CostHamiltonian::Ising { h, j } => {
140 for (i, &h_i) in h.iter().enumerate() {
142 if h_i.abs() > 1e-10 {
143 self.apply_z_rotation(state, i, gamma * h_i);
144 }
145 }
146 for &((i, j), j_ij) in j {
148 if j_ij.abs() > 1e-10 {
149 self.apply_zz_rotation(state, i, j, gamma * j_ij);
150 }
151 }
152 }
153 }
154 }
155
156 pub fn apply_mixer_evolution(&self, state: &mut [Complex64], beta: f64) {
158 match &self.mixer_hamiltonian {
159 MixerHamiltonian::TransverseField => {
160 for i in 0..self.num_qubits {
162 self.apply_x_rotation(state, i, beta);
163 }
164 }
165 MixerHamiltonian::Custom(matrices) => {
166 for matrix in matrices {
169 self.apply_custom_mixer_term(state, matrix, beta);
170 }
171 }
172 }
173 }
174
175 fn apply_custom_mixer_term(
181 &self,
182 state: &mut [Complex64],
183 hamiltonian: &Array2<Complex64>,
184 beta: f64,
185 ) {
186 use scirs2_core::ndarray::Array1;
187
188 let dim = hamiltonian.nrows();
190
191 let num_target_qubits = (dim as f64).log2() as usize;
193
194 if num_target_qubits == 0 || dim != (1 << num_target_qubits) {
195 return;
197 }
198
199 if num_target_qubits <= 2 {
201 let evolution_op = self.compute_matrix_exponential(hamiltonian, -beta);
203
204 self.apply_small_unitary(state, &evolution_op, num_target_qubits);
207 } else {
208 let n_trotter_steps = 10;
213 let small_beta = beta / (n_trotter_steps as f64);
214
215 for _ in 0..n_trotter_steps {
216 let evolution_op = self.compute_matrix_exponential(hamiltonian, -small_beta);
217 self.apply_small_unitary(state, &evolution_op, num_target_qubits);
218 }
219 }
220 }
221
222 fn compute_matrix_exponential(
227 &self,
228 matrix: &Array2<Complex64>,
229 angle: f64,
230 ) -> Array2<Complex64> {
231 use scirs2_core::ndarray::{s, Array2};
232
233 let dim = matrix.nrows();
234 let i_angle = Complex64::new(0.0, angle);
235
236 let scaled_matrix = matrix.mapv(|x| x * i_angle);
238
239 let mut result = Array2::eye(dim);
241 let mut term = Array2::eye(dim);
242
243 for k in 1..=20 {
245 term = term.dot(&scaled_matrix) / (k as f64);
247 result = result + &term;
248
249 let term_norm: f64 = term.iter().map(|x| x.norm_sqr()).sum::<f64>().sqrt();
251 if term_norm < 1e-12 {
252 break;
253 }
254 }
255
256 result
257 }
258
259 fn apply_small_unitary(
264 &self,
265 state: &mut [Complex64],
266 unitary: &Array2<Complex64>,
267 num_operator_qubits: usize,
268 ) {
269 use scirs2_core::ndarray::Array1;
270
271 let operator_dim = 1 << num_operator_qubits;
272 let remaining_dim = state.len() / operator_dim;
273
274 for block in 0..remaining_dim {
276 let mut local_state = vec![Complex64::new(0.0, 0.0); operator_dim];
277
278 for i in 0..operator_dim {
280 local_state[i] = state[block * operator_dim + i];
281 }
282
283 let state_vec = Array1::from_vec(local_state);
285 let new_state = unitary.dot(&state_vec);
286
287 for i in 0..operator_dim {
289 state[block * operator_dim + i] = new_state[i];
290 }
291 }
292 }
293
294 fn apply_z_rotation(&self, state: &mut [Complex64], qubit: usize, angle: f64) {
296 let phase = Complex64::from_polar(1.0, -angle / 2.0);
297 let phase_conj = phase.conj();
298
299 let qubit_mask = 1 << qubit;
300
301 for (i, amp) in state.iter_mut().enumerate() {
302 if i & qubit_mask == 0 {
303 *amp *= phase;
304 } else {
305 *amp *= phase_conj;
306 }
307 }
308 }
309
310 fn apply_x_rotation(&self, state: &mut [Complex64], qubit: usize, angle: f64) {
312 let cos_half = (angle / 2.0).cos();
313 let sin_half = (angle / 2.0).sin();
314
315 let n = state.len();
316 let stride = 1 << (qubit + 1);
318
319 for chunk_start in (0..n).step_by(stride) {
321 for i in 0..(stride / 2) {
322 let idx0 = chunk_start + i;
323 let idx1 = idx0 + (stride / 2);
324
325 if idx1 < n {
326 let amp0 = state[idx0];
327 let amp1 = state[idx1];
328
329 state[idx0] = amp0 * cos_half + amp1 * Complex64::new(0.0, -sin_half);
330 state[idx1] = amp1 * cos_half + amp0 * Complex64::new(0.0, -sin_half);
331 }
332 }
333 }
334 }
335
336 fn apply_zz_rotation(&self, state: &mut [Complex64], qubit1: usize, qubit2: usize, angle: f64) {
338 let phase = Complex64::from_polar(1.0, -angle / 2.0);
339
340 let mask1 = 1 << qubit1;
341 let mask2 = 1 << qubit2;
342
343 for (i, amp) in state.iter_mut().enumerate() {
344 let parity = ((i & mask1) >> qubit1) ^ ((i & mask2) >> qubit2);
345 if parity == 0 {
346 *amp *= phase;
347 } else {
348 *amp *= phase.conj();
349 }
350 }
351 }
352
353 pub fn execute(&self, state: &mut [Complex64]) {
355 self.prepare_initial_state(state);
357
358 for layer in 0..self.params.layers {
360 self.apply_cost_evolution(state, self.params.gamma[layer]);
361 self.apply_mixer_evolution(state, self.params.beta[layer]);
362 }
363
364 let _ = simd_ops::normalize_simd(state);
366 }
367
368 pub fn compute_expectation(&self, state: &[Complex64]) -> f64 {
370 match &self.cost_hamiltonian {
371 CostHamiltonian::MaxCut(edges) => {
372 let mut expectation = 0.0;
373 for &(i, j) in edges {
374 expectation += self.compute_zz_expectation(state, i, j);
375 }
376 edges.len() as f64 / 2.0 - expectation / 2.0
377 }
378 CostHamiltonian::WeightedMaxCut(weighted_edges) => {
379 let mut expectation = 0.0;
380 let mut total_weight = 0.0;
381 for &(i, j, weight) in weighted_edges {
382 expectation += weight * self.compute_zz_expectation(state, i, j);
383 total_weight += weight;
384 }
385 total_weight / 2.0 - expectation / 2.0
386 }
387 CostHamiltonian::Ising { h, j } => {
388 let mut expectation = 0.0;
389
390 for (i, &h_i) in h.iter().enumerate() {
392 if h_i.abs() > 1e-10 {
393 let num_qubits = (state.len() as f64).log2() as usize;
394 expectation += h_i * simd_ops::expectation_z_simd(state, i, num_qubits);
395 }
396 }
397
398 for &((i, j), j_ij) in j {
400 if j_ij.abs() > 1e-10 {
401 expectation += j_ij * self.compute_zz_expectation(state, i, j);
402 }
403 }
404
405 expectation
406 }
407 }
408 }
409
410 fn compute_zz_expectation(&self, state: &[Complex64], qubit1: usize, qubit2: usize) -> f64 {
412 let mask1 = 1 << qubit1;
413 let mask2 = 1 << qubit2;
414
415 let mut expectation = 0.0;
416 for (i, amp) in state.iter().enumerate() {
417 let bit1 = (i & mask1) >> qubit1;
418 let bit2 = (i & mask2) >> qubit2;
419 let sign = if bit1 == bit2 { 1.0 } else { -1.0 };
420 expectation += sign * amp.probability();
421 }
422
423 expectation
424 }
425
426 pub fn get_solution(&self, state: &[Complex64]) -> Vec<bool> {
428 let mut max_prob = 0.0;
429 let mut max_idx = 0;
430
431 for (i, amp) in state.iter().enumerate() {
432 let prob = amp.probability();
433 if prob > max_prob {
434 max_prob = prob;
435 max_idx = i;
436 }
437 }
438
439 (0..self.num_qubits)
441 .map(|i| (max_idx >> i) & 1 == 1)
442 .collect()
443 }
444}
445
446pub struct QAOAOptimizer {
448 circuit: QAOACircuit,
449 #[allow(dead_code)]
450 max_iterations: usize,
451 #[allow(dead_code)]
452 tolerance: f64,
453}
454
455impl QAOAOptimizer {
456 pub const fn new(circuit: QAOACircuit, max_iterations: usize, tolerance: f64) -> Self {
458 Self {
459 circuit,
460 max_iterations,
461 tolerance,
462 }
463 }
464
465 pub fn execute_circuit(&mut self) -> Vec<Complex64> {
467 let state_size = 1 << self.circuit.num_qubits;
468 let mut state = vec![Complex64::new(0.0, 0.0); state_size];
469 self.circuit.execute(&mut state);
470 state
471 }
472
473 pub fn get_solution(&self, state: &[Complex64]) -> Vec<bool> {
475 self.circuit.get_solution(state)
476 }
477
478 pub fn optimize(&mut self) -> (Vec<f64>, Vec<f64>, f64) {
480 let initial_beta = self.circuit.params.beta.clone();
481 let initial_gamma = self.circuit.params.gamma.clone();
482
483 let mut best_beta = initial_beta;
484 let mut best_gamma = initial_gamma;
485 let mut best_expectation = f64::NEG_INFINITY;
486
487 let num_points = 5;
489 for beta_scale in 0..num_points {
490 for gamma_scale in 0..num_points {
491 let beta_val = (beta_scale as f64) * std::f64::consts::PI / (num_points as f64);
492 let gamma_val =
493 (gamma_scale as f64) * 2.0 * std::f64::consts::PI / (num_points as f64);
494
495 let beta_params = vec![beta_val; self.circuit.params.layers];
496 let gamma_params = vec![gamma_val; self.circuit.params.layers];
497
498 self.circuit.params.beta.clone_from(&beta_params);
499 self.circuit.params.gamma.clone_from(&gamma_params);
500
501 let state = self.execute_circuit();
502 let expectation = self.circuit.compute_expectation(&state);
503
504 if expectation > best_expectation {
505 best_expectation = expectation;
506 best_beta = beta_params;
507 best_gamma = gamma_params;
508 }
509 }
510 }
511
512 self.circuit.params.beta.clone_from(&best_beta);
513 self.circuit.params.gamma.clone_from(&best_gamma);
514
515 (best_beta, best_gamma, best_expectation)
516 }
517}
518
519#[derive(Debug, Clone)]
521pub struct MaxCutQAOA {
522 pub graph: Vec<Vec<usize>>,
524 pub weights: Option<Vec<Vec<f64>>>,
526 pub num_vertices: usize,
528 circuit: Option<QAOACircuit>,
530}
531
532impl MaxCutQAOA {
533 pub fn new(graph: Vec<Vec<usize>>) -> Self {
535 let num_vertices = graph.len();
536 Self {
537 graph,
538 weights: None,
539 num_vertices,
540 circuit: None,
541 }
542 }
543
544 #[must_use]
546 pub fn with_weights(mut self, weights: Vec<Vec<f64>>) -> Self {
547 assert_eq!(weights.len(), self.num_vertices);
548 self.weights = Some(weights);
549 self
550 }
551
552 pub fn build_circuit(&mut self, layers: usize) -> &mut Self {
554 let edges = self.extract_edges();
555
556 let cost_hamiltonian = if let Some(ref weights) = self.weights {
557 let weighted_edges = edges
558 .iter()
559 .map(|(i, j)| (*i, *j, weights[*i][*j]))
560 .collect();
561 CostHamiltonian::WeightedMaxCut(weighted_edges)
562 } else {
563 CostHamiltonian::MaxCut(edges)
564 };
565
566 let mixer_hamiltonian = MixerHamiltonian::TransverseField;
567 let params = QAOAParams::random(layers);
568
569 self.circuit = Some(QAOACircuit::new(
570 self.num_vertices,
571 cost_hamiltonian,
572 mixer_hamiltonian,
573 params,
574 ));
575
576 self
577 }
578
579 fn extract_edges(&self) -> Vec<(usize, usize)> {
581 let mut edges = Vec::new();
582 for (i, neighbors) in self.graph.iter().enumerate() {
583 for &j in neighbors {
584 if i < j {
585 edges.push((i, j));
587 }
588 }
589 }
590 edges
591 }
592
593 pub fn solve(&mut self) -> (Vec<bool>, f64) {
595 if self.circuit.is_none() {
596 self.build_circuit(2); }
598
599 let circuit = self
601 .circuit
602 .as_mut()
603 .expect("Circuit should be built after build_circuit call");
604 let mut optimizer = QAOAOptimizer::new(circuit.clone(), 100, 1e-6);
605
606 let (_, _, best_expectation) = optimizer.optimize();
607 let final_state = optimizer.execute_circuit();
608 let solution = optimizer.get_solution(&final_state);
609
610 (solution, best_expectation)
611 }
612
613 pub fn evaluate_cut(&self, solution: &[bool]) -> f64 {
615 let mut cut_value = 0.0;
616
617 for (i, neighbors) in self.graph.iter().enumerate() {
618 for &j in neighbors {
619 if i < j && solution[i] != solution[j] {
620 let weight = if let Some(ref weights) = self.weights {
621 weights[i][j]
622 } else {
623 1.0
624 };
625 cut_value += weight;
626 }
627 }
628 }
629
630 cut_value
631 }
632
633 pub fn random_graph(num_vertices: usize, edge_probability: f64) -> Self {
635 let mut graph = vec![Vec::new(); num_vertices];
636
637 for i in 0..num_vertices {
638 for j in i + 1..num_vertices {
639 let rand_val = ((i * j) as f64).mul_add(1.234, 5.678).sin().abs();
641 if rand_val < edge_probability {
642 graph[i].push(j);
643 graph[j].push(i);
644 }
645 }
646 }
647
648 Self::new(graph)
649 }
650}
651
652#[derive(Debug, Clone)]
654pub struct TSPQAOA {
655 pub distances: Vec<Vec<f64>>,
657 pub num_cities: usize,
659 circuit: Option<QAOACircuit>,
661}
662
663impl TSPQAOA {
664 pub fn new(distances: Vec<Vec<f64>>) -> Self {
666 let num_cities = distances.len();
667 assert!(distances.iter().all(|row| row.len() == num_cities));
668
669 Self {
670 distances,
671 num_cities,
672 circuit: None,
673 }
674 }
675
676 pub fn build_circuit(&mut self, layers: usize) -> &mut Self {
679 let num_qubits = self.num_cities * self.num_cities;
680
681 let (h_fields, j_couplings) = self.build_tsp_hamiltonian();
683
684 let cost_hamiltonian = CostHamiltonian::Ising {
685 h: h_fields,
686 j: j_couplings,
687 };
688 let mixer_hamiltonian = MixerHamiltonian::TransverseField;
689 let params = QAOAParams::random(layers);
690
691 self.circuit = Some(QAOACircuit::new(
692 num_qubits,
693 cost_hamiltonian,
694 mixer_hamiltonian,
695 params,
696 ));
697
698 self
699 }
700
701 fn build_tsp_hamiltonian(&self) -> (Vec<f64>, Vec<((usize, usize), f64)>) {
703 let n = self.num_cities;
704 let num_qubits = n * n;
705
706 let mut h_fields = vec![0.0; num_qubits];
707 let mut j_couplings = Vec::new();
708
709 let penalty_strength = 10.0; for i in 0..n {
713 for t in 0..n {
714 let t_next = (t + 1) % n;
715 for j in 0..n {
716 if i != j {
717 let qubit_it = i * n + t;
718 let qubit_jt_next = j * n + t_next;
719
720 j_couplings.push(((qubit_it, qubit_jt_next), self.distances[i][j] / 4.0));
722 }
723 }
724 }
725 }
726
727 for i in 0..n {
729 for t in 0..n {
731 let qubit = i * n + t;
732 h_fields[qubit] -= penalty_strength;
733 }
734
735 for t1 in 0..n {
737 for t2 in t1 + 1..n {
738 let qubit1 = i * n + t1;
739 let qubit2 = i * n + t2;
740 j_couplings.push(((qubit1, qubit2), penalty_strength));
741 }
742 }
743 }
744
745 for t in 0..n {
747 for i in 0..n {
749 let qubit = i * n + t;
750 h_fields[qubit] -= penalty_strength;
751 }
752
753 for i1 in 0..n {
755 for i2 in i1 + 1..n {
756 let qubit1 = i1 * n + t;
757 let qubit2 = i2 * n + t;
758 j_couplings.push(((qubit1, qubit2), penalty_strength));
759 }
760 }
761 }
762
763 (h_fields, j_couplings)
764 }
765
766 pub fn solve(&mut self) -> (Vec<usize>, f64) {
768 if self.circuit.is_none() {
769 self.build_circuit(3); }
771
772 let circuit = self
774 .circuit
775 .as_mut()
776 .expect("Circuit should be built after build_circuit call");
777 let mut optimizer = QAOAOptimizer::new(circuit.clone(), 200, 1e-6);
778
779 let (_, _, _best_expectation) = optimizer.optimize();
780 let final_state = optimizer.execute_circuit();
781 let bit_solution = optimizer.get_solution(&final_state);
782
783 let route = self.decode_tsp_solution(&bit_solution);
785 let distance = self.evaluate_route(&route);
786
787 (route, distance)
788 }
789
790 fn decode_tsp_solution(&self, bits: &[bool]) -> Vec<usize> {
792 let n = self.num_cities;
793 let mut route = Vec::new();
794
795 for t in 0..n {
796 let mut city_at_time_t = None;
797 let mut _max_confidence = 0;
798
799 for i in 0..n {
801 let qubit_idx = i * n + t;
802 if qubit_idx < bits.len() && bits[qubit_idx] {
803 _max_confidence += 1;
804 city_at_time_t = Some(i);
805 }
806 }
807
808 if city_at_time_t.is_none() {
810 for i in 0..n {
811 if !route.contains(&i) {
812 city_at_time_t = Some(i);
813 break;
814 }
815 }
816 }
817
818 if let Some(city) = city_at_time_t {
819 if !route.contains(&city) {
820 route.push(city);
821 }
822 }
823 }
824
825 for i in 0..n {
827 if !route.contains(&i) {
828 route.push(i);
829 }
830 }
831
832 route
833 }
834
835 pub fn evaluate_route(&self, route: &[usize]) -> f64 {
837 let mut total_distance = 0.0;
838
839 for i in 0..route.len() {
840 let current_city = route[i];
841 let next_city = route[(i + 1) % route.len()];
842 total_distance += self.distances[current_city][next_city];
843 }
844
845 total_distance
846 }
847
848 pub fn random_instance(num_cities: usize) -> Self {
850 let mut distances = vec![vec![0.0; num_cities]; num_cities];
851
852 for i in 0..num_cities {
853 for j in 0..num_cities {
854 if i != j {
855 let dist = ((i * j + i + j) as f64)
857 .mul_add(1.234, 5.678)
858 .sin()
859 .abs()
860 .mul_add(100.0, 1.0);
861 distances[i][j] = dist;
862 distances[j][i] = dist;
863 }
864 }
865 }
866
867 Self::new(distances)
868 }
869}
870
871#[cfg(test)]
872mod tests {
873 use super::*;
874
875 #[test]
876 fn test_qaoa_params() {
877 let params = QAOAParams::new(3);
878 assert_eq!(params.layers, 3);
879 assert_eq!(params.beta.len(), 3);
880 assert_eq!(params.gamma.len(), 3);
881
882 let random_params = QAOAParams::random(2);
883 assert_eq!(random_params.layers, 2);
884 assert!(random_params.beta.iter().all(|&x| x >= 0.0 && x <= PI));
885 }
886
887 #[test]
888 fn test_maxcut_qaoa_simple() {
889 let graph = vec![
891 vec![1, 2], vec![0, 2], vec![0, 1], ];
895
896 let mut maxcut = MaxCutQAOA::new(graph);
897 maxcut.build_circuit(1);
898
899 let (solution, _expectation) = maxcut.solve();
900 assert_eq!(solution.len(), 3);
901
902 let cut_value = maxcut.evaluate_cut(&solution);
904 println!(
905 "Triangle MaxCut solution: {:?}, value: {}",
906 solution, cut_value
907 );
908
909 let expected_cut_values = [0.0, 2.0]; assert!(expected_cut_values.contains(&cut_value) || cut_value == 1.0);
912 }
913
914 #[test]
915 fn test_maxcut_weighted() {
916 let graph = vec![vec![1], vec![0]];
917
918 let weights = vec![vec![0.0, 5.0], vec![5.0, 0.0]];
919
920 let mut maxcut = MaxCutQAOA::new(graph).with_weights(weights);
921 maxcut.build_circuit(1);
922
923 let (solution, _expectation) = maxcut.solve();
924 let cut_value = maxcut.evaluate_cut(&solution);
925
926 println!(
928 "Weighted MaxCut solution: {:?}, value: {}",
929 solution, cut_value
930 );
931 assert!(cut_value >= 0.0);
932 }
933
934 #[test]
935 fn test_maxcut_random_graph() {
936 let maxcut = MaxCutQAOA::random_graph(4, 0.5);
937 assert_eq!(maxcut.num_vertices, 4);
938 println!("Random graph: {:?}", maxcut.graph);
939 }
940
941 #[test]
942 fn test_tsp_qaoa_simple() {
943 let distances = vec![
945 vec![0.0, 1.0, 2.0],
946 vec![1.0, 0.0, 1.5],
947 vec![2.0, 1.5, 0.0],
948 ];
949
950 let mut tsp = TSPQAOA::new(distances);
951 tsp.build_circuit(2);
952
953 let (route, distance) = tsp.solve();
954 assert_eq!(route.len(), 3);
955
956 let mut sorted_route = route.clone();
958 sorted_route.sort();
959 assert_eq!(sorted_route, vec![0, 1, 2]);
960
961 println!("TSP route: {:?}, distance: {}", route, distance);
962 assert!(distance > 0.0);
963 }
964
965 #[test]
966 fn test_tsp_evaluation() {
967 let distances = vec![
968 vec![0.0, 1.0, 3.0],
969 vec![1.0, 0.0, 2.0],
970 vec![3.0, 2.0, 0.0],
971 ];
972
973 let tsp = TSPQAOA::new(distances);
974
975 let route = vec![0, 1, 2];
977 let distance = tsp.evaluate_route(&route);
978
979 assert_eq!(distance, 6.0);
981 }
982
983 #[test]
984 fn test_tsp_random_instance() {
985 let tsp = TSPQAOA::random_instance(4);
986 assert_eq!(tsp.num_cities, 4);
987 assert_eq!(tsp.distances.len(), 4);
988
989 for i in 0..4 {
991 for j in 0..4 {
992 if i != j {
993 assert_eq!(tsp.distances[i][j], tsp.distances[j][i]);
994 }
995 }
996 }
997 }
998
999 #[test]
1000 fn test_qaoa_circuit_execution() {
1001 let edges = vec![(0, 1), (1, 2)];
1002 let cost_hamiltonian = CostHamiltonian::MaxCut(edges);
1003 let mixer_hamiltonian = MixerHamiltonian::TransverseField;
1004 let params = QAOAParams::new(1);
1005
1006 let circuit = QAOACircuit::new(3, cost_hamiltonian, mixer_hamiltonian, params);
1007
1008 let state_size = 1 << 3;
1009 let mut state = vec![Complex64::new(0.0, 0.0); state_size];
1010
1011 circuit.execute(&mut state);
1012
1013 let norm_sq: f64 = state.iter().map(|c| c.norm_sqr()).sum();
1015 assert!((norm_sq - 1.0).abs() < 1e-10);
1016 }
1017
1018 #[test]
1019 fn test_qaoa_optimizer_simple() {
1020 let edges = vec![(0, 1)];
1021 let cost_hamiltonian = CostHamiltonian::MaxCut(edges);
1022 let mixer_hamiltonian = MixerHamiltonian::TransverseField;
1023 let params = QAOAParams::random(1);
1024
1025 let circuit = QAOACircuit::new(2, cost_hamiltonian, mixer_hamiltonian, params);
1026 let mut optimizer = QAOAOptimizer::new(circuit, 10, 1e-6);
1027
1028 let (_beta, _gamma, expectation) = optimizer.optimize();
1029
1030 assert!(expectation.is_finite());
1032 println!("QAOA optimizer result: expectation = {}", expectation);
1033 }
1034
1035 #[test]
1036 fn test_custom_mixer_hamiltonian() {
1037 use scirs2_core::ndarray::array;
1038
1039 let pauli_x = array![
1042 [Complex64::new(0.0, 0.0), Complex64::new(1.0, 0.0)],
1043 [Complex64::new(1.0, 0.0), Complex64::new(0.0, 0.0)]
1044 ];
1045
1046 let custom_mixers = vec![pauli_x.clone(), pauli_x];
1048
1049 let edges = vec![(0, 1)];
1050 let cost_hamiltonian = CostHamiltonian::MaxCut(edges);
1051 let mixer_hamiltonian = MixerHamiltonian::Custom(custom_mixers);
1052 let params = QAOAParams::new(1);
1053
1054 let circuit = QAOACircuit::new(2, cost_hamiltonian, mixer_hamiltonian, params);
1055
1056 let state_size = 1 << 2;
1057 let mut state = vec![Complex64::new(0.0, 0.0); state_size];
1058
1059 circuit.execute(&mut state);
1061
1062 let norm_sq: f64 = state.iter().map(|c| c.norm_sqr()).sum();
1064 assert!((norm_sq - 1.0).abs() < 1e-10);
1065
1066 println!("Custom mixer QAOA state: {:?}", state);
1067 }
1068
1069 #[test]
1070 fn test_custom_mixer_vs_standard() {
1071 use scirs2_core::ndarray::array;
1072
1073 let edges = vec![(0, 1)];
1075 let cost_hamiltonian1 = CostHamiltonian::MaxCut(edges.clone());
1076 let cost_hamiltonian2 = CostHamiltonian::MaxCut(edges);
1077
1078 let mixer1 = MixerHamiltonian::TransverseField;
1080
1081 let pauli_x = array![
1083 [Complex64::new(0.0, 0.0), Complex64::new(1.0, 0.0)],
1084 [Complex64::new(1.0, 0.0), Complex64::new(0.0, 0.0)]
1085 ];
1086 let custom_mixers = vec![pauli_x.clone(), pauli_x];
1087 let mixer2 = MixerHamiltonian::Custom(custom_mixers);
1088
1089 let params = QAOAParams::new(1);
1090
1091 let circuit1 = QAOACircuit::new(2, cost_hamiltonian1, mixer1, params.clone());
1092 let circuit2 = QAOACircuit::new(2, cost_hamiltonian2, mixer2, params);
1093
1094 let state_size = 1 << 2;
1095 let mut state1 = vec![Complex64::new(0.0, 0.0); state_size];
1096 let mut state2 = vec![Complex64::new(0.0, 0.0); state_size];
1097
1098 circuit1.execute(&mut state1);
1099 circuit2.execute(&mut state2);
1100
1101 println!("Standard mixer state: {:?}", state1);
1103 println!("Custom mixer state: {:?}", state2);
1104
1105 let norm1: f64 = state1.iter().map(|c| c.norm_sqr()).sum();
1107 let norm2: f64 = state2.iter().map(|c| c.norm_sqr()).sum();
1108 assert!((norm1 - 1.0).abs() < 1e-10);
1109 assert!((norm2 - 1.0).abs() < 1e-10);
1110 }
1111}