1use scirs2_core::random::prelude::*;
9use scirs2_core::random::ChaCha8Rng;
10use scirs2_core::random::{Rng, SeedableRng};
11use std::collections::HashMap;
12use std::time::{Duration, Instant};
13use thiserror::Error;
14
15use crate::ising::{IsingError, IsingModel};
16use crate::simulator::{AnnealingParams, AnnealingSolution, QuantumAnnealingSimulator};
17
18#[derive(Error, Debug)]
20pub enum VqaError {
21 #[error("Ising error: {0}")]
23 IsingError(#[from] IsingError),
24
25 #[error("Invalid parameters: {0}")]
27 InvalidParameters(String),
28
29 #[error("Optimization failed: {0}")]
31 OptimizationFailed(String),
32
33 #[error("Circuit error: {0}")]
35 CircuitError(String),
36
37 #[error("Convergence error: {0}")]
39 ConvergenceError(String),
40}
41
42pub type VqaResult<T> = Result<T, VqaError>;
44
45#[derive(Debug, Clone, PartialEq)]
47pub enum AnsatzType {
48 HardwareEfficient {
50 depth: usize,
51 entangling_gates: EntanglingGateType,
52 },
53
54 QaoaInspired {
56 layers: usize,
57 mixer_type: MixerType,
58 },
59
60 AdiabaticInspired {
62 time_steps: usize,
63 evolution_time: f64,
64 },
65
66 Custom { structure: Vec<QuantumGate> },
68}
69
70#[derive(Debug, Clone, PartialEq, Eq)]
72pub enum EntanglingGateType {
73 CNot,
75 CZ,
77 ZZ,
79 XY,
81}
82
83#[derive(Debug, Clone, PartialEq, Eq)]
85pub enum MixerType {
86 XRotation,
88 XY,
90 MultiAngle,
92}
93
94#[derive(Debug, Clone, PartialEq)]
96pub enum QuantumGate {
97 RX { qubit: usize, angle: ParameterRef },
99 RY { qubit: usize, angle: ParameterRef },
101 RZ { qubit: usize, angle: ParameterRef },
103 CNOT { control: usize, target: usize },
105 CZ { control: usize, target: usize },
107 ZZ {
109 qubit1: usize,
110 qubit2: usize,
111 angle: ParameterRef,
112 },
113}
114
115#[derive(Debug, Clone, PartialEq)]
117pub struct ParameterRef {
118 pub index: usize,
120 pub scale: f64,
122}
123
124impl ParameterRef {
125 #[must_use]
127 pub const fn new(index: usize) -> Self {
128 Self { index, scale: 1.0 }
129 }
130
131 #[must_use]
133 pub const fn scaled(index: usize, scale: f64) -> Self {
134 Self { index, scale }
135 }
136}
137
138#[derive(Debug, Clone)]
140pub struct VqaConfig {
141 pub ansatz: AnsatzType,
143
144 pub optimizer: ClassicalOptimizer,
146
147 pub max_iterations: usize,
149
150 pub convergence_tolerance: f64,
152
153 pub num_shots: usize,
155
156 pub annealing_params: AnnealingParams,
158
159 pub parameter_init_range: (f64, f64),
161
162 pub use_gradients: bool,
164
165 pub gradient_step: f64,
167
168 pub seed: Option<u64>,
170
171 pub max_runtime: Option<Duration>,
173
174 pub log_frequency: usize,
176}
177
178impl Default for VqaConfig {
179 fn default() -> Self {
180 Self {
181 ansatz: AnsatzType::HardwareEfficient {
182 depth: 3,
183 entangling_gates: EntanglingGateType::CNot,
184 },
185 optimizer: ClassicalOptimizer::Adam {
186 learning_rate: 0.01,
187 beta1: 0.9,
188 beta2: 0.999,
189 epsilon: 1e-8,
190 },
191 max_iterations: 100,
192 convergence_tolerance: 1e-6,
193 num_shots: 100,
194 annealing_params: AnnealingParams::default(),
195 parameter_init_range: (-0.5, 0.5),
196 use_gradients: true,
197 gradient_step: 0.01,
198 seed: None,
199 max_runtime: Some(Duration::from_secs(3600)),
200 log_frequency: 10,
201 }
202 }
203}
204
205#[derive(Debug, Clone)]
207pub enum ClassicalOptimizer {
208 GradientDescent { learning_rate: f64 },
210
211 Adam {
213 learning_rate: f64,
214 beta1: f64,
215 beta2: f64,
216 epsilon: f64,
217 },
218
219 RMSprop {
221 learning_rate: f64,
222 decay_rate: f64,
223 epsilon: f64,
224 },
225
226 NelderMead {
228 initial_simplex_size: f64,
229 alpha: f64,
230 gamma: f64,
231 rho: f64,
232 sigma: f64,
233 },
234
235 BFGS {
237 line_search_tolerance: f64,
238 max_line_search_iterations: usize,
239 },
240}
241
242#[derive(Debug, Clone)]
244pub struct VqaResults {
245 pub best_solution: Vec<i8>,
247
248 pub best_energy: f64,
250
251 pub optimal_parameters: Vec<f64>,
253
254 pub energy_history: Vec<f64>,
256
257 pub parameter_history: Vec<Vec<f64>>,
259
260 pub gradient_norms: Vec<f64>,
262
263 pub iterations_completed: usize,
265
266 pub converged: bool,
268
269 pub total_time: Duration,
271
272 pub statistics: VqaStatistics,
274}
275
276#[derive(Debug, Clone)]
278pub struct VqaStatistics {
279 pub function_evaluations: usize,
281
282 pub gradient_evaluations: usize,
284
285 pub total_annealing_time: Duration,
287
288 pub average_energy: f64,
290
291 pub energy_variance: f64,
293
294 pub parameter_stats: ParameterStatistics,
296
297 pub optimizer_stats: OptimizerStatistics,
299}
300
301#[derive(Debug, Clone)]
303pub struct ParameterStatistics {
304 pub average_magnitude: f64,
306
307 pub parameter_variance: f64,
309
310 pub num_updates: usize,
312
313 pub max_parameter_change: Vec<f64>,
315}
316
317#[derive(Debug, Clone)]
319pub struct OptimizerStatistics {
320 pub step_acceptance_rate: f64,
322
323 pub average_step_size: f64,
325
326 pub line_search_iterations: usize,
328
329 pub optimizer_metrics: HashMap<String, f64>,
331}
332
333pub struct VariationalQuantumAnnealer {
335 config: VqaConfig,
337
338 parameters: Vec<f64>,
340
341 optimizer_state: OptimizerState,
343
344 rng: ChaCha8Rng,
346
347 history: OptimizationHistory,
349}
350
351#[derive(Debug)]
353enum OptimizerState {
354 GradientDescent {
355 momentum: Option<Vec<f64>>,
356 },
357
358 Adam {
359 m: Vec<f64>, v: Vec<f64>, t: usize, },
363
364 RMSprop {
365 s: Vec<f64>, },
367
368 NelderMead {
369 simplex: Vec<Vec<f64>>,
370 function_values: Vec<f64>,
371 },
372
373 BFGS {
374 hessian_inverse: Vec<Vec<f64>>,
375 previous_gradient: Option<Vec<f64>>,
376 previous_parameters: Option<Vec<f64>>,
377 },
378}
379
380#[derive(Debug)]
382struct OptimizationHistory {
383 energies: Vec<f64>,
384 parameters: Vec<Vec<f64>>,
385 gradients: Vec<Vec<f64>>,
386 function_evals: usize,
387 gradient_evals: usize,
388 start_time: Instant,
389}
390
391impl VariationalQuantumAnnealer {
392 pub fn new(config: VqaConfig) -> VqaResult<Self> {
394 let num_parameters = Self::count_parameters(&config.ansatz)?;
395
396 let rng = match config.seed {
397 Some(seed) => ChaCha8Rng::seed_from_u64(seed),
398 None => ChaCha8Rng::seed_from_u64(thread_rng().gen()),
399 };
400
401 let mut vqa = Self {
402 config: config.clone(),
403 parameters: vec![0.0; num_parameters],
404 optimizer_state: Self::initialize_optimizer_state(&config.optimizer, num_parameters)?,
405 rng,
406 history: OptimizationHistory {
407 energies: Vec::new(),
408 parameters: Vec::new(),
409 gradients: Vec::new(),
410 function_evals: 0,
411 gradient_evals: 0,
412 start_time: Instant::now(),
413 },
414 };
415
416 vqa.initialize_parameters()?;
417 Ok(vqa)
418 }
419
420 fn count_parameters(ansatz: &AnsatzType) -> VqaResult<usize> {
422 match ansatz {
423 AnsatzType::HardwareEfficient { depth, .. } => {
424 Ok(depth * 10) }
428
429 AnsatzType::QaoaInspired { layers, .. } => {
430 Ok(layers * 2)
432 }
433
434 AnsatzType::AdiabaticInspired { time_steps, .. } => {
435 Ok(*time_steps)
437 }
438
439 AnsatzType::Custom { structure } => {
440 let mut max_param_index = 0;
442 for gate in structure {
443 if let Some(param_ref) = Self::extract_parameter_ref(gate) {
444 max_param_index = max_param_index.max(param_ref.index);
445 }
446 }
447 Ok(max_param_index + 1)
448 }
449 }
450 }
451
452 const fn extract_parameter_ref(gate: &QuantumGate) -> Option<&ParameterRef> {
454 match gate {
455 QuantumGate::RX { angle, .. }
456 | QuantumGate::RY { angle, .. }
457 | QuantumGate::RZ { angle, .. }
458 | QuantumGate::ZZ { angle, .. } => Some(angle),
459 _ => None,
460 }
461 }
462
463 fn initialize_optimizer_state(
465 optimizer: &ClassicalOptimizer,
466 num_params: usize,
467 ) -> VqaResult<OptimizerState> {
468 match optimizer {
469 ClassicalOptimizer::GradientDescent { .. } => {
470 Ok(OptimizerState::GradientDescent { momentum: None })
471 }
472
473 ClassicalOptimizer::Adam { .. } => Ok(OptimizerState::Adam {
474 m: vec![0.0; num_params],
475 v: vec![0.0; num_params],
476 t: 0,
477 }),
478
479 ClassicalOptimizer::RMSprop { .. } => Ok(OptimizerState::RMSprop {
480 s: vec![0.0; num_params],
481 }),
482
483 ClassicalOptimizer::NelderMead {
484 initial_simplex_size,
485 ..
486 } => {
487 let mut simplex = vec![vec![0.0; num_params]; num_params + 1];
489 for i in 0..num_params {
490 simplex[i + 1][i] = *initial_simplex_size;
491 }
492
493 Ok(OptimizerState::NelderMead {
494 simplex,
495 function_values: vec![f64::INFINITY; num_params + 1],
496 })
497 }
498
499 ClassicalOptimizer::BFGS { .. } => {
500 let mut hessian_inverse = vec![vec![0.0; num_params]; num_params];
502 for i in 0..num_params {
503 hessian_inverse[i][i] = 1.0;
504 }
505
506 Ok(OptimizerState::BFGS {
507 hessian_inverse,
508 previous_gradient: None,
509 previous_parameters: None,
510 })
511 }
512 }
513 }
514
515 fn initialize_parameters(&mut self) -> VqaResult<()> {
517 let (min, max) = self.config.parameter_init_range;
518
519 for param in &mut self.parameters {
520 *param = self.rng.gen_range(min..max);
521 }
522
523 Ok(())
524 }
525
526 pub fn optimize(&mut self, problem: &IsingModel) -> VqaResult<VqaResults> {
528 println!("Starting variational quantum annealing optimization...");
529
530 self.history.start_time = Instant::now();
531 let mut best_energy = f64::INFINITY;
532 let mut best_solution = vec![0; problem.num_qubits];
533 let mut best_parameters = self.parameters.clone();
534
535 for iteration in 0..self.config.max_iterations {
536 let iteration_start = Instant::now();
537
538 if let Some(max_runtime) = self.config.max_runtime {
540 if self.history.start_time.elapsed() > max_runtime {
541 println!("Maximum runtime exceeded");
542 break;
543 }
544 }
545
546 let current_params = self.parameters.clone();
548 let (energy, solution) = self.evaluate_objective(problem, ¤t_params)?;
549
550 if energy < best_energy {
552 best_energy = energy;
553 best_solution = solution;
554 best_parameters = self.parameters.clone();
555 }
556
557 self.history.energies.push(energy);
559 self.history.parameters.push(self.parameters.clone());
560
561 let gradients = if self.config.use_gradients {
563 let grads = self.compute_gradients(problem)?;
564 self.history.gradients.push(grads.clone());
565 Some(grads)
566 } else {
567 None
568 };
569
570 self.update_parameters(gradients.as_ref().map(std::vec::Vec::as_slice))?;
572
573 if iteration % self.config.log_frequency == 0 {
575 let grad_norm = gradients
576 .as_ref()
577 .map_or(0.0, |g| g.iter().map(|&x| x.powi(2)).sum::<f64>().sqrt());
578
579 println!(
580 "Iteration {}: Energy = {:.6}, Gradient norm = {:.6}, Time = {:.2?}",
581 iteration,
582 energy,
583 grad_norm,
584 iteration_start.elapsed()
585 );
586 }
587
588 if self.check_convergence()? {
590 println!("Converged at iteration {iteration}");
591 break;
592 }
593 }
594
595 let total_time = self.history.start_time.elapsed();
596
597 let statistics = self.calculate_statistics();
599
600 Ok(VqaResults {
601 best_solution,
602 best_energy,
603 optimal_parameters: best_parameters,
604 energy_history: self.history.energies.clone(),
605 parameter_history: self.history.parameters.clone(),
606 gradient_norms: self
607 .history
608 .gradients
609 .iter()
610 .map(|g| g.iter().map(|&x| x.powi(2)).sum::<f64>().sqrt())
611 .collect(),
612 iterations_completed: self.history.energies.len(),
613 converged: self.check_convergence()?,
614 total_time,
615 statistics,
616 })
617 }
618
619 fn evaluate_objective(
621 &mut self,
622 problem: &IsingModel,
623 parameters: &[f64],
624 ) -> VqaResult<(f64, Vec<i8>)> {
625 self.history.function_evals += 1;
626
627 let circuit = self.build_quantum_circuit(problem, parameters)?;
629
630 let modified_problem = self.apply_circuit_to_problem(problem, &circuit)?;
632
633 let mut simulator = QuantumAnnealingSimulator::new(self.config.annealing_params.clone())
635 .map_err(|e| VqaError::OptimizationFailed(e.to_string()))?;
636
637 let mut best_energy = f64::INFINITY;
638 let mut best_solution = vec![0; problem.num_qubits];
639
640 for _ in 0..self.config.num_shots {
642 let result = simulator
643 .solve(&modified_problem)
644 .map_err(|e| VqaError::OptimizationFailed(e.to_string()))?;
645
646 if result.best_energy < best_energy {
647 best_energy = result.best_energy;
648 best_solution = result.best_spins;
649 }
650 }
651
652 Ok((best_energy, best_solution))
653 }
654
655 fn build_quantum_circuit(
657 &self,
658 problem: &IsingModel,
659 parameters: &[f64],
660 ) -> VqaResult<QuantumCircuit> {
661 let num_qubits = problem.num_qubits;
662 let mut circuit = QuantumCircuit::new(num_qubits);
663
664 match &self.config.ansatz {
665 AnsatzType::HardwareEfficient {
666 depth,
667 entangling_gates,
668 } => {
669 self.build_hardware_efficient_circuit(
670 &mut circuit,
671 *depth,
672 entangling_gates,
673 parameters,
674 )?;
675 }
676
677 AnsatzType::QaoaInspired { layers, mixer_type } => {
678 self.build_qaoa_inspired_circuit(
679 &mut circuit,
680 problem,
681 *layers,
682 mixer_type,
683 parameters,
684 )?;
685 }
686
687 AnsatzType::AdiabaticInspired {
688 time_steps,
689 evolution_time,
690 } => {
691 self.build_adiabatic_inspired_circuit(
692 &mut circuit,
693 problem,
694 *time_steps,
695 *evolution_time,
696 parameters,
697 )?;
698 }
699
700 AnsatzType::Custom { structure } => {
701 self.build_custom_circuit(&mut circuit, structure, parameters)?;
702 }
703 }
704
705 Ok(circuit)
706 }
707
708 fn build_hardware_efficient_circuit(
710 &self,
711 circuit: &mut QuantumCircuit,
712 depth: usize,
713 entangling_gates: &EntanglingGateType,
714 parameters: &[f64],
715 ) -> VqaResult<()> {
716 let num_qubits = circuit.num_qubits;
717 let mut param_idx = 0;
718
719 for layer in 0..depth {
720 for qubit in 0..num_qubits {
722 if param_idx < parameters.len() {
723 circuit.add_gate(QuantumGate::RY {
724 qubit,
725 angle: ParameterRef::new(param_idx),
726 });
727 param_idx += 1;
728 }
729
730 if param_idx < parameters.len() {
731 circuit.add_gate(QuantumGate::RZ {
732 qubit,
733 angle: ParameterRef::new(param_idx),
734 });
735 param_idx += 1;
736 }
737 }
738
739 match entangling_gates {
741 EntanglingGateType::CNot => {
742 for qubit in 0..num_qubits - 1 {
743 circuit.add_gate(QuantumGate::CNOT {
744 control: qubit,
745 target: qubit + 1,
746 });
747 }
748 }
749
750 EntanglingGateType::CZ => {
751 for qubit in 0..num_qubits - 1 {
752 circuit.add_gate(QuantumGate::CZ {
753 control: qubit,
754 target: qubit + 1,
755 });
756 }
757 }
758
759 EntanglingGateType::ZZ => {
760 for qubit in 0..num_qubits - 1 {
761 if param_idx < parameters.len() {
762 circuit.add_gate(QuantumGate::ZZ {
763 qubit1: qubit,
764 qubit2: qubit + 1,
765 angle: ParameterRef::new(param_idx),
766 });
767 param_idx += 1;
768 }
769 }
770 }
771
772 EntanglingGateType::XY => {
773 for qubit in 0..num_qubits - 1 {
775 if param_idx < parameters.len() {
776 circuit.add_gate(QuantumGate::CNOT {
778 control: qubit,
779 target: qubit + 1,
780 });
781 param_idx += 1;
782 }
783 }
784 }
785 }
786 }
787
788 Ok(())
789 }
790
791 fn build_qaoa_inspired_circuit(
793 &self,
794 circuit: &mut QuantumCircuit,
795 problem: &IsingModel,
796 layers: usize,
797 mixer_type: &MixerType,
798 parameters: &[f64],
799 ) -> VqaResult<()> {
800 let num_qubits = circuit.num_qubits;
801
802 for layer in 0..layers {
803 let gamma_idx = layer * 2;
804 let beta_idx = layer * 2 + 1;
805
806 if gamma_idx >= parameters.len() || beta_idx >= parameters.len() {
807 break;
808 }
809
810 let gamma = parameters[gamma_idx];
811 let beta = parameters[beta_idx];
812
813 for i in 0..num_qubits {
815 for j in (i + 1)..num_qubits {
816 if let Ok(coupling) = problem.get_coupling(i, j) {
817 if coupling != 0.0 {
818 circuit.add_gate(QuantumGate::ZZ {
819 qubit1: i,
820 qubit2: j,
821 angle: ParameterRef::scaled(gamma_idx, gamma * coupling),
822 });
823 }
824 }
825 }
826
827 if let Ok(bias) = problem.get_bias(i) {
829 if bias != 0.0 {
830 circuit.add_gate(QuantumGate::RZ {
831 qubit: i,
832 angle: ParameterRef::scaled(gamma_idx, gamma * bias),
833 });
834 }
835 }
836 }
837
838 match mixer_type {
840 MixerType::XRotation => {
841 for qubit in 0..num_qubits {
842 circuit.add_gate(QuantumGate::RX {
843 qubit,
844 angle: ParameterRef::scaled(beta_idx, beta),
845 });
846 }
847 }
848
849 MixerType::XY => {
850 for qubit in 0..num_qubits - 1 {
852 circuit.add_gate(QuantumGate::CNOT {
853 control: qubit,
854 target: qubit + 1,
855 });
856 }
857 }
858
859 MixerType::MultiAngle => {
860 for qubit in 0..num_qubits {
862 circuit.add_gate(QuantumGate::RX {
863 qubit,
864 angle: ParameterRef::scaled(beta_idx, beta),
865 });
866 circuit.add_gate(QuantumGate::RY {
867 qubit,
868 angle: ParameterRef::scaled(beta_idx, beta * 0.5),
869 });
870 }
871 }
872 }
873 }
874
875 Ok(())
876 }
877
878 fn build_adiabatic_inspired_circuit(
880 &self,
881 circuit: &mut QuantumCircuit,
882 problem: &IsingModel,
883 time_steps: usize,
884 evolution_time: f64,
885 parameters: &[f64],
886 ) -> VqaResult<()> {
887 let num_qubits = circuit.num_qubits;
888 let dt = evolution_time / time_steps as f64;
889
890 for step in 0..time_steps {
891 if step >= parameters.len() {
892 break;
893 }
894
895 let s = parameters[step]; for qubit in 0..num_qubits {
899 circuit.add_gate(QuantumGate::RX {
900 qubit,
901 angle: ParameterRef::scaled(step, -2.0 * (1.0 - s) * dt),
902 });
903 }
904
905 for i in 0..num_qubits {
907 for j in (i + 1)..num_qubits {
908 if let Ok(coupling) = problem.get_coupling(i, j) {
909 if coupling != 0.0 {
910 circuit.add_gate(QuantumGate::ZZ {
911 qubit1: i,
912 qubit2: j,
913 angle: ParameterRef::scaled(step, -s * coupling * dt),
914 });
915 }
916 }
917 }
918 }
919 }
920
921 Ok(())
922 }
923
924 fn build_custom_circuit(
926 &self,
927 circuit: &mut QuantumCircuit,
928 structure: &[QuantumGate],
929 parameters: &[f64],
930 ) -> VqaResult<()> {
931 for gate in structure {
932 let parameterized_gate = match gate {
934 QuantumGate::RX { qubit, angle } => {
935 let param_value = if angle.index < parameters.len() {
936 parameters[angle.index] * angle.scale
937 } else {
938 0.0
939 };
940 QuantumGate::RX {
941 qubit: *qubit,
942 angle: ParameterRef::scaled(angle.index, param_value),
943 }
944 }
945
946 QuantumGate::RY { qubit, angle } => {
947 let param_value = if angle.index < parameters.len() {
948 parameters[angle.index] * angle.scale
949 } else {
950 0.0
951 };
952 QuantumGate::RY {
953 qubit: *qubit,
954 angle: ParameterRef::scaled(angle.index, param_value),
955 }
956 }
957
958 QuantumGate::RZ { qubit, angle } => {
959 let param_value = if angle.index < parameters.len() {
960 parameters[angle.index] * angle.scale
961 } else {
962 0.0
963 };
964 QuantumGate::RZ {
965 qubit: *qubit,
966 angle: ParameterRef::scaled(angle.index, param_value),
967 }
968 }
969
970 QuantumGate::ZZ {
971 qubit1,
972 qubit2,
973 angle,
974 } => {
975 let param_value = if angle.index < parameters.len() {
976 parameters[angle.index] * angle.scale
977 } else {
978 0.0
979 };
980 QuantumGate::ZZ {
981 qubit1: *qubit1,
982 qubit2: *qubit2,
983 angle: ParameterRef::scaled(angle.index, param_value),
984 }
985 }
986
987 _ => gate.clone(),
989 };
990
991 circuit.add_gate(parameterized_gate);
992 }
993
994 Ok(())
995 }
996
997 fn apply_circuit_to_problem(
999 &self,
1000 problem: &IsingModel,
1001 circuit: &QuantumCircuit,
1002 ) -> VqaResult<IsingModel> {
1003 Ok(problem.clone())
1007 }
1008
1009 fn compute_gradients(&mut self, problem: &IsingModel) -> VqaResult<Vec<f64>> {
1011 self.history.gradient_evals += 1;
1012
1013 let mut gradients = vec![0.0; self.parameters.len()];
1014 let step = self.config.gradient_step;
1015
1016 for i in 0..self.parameters.len() {
1017 let mut params_plus = self.parameters.clone();
1019 let mut params_minus = self.parameters.clone();
1020
1021 params_plus[i] += step;
1022 params_minus[i] -= step;
1023
1024 let (energy_plus, _) = self.evaluate_objective(problem, ¶ms_plus)?;
1025 let (energy_minus, _) = self.evaluate_objective(problem, ¶ms_minus)?;
1026
1027 gradients[i] = (energy_plus - energy_minus) / (2.0 * step);
1029 }
1030
1031 Ok(gradients)
1032 }
1033
1034 fn update_parameters(&mut self, gradients: Option<&[f64]>) -> VqaResult<()> {
1036 match (&mut self.optimizer_state, &self.config.optimizer) {
1037 (
1038 OptimizerState::Adam { m, v, t },
1039 ClassicalOptimizer::Adam {
1040 learning_rate,
1041 beta1,
1042 beta2,
1043 epsilon,
1044 },
1045 ) => {
1046 if let Some(grads) = gradients {
1047 *t += 1;
1048
1049 for i in 0..self.parameters.len() {
1050 m[i] = (1.0 - beta1).mul_add(grads[i], beta1 * m[i]);
1052
1053 v[i] = (1.0 - beta2).mul_add(grads[i].powi(2), beta2 * v[i]);
1055
1056 let m_hat = m[i] / (1.0 - beta1.powi(*t as i32));
1058 let v_hat = v[i] / (1.0 - beta2.powi(*t as i32));
1059
1060 self.parameters[i] -= learning_rate * m_hat / (v_hat.sqrt() + epsilon);
1062 }
1063 }
1064 }
1065
1066 (
1067 OptimizerState::GradientDescent { .. },
1068 ClassicalOptimizer::GradientDescent { learning_rate },
1069 ) => {
1070 if let Some(grads) = gradients {
1071 for i in 0..self.parameters.len() {
1072 self.parameters[i] -= learning_rate * grads[i];
1073 }
1074 }
1075 }
1076
1077 _ => {
1078 return Err(VqaError::OptimizationFailed(
1080 "Optimizer not implemented".to_string(),
1081 ));
1082 }
1083 }
1084
1085 Ok(())
1086 }
1087
1088 fn check_convergence(&self) -> VqaResult<bool> {
1090 if self.history.energies.len() < 2 {
1091 return Ok(false);
1092 }
1093
1094 let recent_energies =
1095 &self.history.energies[self.history.energies.len().saturating_sub(5)..];
1096 let energy_range = recent_energies
1097 .iter()
1098 .copied()
1099 .fold(f64::NEG_INFINITY, f64::max)
1100 - recent_energies
1101 .iter()
1102 .copied()
1103 .fold(f64::INFINITY, f64::min);
1104
1105 Ok(energy_range < self.config.convergence_tolerance)
1106 }
1107
1108 fn calculate_statistics(&self) -> VqaStatistics {
1110 let average_energy = if self.history.energies.is_empty() {
1111 0.0
1112 } else {
1113 self.history.energies.iter().sum::<f64>() / self.history.energies.len() as f64
1114 };
1115
1116 let energy_variance = if self.history.energies.len() > 1 {
1117 let mean = average_energy;
1118 self.history
1119 .energies
1120 .iter()
1121 .map(|&e| (e - mean).powi(2))
1122 .sum::<f64>()
1123 / (self.history.energies.len() - 1) as f64
1124 } else {
1125 0.0
1126 };
1127
1128 let parameter_stats = self.calculate_parameter_statistics();
1130
1131 VqaStatistics {
1132 function_evaluations: self.history.function_evals,
1133 gradient_evaluations: self.history.gradient_evals,
1134 total_annealing_time: Duration::from_secs(0), average_energy,
1136 energy_variance,
1137 parameter_stats,
1138 optimizer_stats: OptimizerStatistics {
1139 step_acceptance_rate: 1.0, average_step_size: 0.01, line_search_iterations: 0,
1142 optimizer_metrics: HashMap::new(),
1143 },
1144 }
1145 }
1146
1147 fn calculate_parameter_statistics(&self) -> ParameterStatistics {
1149 let average_magnitude = if self.parameters.is_empty() {
1150 0.0
1151 } else {
1152 self.parameters.iter().map(|&p| p.abs()).sum::<f64>() / self.parameters.len() as f64
1153 };
1154
1155 let parameter_variance = if self.parameters.len() > 1 {
1156 let mean = self.parameters.iter().sum::<f64>() / self.parameters.len() as f64;
1157 self.parameters
1158 .iter()
1159 .map(|&p| (p - mean).powi(2))
1160 .sum::<f64>()
1161 / (self.parameters.len() - 1) as f64
1162 } else {
1163 0.0
1164 };
1165
1166 ParameterStatistics {
1167 average_magnitude,
1168 parameter_variance,
1169 num_updates: self.history.parameters.len(),
1170 max_parameter_change: Vec::new(), }
1172 }
1173}
1174
1175#[derive(Debug, Clone)]
1177pub struct QuantumCircuit {
1178 pub num_qubits: usize,
1180
1181 pub gates: Vec<QuantumGate>,
1183}
1184
1185impl QuantumCircuit {
1186 #[must_use]
1188 pub const fn new(num_qubits: usize) -> Self {
1189 Self {
1190 num_qubits,
1191 gates: Vec::new(),
1192 }
1193 }
1194
1195 pub fn add_gate(&mut self, gate: QuantumGate) {
1197 self.gates.push(gate);
1198 }
1199
1200 #[must_use]
1202 pub fn depth(&self) -> usize {
1203 self.gates.len()
1205 }
1206}
1207
1208#[must_use]
1212pub fn create_qaoa_vqa_config(layers: usize, max_iterations: usize) -> VqaConfig {
1213 VqaConfig {
1214 ansatz: AnsatzType::QaoaInspired {
1215 layers,
1216 mixer_type: MixerType::XRotation,
1217 },
1218 max_iterations,
1219 ..Default::default()
1220 }
1221}
1222
1223#[must_use]
1225pub fn create_hardware_efficient_vqa_config(depth: usize, max_iterations: usize) -> VqaConfig {
1226 VqaConfig {
1227 ansatz: AnsatzType::HardwareEfficient {
1228 depth,
1229 entangling_gates: EntanglingGateType::CNot,
1230 },
1231 max_iterations,
1232 ..Default::default()
1233 }
1234}
1235
1236#[must_use]
1238pub fn create_adiabatic_vqa_config(
1239 time_steps: usize,
1240 evolution_time: f64,
1241 max_iterations: usize,
1242) -> VqaConfig {
1243 VqaConfig {
1244 ansatz: AnsatzType::AdiabaticInspired {
1245 time_steps,
1246 evolution_time,
1247 },
1248 max_iterations,
1249 ..Default::default()
1250 }
1251}
1252
1253#[cfg(test)]
1254mod tests {
1255 use super::*;
1256
1257 #[test]
1258 fn test_vqa_config_creation() {
1259 let config = create_qaoa_vqa_config(3, 50);
1260
1261 match config.ansatz {
1262 AnsatzType::QaoaInspired { layers, .. } => {
1263 assert_eq!(layers, 3);
1264 }
1265 _ => panic!("Expected QAOA ansatz"),
1266 }
1267
1268 assert_eq!(config.max_iterations, 50);
1269 }
1270
1271 #[test]
1272 fn test_parameter_ref() {
1273 let param_ref = ParameterRef::new(5);
1274 assert_eq!(param_ref.index, 5);
1275 assert_eq!(param_ref.scale, 1.0);
1276
1277 let scaled_ref = ParameterRef::scaled(3, 2.5);
1278 assert_eq!(scaled_ref.index, 3);
1279 assert_eq!(scaled_ref.scale, 2.5);
1280 }
1281
1282 #[test]
1283 fn test_quantum_circuit() {
1284 let mut circuit = QuantumCircuit::new(3);
1285 assert_eq!(circuit.num_qubits, 3);
1286 assert_eq!(circuit.gates.len(), 0);
1287
1288 circuit.add_gate(QuantumGate::RX {
1289 qubit: 0,
1290 angle: ParameterRef::new(0),
1291 });
1292
1293 assert_eq!(circuit.gates.len(), 1);
1294 assert_eq!(circuit.depth(), 1);
1295 }
1296
1297 #[test]
1298 fn test_parameter_counting() {
1299 let ansatz = AnsatzType::QaoaInspired {
1300 layers: 5,
1301 mixer_type: MixerType::XRotation,
1302 };
1303
1304 let count = VariationalQuantumAnnealer::count_parameters(&ansatz)
1305 .expect("parameter counting should succeed");
1306 assert_eq!(count, 10); }
1308}