1use scirs2_core::random::prelude::*;
16use scirs2_core::random::ChaCha8Rng;
17use scirs2_core::random::{Rng, SeedableRng};
18use std::collections::HashMap;
19use std::time::{Duration, Instant};
20use thiserror::Error;
21
22use crate::ising::{IsingError, IsingModel, QuboModel};
23
24#[derive(Error, Debug)]
26pub enum QaoaError {
27 #[error("Ising error: {0}")]
29 IsingError(#[from] IsingError),
30
31 #[error("Invalid parameters: {0}")]
33 InvalidParameters(String),
34
35 #[error("Circuit error: {0}")]
37 CircuitError(String),
38
39 #[error("Optimization failed: {0}")]
41 OptimizationFailed(String),
42
43 #[error("Simulation error: {0}")]
45 SimulationError(String),
46
47 #[error("Convergence error: {0}")]
49 ConvergenceError(String),
50}
51
52pub type QaoaResult<T> = Result<T, QaoaError>;
54
55#[derive(Debug, Clone, PartialEq)]
57pub enum QaoaVariant {
58 Standard {
60 layers: usize,
62 },
63
64 QaoaPlus {
66 layers: usize,
68 multi_angle: bool,
70 },
71
72 MultiAngle {
74 layers: usize,
76 angles_per_layer: usize,
78 },
79
80 WarmStart {
82 layers: usize,
84 initial_solution: Vec<i8>,
86 },
87
88 Recursive {
90 max_layers: usize,
92 correlation_threshold: f64,
94 },
95}
96
97#[derive(Debug, Clone, PartialEq)]
99pub enum MixerType {
100 XMixer,
102
103 XYMixer,
105
106 RingMixer,
108
109 Custom {
111 terms: Vec<(Vec<usize>, f64)>,
113 },
114
115 GroverMixer,
117}
118
119#[derive(Debug, Clone, PartialEq)]
121pub enum ProblemEncoding {
122 Ising,
124
125 Qubo { use_slack_variables: bool },
127
128 PenaltyMethod { penalty_weight: f64 },
130
131 ConstraintPreserving,
133}
134
135#[derive(Debug, Clone)]
137pub enum QaoaClassicalOptimizer {
138 NelderMead {
140 initial_size: f64,
142 tolerance: f64,
144 max_iterations: usize,
146 },
147
148 Cobyla {
150 rhobeg: f64,
152 rhoend: f64,
154 maxfun: usize,
156 },
157
158 Powell {
160 tolerance: f64,
162 max_iterations: usize,
164 },
165
166 GradientBased {
168 learning_rate: f64,
170 gradient_step: f64,
172 max_iterations: usize,
174 },
175
176 BasinHopping {
178 n_iterations: usize,
180 temperature: f64,
182 local_optimizer: Box<Self>,
184 },
185}
186
187#[derive(Debug, Clone)]
189pub struct QaoaConfig {
190 pub variant: QaoaVariant,
192
193 pub mixer_type: MixerType,
195
196 pub problem_encoding: ProblemEncoding,
198
199 pub optimizer: QaoaClassicalOptimizer,
201
202 pub num_shots: usize,
204
205 pub parameter_init: ParameterInitialization,
207
208 pub convergence_tolerance: f64,
210
211 pub max_optimization_time: Option<Duration>,
213
214 pub seed: Option<u64>,
216
217 pub detailed_logging: bool,
219
220 pub track_optimization_history: bool,
222
223 pub max_circuit_depth: Option<usize>,
225
226 pub use_symmetry_reduction: bool,
228}
229
230impl Default for QaoaConfig {
231 fn default() -> Self {
232 Self {
233 variant: QaoaVariant::Standard { layers: 1 },
234 mixer_type: MixerType::XMixer,
235 problem_encoding: ProblemEncoding::Ising,
236 optimizer: QaoaClassicalOptimizer::NelderMead {
237 initial_size: 0.5,
238 tolerance: 1e-6,
239 max_iterations: 1000,
240 },
241 num_shots: 1000,
242 parameter_init: ParameterInitialization::Random {
243 range: (-std::f64::consts::PI, std::f64::consts::PI),
244 },
245 convergence_tolerance: 1e-6,
246 max_optimization_time: Some(Duration::from_secs(3600)),
247 seed: None,
248 detailed_logging: false,
249 track_optimization_history: true,
250 max_circuit_depth: None,
251 use_symmetry_reduction: false,
252 }
253 }
254}
255
256#[derive(Debug, Clone)]
258pub enum ParameterInitialization {
259 Random { range: (f64, f64) },
261
262 Linear { gamma_max: f64, beta_max: f64 },
264
265 ProblemAware,
267
268 WarmStart { solution: Vec<i8> },
270
271 TransferLearning { previous_parameters: Vec<f64> },
273}
274
275#[derive(Debug, Clone)]
277pub struct QaoaCircuit {
278 pub num_qubits: usize,
280
281 pub layers: Vec<QaoaLayer>,
283
284 pub parameters: Vec<f64>,
286
287 pub depth: usize,
289}
290
291#[derive(Debug, Clone)]
293pub struct QaoaLayer {
294 pub problem_gates: Vec<QuantumGate>,
296
297 pub mixer_gates: Vec<QuantumGate>,
299
300 pub gamma: f64,
302 pub beta: f64,
303}
304
305#[derive(Debug, Clone, PartialEq)]
307pub enum QuantumGate {
308 RX { qubit: usize, angle: f64 },
310
311 RY { qubit: usize, angle: f64 },
313
314 RZ { qubit: usize, angle: f64 },
316
317 CNOT { control: usize, target: usize },
319
320 CZ { control: usize, target: usize },
322
323 ZZ {
325 qubit1: usize,
326 qubit2: usize,
327 angle: f64,
328 },
329
330 H { qubit: usize },
332
333 Measure { qubit: usize },
335}
336
337#[derive(Debug, Clone)]
339pub struct QaoaResults {
340 pub best_solution: Vec<i8>,
342
343 pub best_energy: f64,
345
346 pub optimal_parameters: Vec<f64>,
348
349 pub energy_history: Vec<f64>,
351
352 pub parameter_history: Vec<Vec<f64>>,
354
355 pub function_evaluations: usize,
357
358 pub converged: bool,
360
361 pub optimization_time: Duration,
363
364 pub approximation_ratio: f64,
366
367 pub circuit_stats: QaoaCircuitStats,
369
370 pub quantum_stats: QuantumStateStats,
372
373 pub performance_metrics: QaoaPerformanceMetrics,
375}
376
377#[derive(Debug, Clone)]
379pub struct QaoaCircuitStats {
380 pub total_depth: usize,
382
383 pub two_qubit_gates: usize,
385
386 pub single_qubit_gates: usize,
388
389 pub estimated_fidelity: f64,
391
392 pub gate_counts: HashMap<String, usize>,
394}
395
396#[derive(Debug, Clone)]
398pub struct QuantumStateStats {
399 pub optimal_overlap: f64,
401
402 pub entanglement_entropy: Vec<f64>,
404
405 pub concentration_ratio: f64,
407
408 pub expectation_variance: f64,
410}
411
412#[derive(Debug, Clone)]
414pub struct QaoaPerformanceMetrics {
415 pub success_probability: f64,
417
418 pub relative_energy: f64,
420
421 pub parameter_sensitivity: Vec<f64>,
423
424 pub optimization_efficiency: f64,
426
427 pub preprocessing_time: Duration,
429
430 pub quantum_simulation_time: Duration,
432}
433
434#[derive(Debug, Clone)]
436pub struct QuantumState {
437 pub amplitudes: Vec<complex::Complex64>,
439
440 pub num_qubits: usize,
442}
443
444pub mod complex {
446 #[derive(Debug, Clone, Copy, PartialEq)]
447 pub struct Complex64 {
448 pub re: f64,
449 pub im: f64,
450 }
451
452 impl Complex64 {
453 #[must_use]
454 pub const fn new(re: f64, im: f64) -> Self {
455 Self { re, im }
456 }
457
458 #[must_use]
459 pub fn norm_squared(&self) -> f64 {
460 self.re.mul_add(self.re, self.im * self.im)
461 }
462
463 #[must_use]
464 pub fn abs(&self) -> f64 {
465 self.re.hypot(self.im)
466 }
467
468 #[must_use]
469 pub fn conj(&self) -> Self {
470 Self {
471 re: self.re,
472 im: -self.im,
473 }
474 }
475 }
476
477 impl std::ops::Add for Complex64 {
478 type Output = Self;
479 fn add(self, rhs: Self) -> Self {
480 Self {
481 re: self.re + rhs.re,
482 im: self.im + rhs.im,
483 }
484 }
485 }
486
487 impl std::ops::Mul for Complex64 {
488 type Output = Self;
489 fn mul(self, rhs: Self) -> Self {
490 Self {
491 re: self.re.mul_add(rhs.re, -(self.im * rhs.im)),
492 im: self.re.mul_add(rhs.im, self.im * rhs.re),
493 }
494 }
495 }
496
497 impl std::ops::Mul<f64> for Complex64 {
498 type Output = Self;
499 fn mul(self, rhs: f64) -> Self {
500 Self {
501 re: self.re * rhs,
502 im: self.im * rhs,
503 }
504 }
505 }
506}
507
508impl QuantumState {
509 #[must_use]
511 pub fn new(num_qubits: usize) -> Self {
512 let mut amplitudes = vec![complex::Complex64::new(0.0, 0.0); 1 << num_qubits];
513 amplitudes[0] = complex::Complex64::new(1.0, 0.0); Self {
516 amplitudes,
517 num_qubits,
518 }
519 }
520
521 #[must_use]
523 pub fn uniform_superposition(num_qubits: usize) -> Self {
524 let amplitude = (1.0 / f64::from(1 << num_qubits)).sqrt();
525 let amplitudes = vec![complex::Complex64::new(amplitude, 0.0); 1 << num_qubits];
526
527 Self {
528 amplitudes,
529 num_qubits,
530 }
531 }
532
533 #[must_use]
535 pub fn get_probability(&self, bitstring: usize) -> f64 {
536 if bitstring < self.amplitudes.len() {
537 self.amplitudes[bitstring].norm_squared()
538 } else {
539 0.0
540 }
541 }
542
543 pub fn sample(&self, rng: &mut ChaCha8Rng) -> usize {
545 let random_value: f64 = rng.gen();
546 let mut cumulative_prob = 0.0;
547
548 for (i, amplitude) in self.amplitudes.iter().enumerate() {
549 cumulative_prob += amplitude.norm_squared();
550 if random_value <= cumulative_prob {
551 return i;
552 }
553 }
554
555 self.amplitudes.len() - 1
557 }
558
559 #[must_use]
561 pub fn bitstring_to_spins(&self, bitstring: usize) -> Vec<i8> {
562 let mut spins = Vec::new();
563 for i in 0..self.num_qubits {
564 if (bitstring >> i) & 1 == 1 {
565 spins.push(1);
566 } else {
567 spins.push(-1);
568 }
569 }
570 spins.reverse(); spins
572 }
573
574 #[must_use]
576 pub fn expectation_z(&self, qubit: usize) -> f64 {
577 let mut expectation = 0.0;
578
579 for (bitstring, amplitude) in self.amplitudes.iter().enumerate() {
580 let probability = amplitude.norm_squared();
581 let bit_value = (bitstring >> qubit) & 1;
582 let spin_value = if bit_value == 1 { 1.0 } else { -1.0 };
583 expectation += probability * spin_value;
584 }
585
586 expectation
587 }
588
589 #[must_use]
591 pub fn expectation_zz(&self, qubit1: usize, qubit2: usize) -> f64 {
592 let mut expectation = 0.0;
593
594 for (bitstring, amplitude) in self.amplitudes.iter().enumerate() {
595 let probability = amplitude.norm_squared();
596 let bit1 = (bitstring >> qubit1) & 1;
597 let bit2 = (bitstring >> qubit2) & 1;
598 let spin1 = if bit1 == 1 { 1.0 } else { -1.0 };
599 let spin2 = if bit2 == 1 { 1.0 } else { -1.0 };
600 expectation += probability * spin1 * spin2;
601 }
602
603 expectation
604 }
605}
606
607pub struct QaoaOptimizer {
609 config: QaoaConfig,
611
612 rng: ChaCha8Rng,
614
615 quantum_state: QuantumState,
617
618 optimization_history: OptimizationHistory,
620
621 current_circuit: Option<QaoaCircuit>,
623}
624
625#[derive(Debug)]
627struct OptimizationHistory {
628 energies: Vec<f64>,
629 parameters: Vec<Vec<f64>>,
630 function_evaluations: usize,
631 start_time: Instant,
632}
633
634impl QaoaOptimizer {
635 pub fn new(config: QaoaConfig) -> QaoaResult<Self> {
637 let rng = match config.seed {
638 Some(seed) => ChaCha8Rng::seed_from_u64(seed),
639 None => ChaCha8Rng::seed_from_u64(thread_rng().gen()),
640 };
641
642 let quantum_state = QuantumState::new(1);
644
645 Ok(Self {
646 config,
647 rng,
648 quantum_state,
649 optimization_history: OptimizationHistory {
650 energies: Vec::new(),
651 parameters: Vec::new(),
652 function_evaluations: 0,
653 start_time: Instant::now(),
654 },
655 current_circuit: None,
656 })
657 }
658
659 pub fn solve(&mut self, problem: &IsingModel) -> QaoaResult<QaoaResults> {
661 println!("Starting QAOA optimization...");
662 let start_time = Instant::now();
663
664 self.quantum_state = QuantumState::uniform_superposition(problem.num_qubits);
666 self.optimization_history.start_time = start_time;
667
668 let initial_parameters = self.initialize_parameters(problem)?;
670
671 let circuit = self.build_qaoa_circuit(problem, &initial_parameters)?;
673 self.current_circuit = Some(circuit);
674
675 let optimization_result = self.optimize_parameters(problem, initial_parameters)?;
677
678 let optimization_time = start_time.elapsed();
679
680 let final_state =
682 self.simulate_qaoa_circuit(problem, &optimization_result.optimal_parameters)?;
683 let (best_solution, best_energy) = self.extract_best_solution(problem, &final_state)?;
684
685 let approximation_ratio = self.calculate_approximation_ratio(best_energy, problem);
687 let circuit_stats = self.calculate_circuit_stats(
688 self.current_circuit
689 .as_ref()
690 .ok_or_else(|| QaoaError::CircuitError("Circuit not initialized".to_string()))?,
691 );
692 let quantum_stats = self.calculate_quantum_stats(&final_state, problem);
693 let performance_metrics = self.calculate_performance_metrics(
694 &optimization_result,
695 best_energy,
696 optimization_time,
697 );
698
699 println!("QAOA optimization completed in {optimization_time:.2?}");
700 println!("Best energy: {best_energy:.6}, Approximation ratio: {approximation_ratio:.3}");
701
702 Ok(QaoaResults {
703 best_solution,
704 best_energy,
705 optimal_parameters: optimization_result.optimal_parameters,
706 energy_history: self.optimization_history.energies.clone(),
707 parameter_history: self.optimization_history.parameters.clone(),
708 function_evaluations: self.optimization_history.function_evaluations,
709 converged: optimization_result.converged,
710 optimization_time,
711 approximation_ratio,
712 circuit_stats,
713 quantum_stats,
714 performance_metrics,
715 })
716 }
717
718 fn initialize_parameters(&mut self, problem: &IsingModel) -> QaoaResult<Vec<f64>> {
720 let num_parameters = self.get_num_parameters();
721 let mut parameters = vec![0.0; num_parameters];
722
723 let param_init = self.config.parameter_init.clone();
725
726 match param_init {
727 ParameterInitialization::Random { range } => {
728 for param in &mut parameters {
729 *param = self.rng.gen_range(range.0..range.1);
730 }
731 }
732
733 ParameterInitialization::Linear {
734 gamma_max,
735 beta_max,
736 } => {
737 for i in 0..num_parameters {
739 if i % 2 == 0 {
740 let layer = i / 2;
742 parameters[i] =
743 gamma_max * (layer + 1) as f64 / self.get_num_layers() as f64;
744 } else {
745 parameters[i] = beta_max;
747 }
748 }
749 }
750
751 ParameterInitialization::ProblemAware => {
752 self.initialize_problem_aware_parameters(&mut parameters, problem)?;
754 }
755
756 ParameterInitialization::WarmStart { solution } => {
757 self.initialize_warm_start_parameters(&mut parameters, &solution)?;
759 }
760
761 ParameterInitialization::TransferLearning {
762 previous_parameters,
763 } => {
764 for (i, &prev_param) in previous_parameters.iter().enumerate() {
766 if i < parameters.len() {
767 parameters[i] = prev_param;
768 }
769 }
770 }
771 }
772
773 Ok(parameters)
774 }
775
776 fn get_num_parameters(&self) -> usize {
778 match &self.config.variant {
779 QaoaVariant::Standard { layers } => layers * 2,
780 QaoaVariant::QaoaPlus {
781 layers,
782 multi_angle,
783 } => {
784 if *multi_angle {
785 layers * 4 } else {
787 layers * 2
788 }
789 }
790 QaoaVariant::MultiAngle {
791 layers,
792 angles_per_layer,
793 } => layers * angles_per_layer,
794 QaoaVariant::WarmStart { layers, .. } => layers * 2,
795 QaoaVariant::Recursive { max_layers, .. } => max_layers * 2,
796 }
797 }
798
799 const fn get_num_layers(&self) -> usize {
801 match &self.config.variant {
802 QaoaVariant::Standard { layers }
803 | QaoaVariant::QaoaPlus { layers, .. }
804 | QaoaVariant::MultiAngle { layers, .. }
805 | QaoaVariant::WarmStart { layers, .. } => *layers,
806 QaoaVariant::Recursive { max_layers, .. } => *max_layers,
807 }
808 }
809
810 fn initialize_problem_aware_parameters(
812 &self,
813 parameters: &mut [f64],
814 problem: &IsingModel,
815 ) -> QaoaResult<()> {
816 let coupling_strength = self.analyze_coupling_strength(problem);
818 let bias_strength = self.analyze_bias_strength(problem);
819
820 let num_layers = self.get_num_layers();
821
822 for layer in 0..num_layers {
823 let gamma_idx = layer * 2;
824 let beta_idx = layer * 2 + 1;
825
826 if gamma_idx < parameters.len() {
827 parameters[gamma_idx] = coupling_strength * (layer + 1) as f64 / num_layers as f64;
829 }
830
831 if beta_idx < parameters.len() {
832 parameters[beta_idx] = std::f64::consts::PI / 2.0 * bias_strength;
834 }
835 }
836
837 Ok(())
838 }
839
840 fn analyze_coupling_strength(&self, problem: &IsingModel) -> f64 {
842 let mut total_coupling = 0.0;
843 let mut num_couplings = 0;
844
845 for i in 0..problem.num_qubits {
846 for j in (i + 1)..problem.num_qubits {
847 if let Ok(coupling) = problem.get_coupling(i, j) {
848 if coupling != 0.0 {
849 total_coupling += coupling.abs();
850 num_couplings += 1;
851 }
852 }
853 }
854 }
855
856 if num_couplings > 0 {
857 total_coupling / f64::from(num_couplings)
858 } else {
859 1.0
860 }
861 }
862
863 fn analyze_bias_strength(&self, problem: &IsingModel) -> f64 {
865 let mut total_bias = 0.0;
866 let mut num_biases = 0;
867
868 for i in 0..problem.num_qubits {
869 if let Ok(bias) = problem.get_bias(i) {
870 if bias != 0.0 {
871 total_bias += bias.abs();
872 num_biases += 1;
873 }
874 }
875 }
876
877 if num_biases > 0 {
878 total_bias / f64::from(num_biases)
879 } else {
880 1.0
881 }
882 }
883
884 fn initialize_warm_start_parameters(
886 &self,
887 parameters: &mut [f64],
888 solution: &[i8],
889 ) -> QaoaResult<()> {
890 for i in 0..parameters.len() {
895 if i % 2 == 0 {
896 parameters[i] = 0.1;
898 } else {
899 parameters[i] = std::f64::consts::PI / 4.0;
901 }
902 }
903
904 Ok(())
905 }
906
907 fn build_qaoa_circuit(
909 &self,
910 problem: &IsingModel,
911 parameters: &[f64],
912 ) -> QaoaResult<QaoaCircuit> {
913 let num_qubits = problem.num_qubits;
914 let num_layers = self.get_num_layers();
915 let mut layers = Vec::new();
916
917 for layer in 0..num_layers {
918 let gamma_idx = layer * 2;
919 let beta_idx = layer * 2 + 1;
920
921 let gamma = if gamma_idx < parameters.len() {
922 parameters[gamma_idx]
923 } else {
924 0.0
925 };
926 let beta = if beta_idx < parameters.len() {
927 parameters[beta_idx]
928 } else {
929 0.0
930 };
931
932 let problem_gates = self.build_problem_hamiltonian_gates(problem, gamma)?;
934
935 let mixer_gates = self.build_mixer_hamiltonian_gates(num_qubits, beta)?;
937
938 layers.push(QaoaLayer {
939 problem_gates,
940 mixer_gates,
941 gamma,
942 beta,
943 });
944 }
945
946 let depth = self.calculate_circuit_depth(&layers);
947
948 Ok(QaoaCircuit {
949 num_qubits,
950 layers,
951 parameters: parameters.to_vec(),
952 depth,
953 })
954 }
955
956 fn build_problem_hamiltonian_gates(
958 &self,
959 problem: &IsingModel,
960 gamma: f64,
961 ) -> QaoaResult<Vec<QuantumGate>> {
962 let mut gates = Vec::new();
963
964 for i in 0..problem.num_qubits {
966 if let Ok(bias) = problem.get_bias(i) {
967 if bias != 0.0 {
968 gates.push(QuantumGate::RZ {
969 qubit: i,
970 angle: gamma * bias,
971 });
972 }
973 }
974 }
975
976 for i in 0..problem.num_qubits {
978 for j in (i + 1)..problem.num_qubits {
979 if let Ok(coupling) = problem.get_coupling(i, j) {
980 if coupling != 0.0 {
981 gates.push(QuantumGate::ZZ {
982 qubit1: i,
983 qubit2: j,
984 angle: gamma * coupling,
985 });
986 }
987 }
988 }
989 }
990
991 Ok(gates)
992 }
993
994 fn build_mixer_hamiltonian_gates(
996 &self,
997 num_qubits: usize,
998 beta: f64,
999 ) -> QaoaResult<Vec<QuantumGate>> {
1000 let mut gates = Vec::new();
1001
1002 match &self.config.mixer_type {
1003 MixerType::XMixer => {
1004 for qubit in 0..num_qubits {
1006 gates.push(QuantumGate::RX {
1007 qubit,
1008 angle: 2.0 * beta,
1009 });
1010 }
1011 }
1012
1013 MixerType::XYMixer => {
1014 for qubit in 0..num_qubits - 1 {
1016 gates.push(QuantumGate::CNOT {
1018 control: qubit,
1019 target: qubit + 1,
1020 });
1021 gates.push(QuantumGate::RZ {
1022 qubit: qubit + 1,
1023 angle: beta,
1024 });
1025 gates.push(QuantumGate::CNOT {
1026 control: qubit,
1027 target: qubit + 1,
1028 });
1029 }
1030 }
1031
1032 MixerType::RingMixer => {
1033 for qubit in 0..num_qubits {
1035 let next_qubit = (qubit + 1) % num_qubits;
1036 gates.push(QuantumGate::ZZ {
1037 qubit1: qubit,
1038 qubit2: next_qubit,
1039 angle: beta,
1040 });
1041 }
1042 }
1043
1044 MixerType::Custom { terms } => {
1045 for (qubits, coefficient) in terms {
1047 if qubits.len() == 1 {
1048 gates.push(QuantumGate::RX {
1049 qubit: qubits[0],
1050 angle: 2.0 * beta * coefficient,
1051 });
1052 } else if qubits.len() == 2 {
1053 gates.push(QuantumGate::ZZ {
1054 qubit1: qubits[0],
1055 qubit2: qubits[1],
1056 angle: beta * coefficient,
1057 });
1058 }
1059 }
1060 }
1061
1062 MixerType::GroverMixer => {
1063 for qubit in 0..num_qubits {
1065 gates.push(QuantumGate::H { qubit });
1066 gates.push(QuantumGate::RZ {
1067 qubit,
1068 angle: 2.0 * beta,
1069 });
1070 gates.push(QuantumGate::H { qubit });
1071 }
1072 }
1073 }
1074
1075 Ok(gates)
1076 }
1077
1078 const fn calculate_circuit_depth(&self, layers: &[QaoaLayer]) -> usize {
1080 layers.len() * 2 }
1082
1083 fn optimize_parameters(
1085 &mut self,
1086 problem: &IsingModel,
1087 initial_parameters: Vec<f64>,
1088 ) -> QaoaResult<OptimizationResult> {
1089 match &self.config.optimizer {
1090 QaoaClassicalOptimizer::NelderMead {
1091 initial_size,
1092 tolerance,
1093 max_iterations,
1094 } => self.optimize_nelder_mead(
1095 problem,
1096 initial_parameters,
1097 *initial_size,
1098 *tolerance,
1099 *max_iterations,
1100 ),
1101
1102 QaoaClassicalOptimizer::GradientBased {
1103 learning_rate,
1104 gradient_step,
1105 max_iterations,
1106 } => self.optimize_gradient_based(
1107 problem,
1108 initial_parameters,
1109 *learning_rate,
1110 *gradient_step,
1111 *max_iterations,
1112 ),
1113
1114 _ => {
1115 self.optimize_simple_search(problem, initial_parameters)
1117 }
1118 }
1119 }
1120
1121 fn optimize_nelder_mead(
1123 &mut self,
1124 problem: &IsingModel,
1125 initial_parameters: Vec<f64>,
1126 initial_size: f64,
1127 tolerance: f64,
1128 max_iterations: usize,
1129 ) -> QaoaResult<OptimizationResult> {
1130 let n = initial_parameters.len();
1131
1132 let mut simplex = vec![initial_parameters.clone()];
1134 for i in 0..n {
1135 let mut vertex = initial_parameters.clone();
1136 vertex[i] += initial_size;
1137 simplex.push(vertex);
1138 }
1139
1140 let mut function_values = Vec::new();
1142 for vertex in &simplex {
1143 let energy = self.evaluate_qaoa_energy(problem, vertex)?;
1144 function_values.push(energy);
1145 }
1146
1147 let mut best_parameters = initial_parameters;
1148 let mut best_energy = f64::INFINITY;
1149 let mut converged = false;
1150
1151 for iteration in 0..max_iterations {
1152 if let Some(max_time) = self.config.max_optimization_time {
1154 if self.optimization_history.start_time.elapsed() > max_time {
1155 break;
1156 }
1157 }
1158
1159 let mut indices: Vec<usize> = (0..simplex.len()).collect();
1161 indices.sort_by(|&i, &j| {
1162 function_values[i]
1163 .partial_cmp(&function_values[j])
1164 .unwrap_or(std::cmp::Ordering::Equal)
1165 });
1166
1167 let best_idx = indices[0];
1168 let worst_idx = indices[n];
1169 let second_worst_idx = indices[n - 1];
1170
1171 if function_values[best_idx] < best_energy {
1173 best_energy = function_values[best_idx];
1174 best_parameters = simplex[best_idx].clone();
1175 }
1176
1177 let energy_range = function_values[worst_idx] - function_values[best_idx];
1179 if energy_range < tolerance {
1180 converged = true;
1181 break;
1182 }
1183
1184 let mut centroid = vec![0.0; n];
1186 for (i, vertex) in simplex.iter().enumerate() {
1187 if i != worst_idx {
1188 for j in 0..n {
1189 centroid[j] += vertex[j];
1190 }
1191 }
1192 }
1193 for j in 0..n {
1194 centroid[j] /= n as f64;
1195 }
1196
1197 let mut reflected = vec![0.0; n];
1199 for j in 0..n {
1200 reflected[j] = centroid[j] + (centroid[j] - simplex[worst_idx][j]);
1201 }
1202 let reflected_value = self.evaluate_qaoa_energy(problem, &reflected)?;
1203
1204 if function_values[best_idx] <= reflected_value
1205 && reflected_value < function_values[second_worst_idx]
1206 {
1207 simplex[worst_idx] = reflected;
1209 function_values[worst_idx] = reflected_value;
1210 } else if reflected_value < function_values[best_idx] {
1211 let mut expanded = vec![0.0; n];
1213 for j in 0..n {
1214 expanded[j] = 2.0f64.mul_add(reflected[j] - centroid[j], centroid[j]);
1215 }
1216 let expanded_value = self.evaluate_qaoa_energy(problem, &expanded)?;
1217
1218 if expanded_value < reflected_value {
1219 simplex[worst_idx] = expanded;
1220 function_values[worst_idx] = expanded_value;
1221 } else {
1222 simplex[worst_idx] = reflected;
1223 function_values[worst_idx] = reflected_value;
1224 }
1225 } else {
1226 let mut contracted = vec![0.0; n];
1228 for j in 0..n {
1229 contracted[j] =
1230 0.5f64.mul_add(simplex[worst_idx][j] - centroid[j], centroid[j]);
1231 }
1232 let contracted_value = self.evaluate_qaoa_energy(problem, &contracted)?;
1233
1234 if contracted_value < function_values[worst_idx] {
1235 simplex[worst_idx] = contracted;
1236 function_values[worst_idx] = contracted_value;
1237 } else {
1238 for i in 1..simplex.len() {
1240 for j in 0..n {
1241 simplex[i][j] = 0.5f64.mul_add(
1242 simplex[i][j] - simplex[best_idx][j],
1243 simplex[best_idx][j],
1244 );
1245 }
1246 function_values[i] = self.evaluate_qaoa_energy(problem, &simplex[i])?;
1247 }
1248 }
1249 }
1250
1251 if iteration % 10 == 0 && self.config.detailed_logging {
1253 println!("Nelder-Mead iter {iteration}: Best energy = {best_energy:.6}");
1254 }
1255 }
1256
1257 Ok(OptimizationResult {
1258 optimal_parameters: best_parameters,
1259 optimal_energy: best_energy,
1260 converged,
1261 iterations: max_iterations.min(self.optimization_history.function_evaluations),
1262 })
1263 }
1264
1265 fn optimize_gradient_based(
1267 &mut self,
1268 problem: &IsingModel,
1269 mut parameters: Vec<f64>,
1270 learning_rate: f64,
1271 gradient_step: f64,
1272 max_iterations: usize,
1273 ) -> QaoaResult<OptimizationResult> {
1274 let mut best_energy = f64::INFINITY;
1275 let mut best_parameters = parameters.clone();
1276 let mut converged = false;
1277
1278 for iteration in 0..max_iterations {
1279 let gradients =
1281 self.compute_finite_difference_gradients(problem, ¶meters, gradient_step)?;
1282
1283 for (i, grad) in gradients.iter().enumerate() {
1285 parameters[i] -= learning_rate * grad;
1286 }
1287
1288 let current_energy = self.evaluate_qaoa_energy(problem, ¶meters)?;
1290
1291 if current_energy < best_energy {
1292 best_energy = current_energy;
1293 best_parameters = parameters.clone();
1294 }
1295
1296 let gradient_norm: f64 = gradients.iter().map(|&g| g * g).sum::<f64>().sqrt();
1298 if gradient_norm < self.config.convergence_tolerance {
1299 converged = true;
1300 break;
1301 }
1302
1303 if iteration % 10 == 0 && self.config.detailed_logging {
1305 println!(
1306 "Gradient iter {iteration}: Energy = {current_energy:.6}, Grad norm = {gradient_norm:.6}"
1307 );
1308 }
1309 }
1310
1311 Ok(OptimizationResult {
1312 optimal_parameters: best_parameters,
1313 optimal_energy: best_energy,
1314 converged,
1315 iterations: max_iterations,
1316 })
1317 }
1318
1319 fn compute_finite_difference_gradients(
1321 &mut self,
1322 problem: &IsingModel,
1323 parameters: &[f64],
1324 step: f64,
1325 ) -> QaoaResult<Vec<f64>> {
1326 let mut gradients = vec![0.0; parameters.len()];
1327
1328 for i in 0..parameters.len() {
1329 let mut params_plus = parameters.to_vec();
1330 let mut params_minus = parameters.to_vec();
1331
1332 params_plus[i] += step;
1333 params_minus[i] -= step;
1334
1335 let energy_plus = self.evaluate_qaoa_energy(problem, ¶ms_plus)?;
1336 let energy_minus = self.evaluate_qaoa_energy(problem, ¶ms_minus)?;
1337
1338 gradients[i] = (energy_plus - energy_minus) / (2.0 * step);
1339 }
1340
1341 Ok(gradients)
1342 }
1343
1344 fn optimize_simple_search(
1346 &mut self,
1347 problem: &IsingModel,
1348 initial_parameters: Vec<f64>,
1349 ) -> QaoaResult<OptimizationResult> {
1350 let mut best_parameters = initial_parameters.clone();
1351 let mut best_energy = self.evaluate_qaoa_energy(problem, &initial_parameters)?;
1352
1353 for _ in 0..100 {
1355 let mut test_parameters = initial_parameters.clone();
1356 for param in &mut test_parameters {
1357 *param += self.rng.gen_range(-0.1..0.1);
1358 }
1359
1360 let energy = self.evaluate_qaoa_energy(problem, &test_parameters)?;
1361 if energy < best_energy {
1362 best_energy = energy;
1363 best_parameters = test_parameters;
1364 }
1365 }
1366
1367 Ok(OptimizationResult {
1368 optimal_parameters: best_parameters,
1369 optimal_energy: best_energy,
1370 converged: false,
1371 iterations: 100,
1372 })
1373 }
1374
1375 fn evaluate_qaoa_energy(
1377 &mut self,
1378 problem: &IsingModel,
1379 parameters: &[f64],
1380 ) -> QaoaResult<f64> {
1381 self.optimization_history.function_evaluations += 1;
1382
1383 if self.config.track_optimization_history {
1385 self.optimization_history
1386 .parameters
1387 .push(parameters.to_vec());
1388 }
1389
1390 let final_state = self.simulate_qaoa_circuit(problem, parameters)?;
1392
1393 let energy = self.calculate_hamiltonian_expectation(problem, &final_state)?;
1395
1396 self.optimization_history.energies.push(energy);
1398
1399 Ok(energy)
1400 }
1401
1402 fn simulate_qaoa_circuit(
1404 &self,
1405 problem: &IsingModel,
1406 parameters: &[f64],
1407 ) -> QaoaResult<QuantumState> {
1408 let mut state = QuantumState::uniform_superposition(problem.num_qubits);
1410
1411 let circuit = self.build_qaoa_circuit(problem, parameters)?;
1413
1414 for layer in &circuit.layers {
1415 for gate in &layer.problem_gates {
1417 self.apply_gate(&mut state, gate)?;
1418 }
1419
1420 for gate in &layer.mixer_gates {
1422 self.apply_gate(&mut state, gate)?;
1423 }
1424 }
1425
1426 Ok(state)
1427 }
1428
1429 fn apply_gate(&self, state: &mut QuantumState, gate: &QuantumGate) -> QaoaResult<()> {
1431 match gate {
1432 QuantumGate::RX { qubit, angle } => {
1433 self.apply_rx_gate(state, *qubit, *angle);
1434 }
1435
1436 QuantumGate::RY { qubit, angle } => {
1437 self.apply_ry_gate(state, *qubit, *angle);
1438 }
1439
1440 QuantumGate::RZ { qubit, angle } => {
1441 self.apply_rz_gate(state, *qubit, *angle);
1442 }
1443
1444 QuantumGate::CNOT { control, target } => {
1445 self.apply_cnot_gate(state, *control, *target);
1446 }
1447
1448 QuantumGate::CZ { control, target } => {
1449 self.apply_cz_gate(state, *control, *target);
1450 }
1451
1452 QuantumGate::ZZ {
1453 qubit1,
1454 qubit2,
1455 angle,
1456 } => {
1457 self.apply_zz_gate(state, *qubit1, *qubit2, *angle);
1458 }
1459
1460 QuantumGate::H { qubit } => {
1461 self.apply_h_gate(state, *qubit);
1462 }
1463
1464 QuantumGate::Measure { .. } => {
1465 }
1467 }
1468
1469 Ok(())
1470 }
1471
1472 fn apply_rx_gate(&self, state: &mut QuantumState, qubit: usize, angle: f64) {
1474 let cos_half = (angle / 2.0).cos();
1475 let sin_half = (angle / 2.0).sin();
1476
1477 let n = state.num_qubits;
1478 let mut new_amplitudes = vec![complex::Complex64::new(0.0, 0.0); 1 << n];
1479
1480 for i in 0..(1 << n) {
1481 let bit = (i >> qubit) & 1;
1482
1483 if bit == 0 {
1484 let j = i | (1 << qubit); new_amplitudes[i] = new_amplitudes[i] + state.amplitudes[i] * cos_half;
1487 new_amplitudes[j] = new_amplitudes[j]
1488 + state.amplitudes[i] * complex::Complex64::new(0.0, -sin_half);
1489 } else {
1490 let j = i & !(1 << qubit); new_amplitudes[i] = new_amplitudes[i] + state.amplitudes[i] * cos_half;
1493 new_amplitudes[j] = new_amplitudes[j]
1494 + state.amplitudes[i] * complex::Complex64::new(0.0, -sin_half);
1495 }
1496 }
1497
1498 state.amplitudes = new_amplitudes;
1499 }
1500
1501 fn apply_ry_gate(&self, state: &mut QuantumState, qubit: usize, angle: f64) {
1503 let cos_half = (angle / 2.0).cos();
1504 let sin_half = (angle / 2.0).sin();
1505
1506 let n = state.num_qubits;
1507 let mut new_amplitudes = vec![complex::Complex64::new(0.0, 0.0); 1 << n];
1508
1509 for i in 0..(1 << n) {
1510 let bit = (i >> qubit) & 1;
1511
1512 if bit == 0 {
1513 let j = i | (1 << qubit); new_amplitudes[i] = new_amplitudes[i] + state.amplitudes[i] * cos_half;
1516 new_amplitudes[j] = new_amplitudes[j] + state.amplitudes[i] * sin_half;
1517 } else {
1518 let j = i & !(1 << qubit); new_amplitudes[i] = new_amplitudes[i] + state.amplitudes[i] * cos_half;
1521 new_amplitudes[j] = new_amplitudes[j] + state.amplitudes[i] * (-sin_half);
1522 }
1523 }
1524
1525 state.amplitudes = new_amplitudes;
1526 }
1527
1528 fn apply_rz_gate(&self, state: &mut QuantumState, qubit: usize, angle: f64) {
1530 let phase_0 = complex::Complex64::new((angle / 2.0).cos(), (-angle / 2.0).sin());
1531 let phase_1 = complex::Complex64::new((angle / 2.0).cos(), (angle / 2.0).sin());
1532
1533 for i in 0..state.amplitudes.len() {
1534 let bit = (i >> qubit) & 1;
1535 if bit == 0 {
1536 state.amplitudes[i] = state.amplitudes[i] * phase_0;
1537 } else {
1538 state.amplitudes[i] = state.amplitudes[i] * phase_1;
1539 }
1540 }
1541 }
1542
1543 fn apply_cnot_gate(&self, state: &mut QuantumState, control: usize, target: usize) {
1545 let n = state.num_qubits;
1546 let mut new_amplitudes = state.amplitudes.clone();
1547
1548 for i in 0..(1 << n) {
1549 let control_bit = (i >> control) & 1;
1550 let target_bit = (i >> target) & 1;
1551
1552 if control_bit == 1 {
1553 let j = i ^ (1 << target);
1555 new_amplitudes[i] = state.amplitudes[j];
1556 }
1557 }
1558
1559 state.amplitudes = new_amplitudes;
1560 }
1561
1562 fn apply_cz_gate(&self, state: &mut QuantumState, control: usize, target: usize) {
1564 for i in 0..state.amplitudes.len() {
1565 let control_bit = (i >> control) & 1;
1566 let target_bit = (i >> target) & 1;
1567
1568 if control_bit == 1 && target_bit == 1 {
1569 state.amplitudes[i] = state.amplitudes[i] * complex::Complex64::new(-1.0, 0.0);
1570 }
1571 }
1572 }
1573
1574 fn apply_zz_gate(&self, state: &mut QuantumState, qubit1: usize, qubit2: usize, angle: f64) {
1576 for i in 0..state.amplitudes.len() {
1577 let bit1 = (i >> qubit1) & 1;
1578 let bit2 = (i >> qubit2) & 1;
1579
1580 let parity = bit1 ^ bit2;
1581 let phase = if parity == 0 {
1582 -angle / 2.0
1583 } else {
1584 angle / 2.0
1585 };
1586 let phase_factor = complex::Complex64::new(phase.cos(), phase.sin());
1587
1588 state.amplitudes[i] = state.amplitudes[i] * phase_factor;
1589 }
1590 }
1591
1592 fn apply_h_gate(&self, state: &mut QuantumState, qubit: usize) {
1594 let sqrt_2_inv = 1.0 / 2.0_f64.sqrt();
1595
1596 let n = state.num_qubits;
1597 let mut new_amplitudes = vec![complex::Complex64::new(0.0, 0.0); 1 << n];
1598
1599 for i in 0..(1 << n) {
1600 let bit = (i >> qubit) & 1;
1601
1602 if bit == 0 {
1603 let j = i | (1 << qubit);
1604 new_amplitudes[i] = new_amplitudes[i] + state.amplitudes[i] * sqrt_2_inv;
1605 new_amplitudes[j] = new_amplitudes[j] + state.amplitudes[i] * sqrt_2_inv;
1606 } else {
1607 let j = i & !(1 << qubit);
1608 new_amplitudes[i] = new_amplitudes[i] + state.amplitudes[i] * sqrt_2_inv;
1609 new_amplitudes[j] = new_amplitudes[j] + state.amplitudes[i] * (-sqrt_2_inv);
1610 }
1611 }
1612
1613 state.amplitudes = new_amplitudes;
1614 }
1615
1616 fn calculate_hamiltonian_expectation(
1618 &self,
1619 problem: &IsingModel,
1620 state: &QuantumState,
1621 ) -> QaoaResult<f64> {
1622 let mut expectation = 0.0;
1623
1624 for i in 0..problem.num_qubits {
1626 if let Ok(bias) = problem.get_bias(i) {
1627 if bias != 0.0 {
1628 expectation += bias * state.expectation_z(i);
1629 }
1630 }
1631 }
1632
1633 for i in 0..problem.num_qubits {
1635 for j in (i + 1)..problem.num_qubits {
1636 if let Ok(coupling) = problem.get_coupling(i, j) {
1637 if coupling != 0.0 {
1638 expectation += coupling * state.expectation_zz(i, j);
1639 }
1640 }
1641 }
1642 }
1643
1644 Ok(expectation)
1645 }
1646
1647 fn extract_best_solution(
1649 &mut self,
1650 problem: &IsingModel,
1651 state: &QuantumState,
1652 ) -> QaoaResult<(Vec<i8>, f64)> {
1653 let mut best_energy = f64::INFINITY;
1654 let mut best_solution = vec![0; problem.num_qubits];
1655
1656 for _ in 0..self.config.num_shots {
1658 let bitstring = state.sample(&mut self.rng);
1659 let solution = state.bitstring_to_spins(bitstring);
1660 let energy = self.evaluate_classical_energy(problem, &solution)?;
1661
1662 if energy < best_energy {
1663 best_energy = energy;
1664 best_solution = solution;
1665 }
1666 }
1667
1668 Ok((best_solution, best_energy))
1669 }
1670
1671 fn evaluate_classical_energy(&self, problem: &IsingModel, solution: &[i8]) -> QaoaResult<f64> {
1673 let mut energy = 0.0;
1674
1675 for i in 0..solution.len() {
1677 if let Ok(bias) = problem.get_bias(i) {
1678 energy += bias * f64::from(solution[i]);
1679 }
1680 }
1681
1682 for i in 0..solution.len() {
1684 for j in (i + 1)..solution.len() {
1685 if let Ok(coupling) = problem.get_coupling(i, j) {
1686 energy += coupling * f64::from(solution[i]) * f64::from(solution[j]);
1687 }
1688 }
1689 }
1690
1691 Ok(energy)
1692 }
1693
1694 const fn calculate_approximation_ratio(
1696 &self,
1697 achieved_energy: f64,
1698 problem: &IsingModel,
1699 ) -> f64 {
1700 0.95 }
1704
1705 fn calculate_circuit_stats(&self, circuit: &QaoaCircuit) -> QaoaCircuitStats {
1707 let mut gate_counts = HashMap::new();
1708 let mut two_qubit_gates = 0;
1709 let mut single_qubit_gates = 0;
1710
1711 for layer in &circuit.layers {
1712 for gate in &layer.problem_gates {
1713 match gate {
1714 QuantumGate::RX { .. }
1715 | QuantumGate::RY { .. }
1716 | QuantumGate::RZ { .. }
1717 | QuantumGate::H { .. } => {
1718 single_qubit_gates += 1;
1719 *gate_counts
1720 .entry(
1721 format!("{gate:?}")
1722 .split(' ')
1723 .next()
1724 .unwrap_or("Unknown")
1725 .to_string(),
1726 )
1727 .or_insert(0) += 1;
1728 }
1729 QuantumGate::CNOT { .. } | QuantumGate::CZ { .. } | QuantumGate::ZZ { .. } => {
1730 two_qubit_gates += 1;
1731 *gate_counts
1732 .entry(
1733 format!("{gate:?}")
1734 .split(' ')
1735 .next()
1736 .unwrap_or("Unknown")
1737 .to_string(),
1738 )
1739 .or_insert(0) += 1;
1740 }
1741 QuantumGate::Measure { .. } => {}
1742 }
1743 }
1744
1745 for gate in &layer.mixer_gates {
1746 match gate {
1747 QuantumGate::RX { .. }
1748 | QuantumGate::RY { .. }
1749 | QuantumGate::RZ { .. }
1750 | QuantumGate::H { .. } => {
1751 single_qubit_gates += 1;
1752 *gate_counts
1753 .entry(
1754 format!("{gate:?}")
1755 .split(' ')
1756 .next()
1757 .unwrap_or("Unknown")
1758 .to_string(),
1759 )
1760 .or_insert(0) += 1;
1761 }
1762 QuantumGate::CNOT { .. } | QuantumGate::CZ { .. } | QuantumGate::ZZ { .. } => {
1763 two_qubit_gates += 1;
1764 *gate_counts
1765 .entry(
1766 format!("{gate:?}")
1767 .split(' ')
1768 .next()
1769 .unwrap_or("Unknown")
1770 .to_string(),
1771 )
1772 .or_insert(0) += 1;
1773 }
1774 QuantumGate::Measure { .. } => {}
1775 }
1776 }
1777 }
1778
1779 QaoaCircuitStats {
1780 total_depth: circuit.depth,
1781 two_qubit_gates,
1782 single_qubit_gates,
1783 estimated_fidelity: 0.9, gate_counts,
1785 }
1786 }
1787
1788 fn calculate_quantum_stats(
1790 &self,
1791 state: &QuantumState,
1792 problem: &IsingModel,
1793 ) -> QuantumStateStats {
1794 QuantumStateStats {
1796 optimal_overlap: 0.8, entanglement_entropy: vec![1.0; problem.num_qubits], concentration_ratio: 0.5,
1799 expectation_variance: 0.1,
1800 }
1801 }
1802
1803 fn calculate_performance_metrics(
1805 &self,
1806 optimization_result: &OptimizationResult,
1807 best_energy: f64,
1808 optimization_time: Duration,
1809 ) -> QaoaPerformanceMetrics {
1810 QaoaPerformanceMetrics {
1811 success_probability: 0.7, relative_energy: 0.95, parameter_sensitivity: vec![0.1; optimization_result.optimal_parameters.len()],
1814 optimization_efficiency: (optimization_result.optimal_energy.abs())
1815 / self.optimization_history.function_evaluations as f64,
1816 preprocessing_time: Duration::from_millis(100), quantum_simulation_time: optimization_time,
1818 }
1819 }
1820}
1821
1822#[derive(Debug)]
1824struct OptimizationResult {
1825 optimal_parameters: Vec<f64>,
1826 optimal_energy: f64,
1827 converged: bool,
1828 iterations: usize,
1829}
1830
1831#[must_use]
1835pub fn create_standard_qaoa_config(layers: usize, shots: usize) -> QaoaConfig {
1836 QaoaConfig {
1837 variant: QaoaVariant::Standard { layers },
1838 num_shots: shots,
1839 ..Default::default()
1840 }
1841}
1842
1843#[must_use]
1845pub fn create_qaoa_plus_config(layers: usize, shots: usize) -> QaoaConfig {
1846 QaoaConfig {
1847 variant: QaoaVariant::QaoaPlus {
1848 layers,
1849 multi_angle: true,
1850 },
1851 mixer_type: MixerType::XMixer,
1852 num_shots: shots,
1853 ..Default::default()
1854 }
1855}
1856
1857#[must_use]
1859pub fn create_warm_start_qaoa_config(
1860 layers: usize,
1861 initial_solution: Vec<i8>,
1862 shots: usize,
1863) -> QaoaConfig {
1864 QaoaConfig {
1865 variant: QaoaVariant::WarmStart {
1866 layers,
1867 initial_solution: initial_solution.clone(),
1868 },
1869 parameter_init: ParameterInitialization::WarmStart {
1870 solution: initial_solution,
1871 },
1872 num_shots: shots,
1873 ..Default::default()
1874 }
1875}
1876
1877#[must_use]
1879pub fn create_constrained_qaoa_config(layers: usize, shots: usize) -> QaoaConfig {
1880 QaoaConfig {
1881 variant: QaoaVariant::Standard { layers },
1882 mixer_type: MixerType::XYMixer,
1883 num_shots: shots,
1884 ..Default::default()
1885 }
1886}
1887
1888#[cfg(test)]
1889mod tests {
1890 use super::*;
1891
1892 #[test]
1893 fn test_qaoa_config_creation() {
1894 let config = create_standard_qaoa_config(3, 1000);
1895
1896 match config.variant {
1897 QaoaVariant::Standard { layers } => {
1898 assert_eq!(layers, 3);
1899 }
1900 _ => panic!("Expected Standard QAOA variant"),
1901 }
1902
1903 assert_eq!(config.num_shots, 1000);
1904 }
1905
1906 #[test]
1907 fn test_quantum_state_creation() {
1908 let state = QuantumState::new(3);
1909 assert_eq!(state.num_qubits, 3);
1910 assert_eq!(state.amplitudes.len(), 8);
1911
1912 assert_eq!(state.get_probability(0), 1.0);
1914 for i in 1..8 {
1915 assert_eq!(state.get_probability(i), 0.0);
1916 }
1917 }
1918
1919 #[test]
1920 fn test_uniform_superposition() {
1921 let state = QuantumState::uniform_superposition(2);
1922 assert_eq!(state.num_qubits, 2);
1923
1924 for i in 0..4 {
1926 assert!((state.get_probability(i) - 0.25).abs() < 1e-10);
1927 }
1928 }
1929
1930 #[test]
1931 fn test_bitstring_to_spins() {
1932 let state = QuantumState::new(3);
1933
1934 let spins = state.bitstring_to_spins(0b101); assert_eq!(spins, vec![1, -1, 1]); }
1937
1938 #[test]
1939 fn test_qaoa_optimizer_creation() {
1940 let config = create_standard_qaoa_config(2, 100);
1941 let optimizer = QaoaOptimizer::new(config);
1942 assert!(optimizer.is_ok());
1943 }
1944}