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().random()),
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> {
429 Self::exact_parameter_count(ansatz, 1)
430 }
431
432 fn exact_parameter_count(ansatz: &AnsatzType, num_qubits: usize) -> VqaResult<usize> {
443 match ansatz {
444 AnsatzType::HardwareEfficient {
445 depth,
446 entangling_gates,
447 } => {
448 let rotations_per_layer = 2 * num_qubits;
449 let entangling_params_per_layer = match entangling_gates {
450 EntanglingGateType::ZZ | EntanglingGateType::XY => num_qubits.saturating_sub(1),
451 EntanglingGateType::CNot | EntanglingGateType::CZ => 0,
452 };
453 Ok(depth * (rotations_per_layer + entangling_params_per_layer))
454 }
455
456 AnsatzType::QaoaInspired { layers, .. } => {
457 Ok(layers * 2)
459 }
460
461 AnsatzType::AdiabaticInspired { time_steps, .. } => {
462 Ok(*time_steps)
464 }
465
466 AnsatzType::Custom { structure } => {
467 let mut max_param_index = 0;
469 for gate in structure {
470 if let Some(param_ref) = Self::extract_parameter_ref(gate) {
471 max_param_index = max_param_index.max(param_ref.index);
472 }
473 }
474 Ok(max_param_index + 1)
475 }
476 }
477 }
478
479 const fn extract_parameter_ref(gate: &QuantumGate) -> Option<&ParameterRef> {
481 match gate {
482 QuantumGate::RX { angle, .. }
483 | QuantumGate::RY { angle, .. }
484 | QuantumGate::RZ { angle, .. }
485 | QuantumGate::ZZ { angle, .. } => Some(angle),
486 _ => None,
487 }
488 }
489
490 fn initialize_optimizer_state(
492 optimizer: &ClassicalOptimizer,
493 num_params: usize,
494 ) -> VqaResult<OptimizerState> {
495 match optimizer {
496 ClassicalOptimizer::GradientDescent { .. } => {
497 Ok(OptimizerState::GradientDescent { momentum: None })
498 }
499
500 ClassicalOptimizer::Adam { .. } => Ok(OptimizerState::Adam {
501 m: vec![0.0; num_params],
502 v: vec![0.0; num_params],
503 t: 0,
504 }),
505
506 ClassicalOptimizer::RMSprop { .. } => Ok(OptimizerState::RMSprop {
507 s: vec![0.0; num_params],
508 }),
509
510 ClassicalOptimizer::NelderMead {
511 initial_simplex_size,
512 ..
513 } => {
514 let mut simplex = vec![vec![0.0; num_params]; num_params + 1];
516 for i in 0..num_params {
517 simplex[i + 1][i] = *initial_simplex_size;
518 }
519
520 Ok(OptimizerState::NelderMead {
521 simplex,
522 function_values: vec![f64::INFINITY; num_params + 1],
523 })
524 }
525
526 ClassicalOptimizer::BFGS { .. } => {
527 let mut hessian_inverse = vec![vec![0.0; num_params]; num_params];
529 for i in 0..num_params {
530 hessian_inverse[i][i] = 1.0;
531 }
532
533 Ok(OptimizerState::BFGS {
534 hessian_inverse,
535 previous_gradient: None,
536 previous_parameters: None,
537 })
538 }
539 }
540 }
541
542 fn initialize_parameters(&mut self) -> VqaResult<()> {
544 let (min, max) = self.config.parameter_init_range;
545
546 for param in &mut self.parameters {
547 *param = self.rng.random_range(min..max);
548 }
549
550 Ok(())
551 }
552
553 pub fn optimize(&mut self, problem: &IsingModel) -> VqaResult<VqaResults> {
555 println!("Starting variational quantum annealing optimization...");
556
557 let exact_params = Self::exact_parameter_count(&self.config.ansatz, problem.num_qubits)?;
562 if exact_params != self.parameters.len() {
563 self.parameters = vec![0.0; exact_params];
564 self.initialize_parameters()?;
565 self.optimizer_state =
566 Self::initialize_optimizer_state(&self.config.optimizer, exact_params)?;
567 }
568
569 self.history.start_time = Instant::now();
570 let mut best_energy = f64::INFINITY;
571 let mut best_solution = vec![0; problem.num_qubits];
572 let mut best_parameters = self.parameters.clone();
573
574 for iteration in 0..self.config.max_iterations {
575 let iteration_start = Instant::now();
576
577 if let Some(max_runtime) = self.config.max_runtime {
579 if self.history.start_time.elapsed() > max_runtime {
580 println!("Maximum runtime exceeded");
581 break;
582 }
583 }
584
585 let current_params = self.parameters.clone();
587 let (energy, solution) = self.evaluate_objective(problem, ¤t_params)?;
588
589 if energy < best_energy {
591 best_energy = energy;
592 best_solution = solution;
593 best_parameters = self.parameters.clone();
594 }
595
596 self.history.energies.push(energy);
598 self.history.parameters.push(self.parameters.clone());
599
600 let gradients = if self.config.use_gradients {
602 let grads = self.compute_gradients(problem)?;
603 self.history.gradients.push(grads.clone());
604 Some(grads)
605 } else {
606 None
607 };
608
609 self.update_parameters(gradients.as_ref().map(std::vec::Vec::as_slice))?;
611
612 if iteration % self.config.log_frequency == 0 {
614 let grad_norm = gradients
615 .as_ref()
616 .map_or(0.0, |g| g.iter().map(|&x| x.powi(2)).sum::<f64>().sqrt());
617
618 println!(
619 "Iteration {}: Energy = {:.6}, Gradient norm = {:.6}, Time = {:.2?}",
620 iteration,
621 energy,
622 grad_norm,
623 iteration_start.elapsed()
624 );
625 }
626
627 if self.check_convergence()? {
629 println!("Converged at iteration {iteration}");
630 break;
631 }
632 }
633
634 let total_time = self.history.start_time.elapsed();
635
636 let statistics = self.calculate_statistics();
638
639 Ok(VqaResults {
640 best_solution,
641 best_energy,
642 optimal_parameters: best_parameters,
643 energy_history: self.history.energies.clone(),
644 parameter_history: self.history.parameters.clone(),
645 gradient_norms: self
646 .history
647 .gradients
648 .iter()
649 .map(|g| g.iter().map(|&x| x.powi(2)).sum::<f64>().sqrt())
650 .collect(),
651 iterations_completed: self.history.energies.len(),
652 converged: self.check_convergence()?,
653 total_time,
654 statistics,
655 })
656 }
657
658 fn evaluate_objective(
660 &mut self,
661 problem: &IsingModel,
662 parameters: &[f64],
663 ) -> VqaResult<(f64, Vec<i8>)> {
664 self.history.function_evals += 1;
665
666 let circuit = self.build_quantum_circuit(problem, parameters)?;
668
669 let modified_problem = self.apply_circuit_to_problem(problem, &circuit)?;
671
672 let mut simulator = QuantumAnnealingSimulator::new(self.config.annealing_params.clone())
674 .map_err(|e| VqaError::OptimizationFailed(e.to_string()))?;
675
676 let mut best_energy = f64::INFINITY;
677 let mut best_solution = vec![0; problem.num_qubits];
678
679 for _ in 0..self.config.num_shots {
681 let result = simulator
682 .solve(&modified_problem)
683 .map_err(|e| VqaError::OptimizationFailed(e.to_string()))?;
684
685 if result.best_energy < best_energy {
686 best_energy = result.best_energy;
687 best_solution = result.best_spins;
688 }
689 }
690
691 Ok((best_energy, best_solution))
692 }
693
694 fn build_quantum_circuit(
696 &self,
697 problem: &IsingModel,
698 parameters: &[f64],
699 ) -> VqaResult<QuantumCircuit> {
700 let num_qubits = problem.num_qubits;
701 let mut circuit = QuantumCircuit::new(num_qubits);
702
703 match &self.config.ansatz {
704 AnsatzType::HardwareEfficient {
705 depth,
706 entangling_gates,
707 } => {
708 self.build_hardware_efficient_circuit(
709 &mut circuit,
710 *depth,
711 entangling_gates,
712 parameters,
713 )?;
714 }
715
716 AnsatzType::QaoaInspired { layers, mixer_type } => {
717 self.build_qaoa_inspired_circuit(
718 &mut circuit,
719 problem,
720 *layers,
721 mixer_type,
722 parameters,
723 )?;
724 }
725
726 AnsatzType::AdiabaticInspired {
727 time_steps,
728 evolution_time,
729 } => {
730 self.build_adiabatic_inspired_circuit(
731 &mut circuit,
732 problem,
733 *time_steps,
734 *evolution_time,
735 parameters,
736 )?;
737 }
738
739 AnsatzType::Custom { structure } => {
740 self.build_custom_circuit(&mut circuit, structure, parameters)?;
741 }
742 }
743
744 Ok(circuit)
745 }
746
747 fn build_hardware_efficient_circuit(
749 &self,
750 circuit: &mut QuantumCircuit,
751 depth: usize,
752 entangling_gates: &EntanglingGateType,
753 parameters: &[f64],
754 ) -> VqaResult<()> {
755 let num_qubits = circuit.num_qubits;
756 let mut param_idx = 0;
757
758 for layer in 0..depth {
759 for qubit in 0..num_qubits {
763 if param_idx < parameters.len() {
764 circuit.add_gate(QuantumGate::RY {
765 qubit,
766 angle: ParameterRef::scaled(param_idx, parameters[param_idx]),
767 });
768 param_idx += 1;
769 }
770
771 if param_idx < parameters.len() {
772 circuit.add_gate(QuantumGate::RZ {
773 qubit,
774 angle: ParameterRef::scaled(param_idx, parameters[param_idx]),
775 });
776 param_idx += 1;
777 }
778 }
779
780 match entangling_gates {
782 EntanglingGateType::CNot => {
783 for qubit in 0..num_qubits - 1 {
784 circuit.add_gate(QuantumGate::CNOT {
785 control: qubit,
786 target: qubit + 1,
787 });
788 }
789 }
790
791 EntanglingGateType::CZ => {
792 for qubit in 0..num_qubits - 1 {
793 circuit.add_gate(QuantumGate::CZ {
794 control: qubit,
795 target: qubit + 1,
796 });
797 }
798 }
799
800 EntanglingGateType::ZZ => {
801 for qubit in 0..num_qubits - 1 {
802 if param_idx < parameters.len() {
803 circuit.add_gate(QuantumGate::ZZ {
804 qubit1: qubit,
805 qubit2: qubit + 1,
806 angle: ParameterRef::scaled(param_idx, parameters[param_idx]),
807 });
808 param_idx += 1;
809 }
810 }
811 }
812
813 EntanglingGateType::XY => {
814 for qubit in 0..num_qubits - 1 {
816 if param_idx < parameters.len() {
817 circuit.add_gate(QuantumGate::CNOT {
819 control: qubit,
820 target: qubit + 1,
821 });
822 param_idx += 1;
823 }
824 }
825 }
826 }
827 }
828
829 Ok(())
830 }
831
832 fn build_qaoa_inspired_circuit(
834 &self,
835 circuit: &mut QuantumCircuit,
836 problem: &IsingModel,
837 layers: usize,
838 mixer_type: &MixerType,
839 parameters: &[f64],
840 ) -> VqaResult<()> {
841 let num_qubits = circuit.num_qubits;
842
843 for layer in 0..layers {
844 let gamma_idx = layer * 2;
845 let beta_idx = layer * 2 + 1;
846
847 if gamma_idx >= parameters.len() || beta_idx >= parameters.len() {
848 break;
849 }
850
851 let gamma = parameters[gamma_idx];
852 let beta = parameters[beta_idx];
853
854 for i in 0..num_qubits {
856 for j in (i + 1)..num_qubits {
857 if let Ok(coupling) = problem.get_coupling(i, j) {
858 if coupling != 0.0 {
859 circuit.add_gate(QuantumGate::ZZ {
860 qubit1: i,
861 qubit2: j,
862 angle: ParameterRef::scaled(gamma_idx, gamma * coupling),
863 });
864 }
865 }
866 }
867
868 if let Ok(bias) = problem.get_bias(i) {
870 if bias != 0.0 {
871 circuit.add_gate(QuantumGate::RZ {
872 qubit: i,
873 angle: ParameterRef::scaled(gamma_idx, gamma * bias),
874 });
875 }
876 }
877 }
878
879 match mixer_type {
881 MixerType::XRotation => {
882 for qubit in 0..num_qubits {
883 circuit.add_gate(QuantumGate::RX {
884 qubit,
885 angle: ParameterRef::scaled(beta_idx, beta),
886 });
887 }
888 }
889
890 MixerType::XY => {
891 for qubit in 0..num_qubits - 1 {
893 circuit.add_gate(QuantumGate::CNOT {
894 control: qubit,
895 target: qubit + 1,
896 });
897 }
898 }
899
900 MixerType::MultiAngle => {
901 for qubit in 0..num_qubits {
903 circuit.add_gate(QuantumGate::RX {
904 qubit,
905 angle: ParameterRef::scaled(beta_idx, beta),
906 });
907 circuit.add_gate(QuantumGate::RY {
908 qubit,
909 angle: ParameterRef::scaled(beta_idx, beta * 0.5),
910 });
911 }
912 }
913 }
914 }
915
916 Ok(())
917 }
918
919 fn build_adiabatic_inspired_circuit(
921 &self,
922 circuit: &mut QuantumCircuit,
923 problem: &IsingModel,
924 time_steps: usize,
925 evolution_time: f64,
926 parameters: &[f64],
927 ) -> VqaResult<()> {
928 let num_qubits = circuit.num_qubits;
929 let dt = evolution_time / time_steps as f64;
930
931 for step in 0..time_steps {
932 if step >= parameters.len() {
933 break;
934 }
935
936 let s = parameters[step]; for qubit in 0..num_qubits {
940 circuit.add_gate(QuantumGate::RX {
941 qubit,
942 angle: ParameterRef::scaled(step, -2.0 * (1.0 - s) * dt),
943 });
944 }
945
946 for i in 0..num_qubits {
948 for j in (i + 1)..num_qubits {
949 if let Ok(coupling) = problem.get_coupling(i, j) {
950 if coupling != 0.0 {
951 circuit.add_gate(QuantumGate::ZZ {
952 qubit1: i,
953 qubit2: j,
954 angle: ParameterRef::scaled(step, -s * coupling * dt),
955 });
956 }
957 }
958 }
959 }
960 }
961
962 Ok(())
963 }
964
965 fn build_custom_circuit(
967 &self,
968 circuit: &mut QuantumCircuit,
969 structure: &[QuantumGate],
970 parameters: &[f64],
971 ) -> VqaResult<()> {
972 for gate in structure {
973 let parameterized_gate = match gate {
975 QuantumGate::RX { qubit, angle } => {
976 let param_value = if angle.index < parameters.len() {
977 parameters[angle.index] * angle.scale
978 } else {
979 0.0
980 };
981 QuantumGate::RX {
982 qubit: *qubit,
983 angle: ParameterRef::scaled(angle.index, param_value),
984 }
985 }
986
987 QuantumGate::RY { qubit, angle } => {
988 let param_value = if angle.index < parameters.len() {
989 parameters[angle.index] * angle.scale
990 } else {
991 0.0
992 };
993 QuantumGate::RY {
994 qubit: *qubit,
995 angle: ParameterRef::scaled(angle.index, param_value),
996 }
997 }
998
999 QuantumGate::RZ { qubit, angle } => {
1000 let param_value = if angle.index < parameters.len() {
1001 parameters[angle.index] * angle.scale
1002 } else {
1003 0.0
1004 };
1005 QuantumGate::RZ {
1006 qubit: *qubit,
1007 angle: ParameterRef::scaled(angle.index, param_value),
1008 }
1009 }
1010
1011 QuantumGate::ZZ {
1012 qubit1,
1013 qubit2,
1014 angle,
1015 } => {
1016 let param_value = if angle.index < parameters.len() {
1017 parameters[angle.index] * angle.scale
1018 } else {
1019 0.0
1020 };
1021 QuantumGate::ZZ {
1022 qubit1: *qubit1,
1023 qubit2: *qubit2,
1024 angle: ParameterRef::scaled(angle.index, param_value),
1025 }
1026 }
1027
1028 _ => gate.clone(),
1030 };
1031
1032 circuit.add_gate(parameterized_gate);
1033 }
1034
1035 Ok(())
1036 }
1037
1038 fn apply_circuit_to_problem(
1060 &self,
1061 problem: &IsingModel,
1062 circuit: &QuantumCircuit,
1063 ) -> VqaResult<IsingModel> {
1064 let magnetizations = Self::compute_magnetizations(problem.num_qubits, circuit);
1065
1066 let mut modified = IsingModel::new(problem.num_qubits);
1067
1068 for (qubit, bias) in problem.biases() {
1070 let new_bias = bias * magnetizations[qubit];
1071 if new_bias != 0.0 {
1072 modified.set_bias(qubit, new_bias)?;
1073 }
1074 }
1075
1076 for coupling in problem.couplings() {
1078 let new_strength =
1079 coupling.strength * magnetizations[coupling.i] * magnetizations[coupling.j];
1080 if new_strength != 0.0 {
1081 modified.set_coupling(coupling.i, coupling.j, new_strength)?;
1082 }
1083 }
1084
1085 Ok(modified)
1086 }
1087
1088 fn compute_magnetizations(num_qubits: usize, circuit: &QuantumCircuit) -> Vec<f64> {
1096 let mut bloch: Vec<[f64; 3]> = vec![[0.0, 0.0, 1.0]; num_qubits];
1098
1099 const ENTANGLE_DAMP: f64 = std::f64::consts::FRAC_1_SQRT_2;
1103
1104 for gate in &circuit.gates {
1105 match gate {
1106 QuantumGate::RX { qubit, angle } => {
1107 if *qubit < num_qubits {
1108 bloch[*qubit] = rotate_x(bloch[*qubit], angle.scale);
1109 }
1110 }
1111 QuantumGate::RY { qubit, angle } => {
1112 if *qubit < num_qubits {
1113 bloch[*qubit] = rotate_y(bloch[*qubit], angle.scale);
1114 }
1115 }
1116 QuantumGate::RZ { qubit, angle } => {
1117 if *qubit < num_qubits {
1118 bloch[*qubit] = rotate_z(bloch[*qubit], angle.scale);
1119 }
1120 }
1121 QuantumGate::ZZ {
1122 qubit1,
1123 qubit2,
1124 angle,
1125 } => {
1126 let damp = angle.scale.cos().abs();
1130 for &q in &[*qubit1, *qubit2] {
1131 if q < num_qubits {
1132 bloch[q][0] *= damp;
1133 bloch[q][1] *= damp;
1134 bloch[q][2] *= damp;
1135 }
1136 }
1137 }
1138 QuantumGate::CNOT { control, target } | QuantumGate::CZ { control, target } => {
1139 for &q in &[*control, *target] {
1140 if q < num_qubits {
1141 bloch[q][0] *= ENTANGLE_DAMP;
1142 bloch[q][1] *= ENTANGLE_DAMP;
1143 bloch[q][2] *= ENTANGLE_DAMP;
1144 }
1145 }
1146 }
1147 }
1148 }
1149
1150 bloch.iter().map(|v| v[2]).collect()
1152 }
1153
1154 fn compute_gradients(&mut self, problem: &IsingModel) -> VqaResult<Vec<f64>> {
1156 self.history.gradient_evals += 1;
1157
1158 let mut gradients = vec![0.0; self.parameters.len()];
1159 let step = self.config.gradient_step;
1160
1161 for i in 0..self.parameters.len() {
1162 let mut params_plus = self.parameters.clone();
1164 let mut params_minus = self.parameters.clone();
1165
1166 params_plus[i] += step;
1167 params_minus[i] -= step;
1168
1169 let (energy_plus, _) = self.evaluate_objective(problem, ¶ms_plus)?;
1170 let (energy_minus, _) = self.evaluate_objective(problem, ¶ms_minus)?;
1171
1172 gradients[i] = (energy_plus - energy_minus) / (2.0 * step);
1174 }
1175
1176 Ok(gradients)
1177 }
1178
1179 fn update_parameters(&mut self, gradients: Option<&[f64]>) -> VqaResult<()> {
1181 match (&mut self.optimizer_state, &self.config.optimizer) {
1182 (
1183 OptimizerState::Adam { m, v, t },
1184 ClassicalOptimizer::Adam {
1185 learning_rate,
1186 beta1,
1187 beta2,
1188 epsilon,
1189 },
1190 ) => {
1191 if let Some(grads) = gradients {
1192 *t += 1;
1193
1194 for i in 0..self.parameters.len() {
1195 m[i] = (1.0 - beta1).mul_add(grads[i], beta1 * m[i]);
1197
1198 v[i] = (1.0 - beta2).mul_add(grads[i].powi(2), beta2 * v[i]);
1200
1201 let m_hat = m[i] / (1.0 - beta1.powi(*t as i32));
1203 let v_hat = v[i] / (1.0 - beta2.powi(*t as i32));
1204
1205 self.parameters[i] -= learning_rate * m_hat / (v_hat.sqrt() + epsilon);
1207 }
1208 }
1209 }
1210
1211 (
1212 OptimizerState::GradientDescent { .. },
1213 ClassicalOptimizer::GradientDescent { learning_rate },
1214 ) => {
1215 if let Some(grads) = gradients {
1216 for i in 0..self.parameters.len() {
1217 self.parameters[i] -= learning_rate * grads[i];
1218 }
1219 }
1220 }
1221
1222 _ => {
1223 return Err(VqaError::OptimizationFailed(
1225 "Optimizer not implemented".to_string(),
1226 ));
1227 }
1228 }
1229
1230 Ok(())
1231 }
1232
1233 fn check_convergence(&self) -> VqaResult<bool> {
1235 if self.history.energies.len() < 2 {
1236 return Ok(false);
1237 }
1238
1239 let recent_energies =
1240 &self.history.energies[self.history.energies.len().saturating_sub(5)..];
1241 let energy_range = recent_energies
1242 .iter()
1243 .copied()
1244 .fold(f64::NEG_INFINITY, f64::max)
1245 - recent_energies
1246 .iter()
1247 .copied()
1248 .fold(f64::INFINITY, f64::min);
1249
1250 Ok(energy_range < self.config.convergence_tolerance)
1251 }
1252
1253 fn calculate_statistics(&self) -> VqaStatistics {
1255 let average_energy = if self.history.energies.is_empty() {
1256 0.0
1257 } else {
1258 self.history.energies.iter().sum::<f64>() / self.history.energies.len() as f64
1259 };
1260
1261 let energy_variance = if self.history.energies.len() > 1 {
1262 let mean = average_energy;
1263 self.history
1264 .energies
1265 .iter()
1266 .map(|&e| (e - mean).powi(2))
1267 .sum::<f64>()
1268 / (self.history.energies.len() - 1) as f64
1269 } else {
1270 0.0
1271 };
1272
1273 let parameter_stats = self.calculate_parameter_statistics();
1275
1276 let total_annealing_time = self.history.start_time.elapsed();
1280
1281 let step_acceptance_rate = if self.history.energies.len() > 1 {
1284 let improving = self
1285 .history
1286 .energies
1287 .windows(2)
1288 .filter(|w| w[1] < w[0])
1289 .count();
1290 improving as f64 / (self.history.energies.len() - 1) as f64
1291 } else {
1292 0.0
1293 };
1294
1295 let average_step_size = if self.history.parameters.len() > 1 {
1298 let total: f64 = self
1299 .history
1300 .parameters
1301 .windows(2)
1302 .map(|w| {
1303 w[0].iter()
1304 .zip(w[1].iter())
1305 .map(|(a, b)| (a - b).powi(2))
1306 .sum::<f64>()
1307 .sqrt()
1308 })
1309 .sum();
1310 total / (self.history.parameters.len() - 1) as f64
1311 } else {
1312 0.0
1313 };
1314
1315 VqaStatistics {
1316 function_evaluations: self.history.function_evals,
1317 gradient_evaluations: self.history.gradient_evals,
1318 total_annealing_time,
1319 average_energy,
1320 energy_variance,
1321 parameter_stats,
1322 optimizer_stats: OptimizerStatistics {
1323 step_acceptance_rate,
1324 average_step_size,
1325 line_search_iterations: 0,
1326 optimizer_metrics: HashMap::new(),
1327 },
1328 }
1329 }
1330
1331 fn calculate_parameter_statistics(&self) -> ParameterStatistics {
1333 let average_magnitude = if self.parameters.is_empty() {
1334 0.0
1335 } else {
1336 self.parameters.iter().map(|&p| p.abs()).sum::<f64>() / self.parameters.len() as f64
1337 };
1338
1339 let parameter_variance = if self.parameters.len() > 1 {
1340 let mean = self.parameters.iter().sum::<f64>() / self.parameters.len() as f64;
1341 self.parameters
1342 .iter()
1343 .map(|&p| (p - mean).powi(2))
1344 .sum::<f64>()
1345 / (self.parameters.len() - 1) as f64
1346 } else {
1347 0.0
1348 };
1349
1350 let max_parameter_change = if self.history.parameters.len() > 1 {
1353 let num_params = self.parameters.len();
1354 let mut max_change = vec![0.0_f64; num_params];
1355 for window in self.history.parameters.windows(2) {
1356 for (idx, slot) in max_change.iter_mut().enumerate() {
1357 if let (Some(&prev), Some(&curr)) = (window[0].get(idx), window[1].get(idx)) {
1358 *slot = slot.max((curr - prev).abs());
1359 }
1360 }
1361 }
1362 max_change
1363 } else {
1364 Vec::new()
1365 };
1366
1367 ParameterStatistics {
1368 average_magnitude,
1369 parameter_variance,
1370 num_updates: self.history.parameters.len(),
1371 max_parameter_change,
1372 }
1373 }
1374}
1375
1376#[derive(Debug, Clone)]
1378pub struct QuantumCircuit {
1379 pub num_qubits: usize,
1381
1382 pub gates: Vec<QuantumGate>,
1384}
1385
1386impl QuantumCircuit {
1387 #[must_use]
1389 pub const fn new(num_qubits: usize) -> Self {
1390 Self {
1391 num_qubits,
1392 gates: Vec::new(),
1393 }
1394 }
1395
1396 pub fn add_gate(&mut self, gate: QuantumGate) {
1398 self.gates.push(gate);
1399 }
1400
1401 #[must_use]
1403 pub fn depth(&self) -> usize {
1404 self.gates.len()
1406 }
1407}
1408
1409fn rotate_x(v: [f64; 3], theta: f64) -> [f64; 3] {
1411 let (s, c) = theta.sin_cos();
1412 [
1413 v[0],
1414 c.mul_add(v[1], -(s * v[2])),
1415 s.mul_add(v[1], c * v[2]),
1416 ]
1417}
1418
1419fn rotate_y(v: [f64; 3], theta: f64) -> [f64; 3] {
1421 let (s, c) = theta.sin_cos();
1422 [
1423 s.mul_add(v[2], c * v[0]),
1424 v[1],
1425 c.mul_add(v[2], -(s * v[0])),
1426 ]
1427}
1428
1429fn rotate_z(v: [f64; 3], theta: f64) -> [f64; 3] {
1431 let (s, c) = theta.sin_cos();
1432 [
1433 c.mul_add(v[0], -(s * v[1])),
1434 s.mul_add(v[0], c * v[1]),
1435 v[2],
1436 ]
1437}
1438
1439#[must_use]
1443pub fn create_qaoa_vqa_config(layers: usize, max_iterations: usize) -> VqaConfig {
1444 VqaConfig {
1445 ansatz: AnsatzType::QaoaInspired {
1446 layers,
1447 mixer_type: MixerType::XRotation,
1448 },
1449 max_iterations,
1450 ..Default::default()
1451 }
1452}
1453
1454#[must_use]
1456pub fn create_hardware_efficient_vqa_config(depth: usize, max_iterations: usize) -> VqaConfig {
1457 VqaConfig {
1458 ansatz: AnsatzType::HardwareEfficient {
1459 depth,
1460 entangling_gates: EntanglingGateType::CNot,
1461 },
1462 max_iterations,
1463 ..Default::default()
1464 }
1465}
1466
1467#[must_use]
1469pub fn create_adiabatic_vqa_config(
1470 time_steps: usize,
1471 evolution_time: f64,
1472 max_iterations: usize,
1473) -> VqaConfig {
1474 VqaConfig {
1475 ansatz: AnsatzType::AdiabaticInspired {
1476 time_steps,
1477 evolution_time,
1478 },
1479 max_iterations,
1480 ..Default::default()
1481 }
1482}
1483
1484#[cfg(test)]
1485mod tests {
1486 use super::*;
1487
1488 #[test]
1489 fn test_vqa_config_creation() {
1490 let config = create_qaoa_vqa_config(3, 50);
1491
1492 match config.ansatz {
1493 AnsatzType::QaoaInspired { layers, .. } => {
1494 assert_eq!(layers, 3);
1495 }
1496 _ => panic!("Expected QAOA ansatz"),
1497 }
1498
1499 assert_eq!(config.max_iterations, 50);
1500 }
1501
1502 #[test]
1503 fn test_parameter_ref() {
1504 let param_ref = ParameterRef::new(5);
1505 assert_eq!(param_ref.index, 5);
1506 assert_eq!(param_ref.scale, 1.0);
1507
1508 let scaled_ref = ParameterRef::scaled(3, 2.5);
1509 assert_eq!(scaled_ref.index, 3);
1510 assert_eq!(scaled_ref.scale, 2.5);
1511 }
1512
1513 #[test]
1514 fn test_quantum_circuit() {
1515 let mut circuit = QuantumCircuit::new(3);
1516 assert_eq!(circuit.num_qubits, 3);
1517 assert_eq!(circuit.gates.len(), 0);
1518
1519 circuit.add_gate(QuantumGate::RX {
1520 qubit: 0,
1521 angle: ParameterRef::new(0),
1522 });
1523
1524 assert_eq!(circuit.gates.len(), 1);
1525 assert_eq!(circuit.depth(), 1);
1526 }
1527
1528 #[test]
1529 fn test_parameter_counting() {
1530 let ansatz = AnsatzType::QaoaInspired {
1531 layers: 5,
1532 mixer_type: MixerType::XRotation,
1533 };
1534
1535 let count = VariationalQuantumAnnealer::count_parameters(&ansatz)
1536 .expect("parameter counting should succeed");
1537 assert_eq!(count, 10); }
1539}