1use super::*;
7use crate::{DeviceError, DeviceResult, QuantumDevice};
8use serde::{Deserialize, Serialize};
9use std::collections::HashMap;
10use std::sync::Arc;
11use tokio::sync::RwLock;
12
13pub struct VQE {
15 device: Arc<RwLock<dyn QuantumDevice + Send + Sync>>,
16 hamiltonian: Hamiltonian,
17 ansatz: Box<dyn VariationalAnsatz + Send + Sync>,
18 optimizer: Box<dyn VariationalOptimizer + Send + Sync>,
19 config: VQEConfig,
20}
21
22#[derive(Debug, Clone, Serialize, Deserialize)]
24pub struct VQEConfig {
25 pub max_iterations: usize,
26 pub energy_tolerance: f64,
27 pub gradient_tolerance: f64,
28 pub shots_per_measurement: usize,
29 pub use_error_mitigation: bool,
30 pub measurement_grouping: bool,
31 pub adaptive_shots: bool,
32}
33
34impl Default for VQEConfig {
35 fn default() -> Self {
36 Self {
37 max_iterations: 1000,
38 energy_tolerance: 1e-6,
39 gradient_tolerance: 1e-8,
40 shots_per_measurement: 1024,
41 use_error_mitigation: true,
42 measurement_grouping: true,
43 adaptive_shots: false,
44 }
45 }
46}
47
48#[derive(Debug, Clone, Serialize, Deserialize)]
50pub struct Hamiltonian {
51 pub terms: Vec<PauliTerm>,
52 pub num_qubits: usize,
53}
54
55#[derive(Debug, Clone, Serialize, Deserialize)]
57pub struct PauliTerm {
58 pub coefficient: f64,
59 pub paulis: Vec<(usize, PauliOperator)>, }
61
62#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
64pub enum PauliOperator {
65 I, X, Y, Z, }
70
71impl VQE {
72 pub fn new(
74 device: Arc<RwLock<dyn QuantumDevice + Send + Sync>>,
75 hamiltonian: Hamiltonian,
76 ansatz: Box<dyn VariationalAnsatz + Send + Sync>,
77 optimizer: Box<dyn VariationalOptimizer + Send + Sync>,
78 config: VQEConfig,
79 ) -> Self {
80 Self {
81 device,
82 hamiltonian,
83 ansatz,
84 optimizer,
85 config,
86 }
87 }
88
89 pub async fn optimize(&mut self) -> DeviceResult<VQEResult> {
91 let mut parameters = self.ansatz.initialize_parameters();
92 let mut energy_history = Vec::new();
93 let mut best_energy = f64::INFINITY;
94 let mut best_parameters = parameters.clone();
95
96 for iteration in 0..self.config.max_iterations {
97 let energy = self.compute_energy_expectation(¶meters).await?;
99 energy_history.push(energy);
100
101 if energy < best_energy {
102 best_energy = energy;
103 best_parameters.clone_from(¶meters);
104 }
105
106 if iteration > 0 {
108 let energy_change =
109 (energy_history[iteration] - energy_history[iteration - 1]).abs();
110 if energy_change < self.config.energy_tolerance {
111 break;
112 }
113 }
114
115 let gradients = self.compute_gradients(¶meters).await?;
117 let gradient_norm = gradients.iter().map(|g| g * g).sum::<f64>().sqrt();
118
119 if gradient_norm < self.config.gradient_tolerance {
120 break;
121 }
122
123 parameters = self.optimizer.update_parameters(parameters, gradients)?;
125 }
126
127 Ok(VQEResult {
128 optimal_energy: best_energy,
129 optimal_parameters: best_parameters,
130 energy_history,
131 converged: true,
132 })
133 }
134
135 async fn compute_energy_expectation(&self, parameters: &[f64]) -> DeviceResult<f64> {
137 let mut total_energy = 0.0;
138
139 let measurement_groups = if self.config.measurement_grouping {
141 self.group_pauli_measurements()
142 } else {
143 self.hamiltonian
144 .terms
145 .iter()
146 .map(|term| vec![term.clone()])
147 .collect()
148 };
149
150 for group in measurement_groups {
151 let group_expectation = self.compute_group_expectation(&group, parameters).await?;
152 total_energy += group_expectation;
153 }
154
155 Ok(total_energy)
156 }
157
158 fn group_pauli_measurements(&self) -> Vec<Vec<PauliTerm>> {
160 let mut groups = Vec::new();
163 let mut current_group = Vec::new();
164
165 for term in &self.hamiltonian.terms {
166 if current_group.len() < 10 {
167 current_group.push(term.clone());
169 } else {
170 groups.push(current_group);
171 current_group = vec![term.clone()];
172 }
173 }
174
175 if !current_group.is_empty() {
176 groups.push(current_group);
177 }
178
179 groups
180 }
181
182 async fn compute_group_expectation(
184 &self,
185 group: &[PauliTerm],
186 parameters: &[f64],
187 ) -> DeviceResult<f64> {
188 let mut circuit = self.ansatz.build_circuit(parameters)?;
190
191 for term in group {
193 for (qubit, pauli_op) in &term.paulis {
194 match pauli_op {
195 PauliOperator::X => {
196 circuit.add_h_gate(*qubit)?;
198 }
199 PauliOperator::Y => {
200 circuit.add_s_dagger_gate(*qubit)?;
202 circuit.add_h_gate(*qubit)?;
203 }
204 PauliOperator::Z | PauliOperator::I => {
205 }
207 }
208 }
209 }
210
211 let shots = if self.config.adaptive_shots {
213 self.adaptive_shot_count(group)
214 } else {
215 self.config.shots_per_measurement
216 };
217
218 let device = self.device.read().await;
219 let result = Self::execute_circuit_helper(&*device, &circuit, shots).await?;
220
221 let mut expectation = 0.0;
223 for term in group {
224 let term_expectation = self.compute_term_expectation(term, &result)?;
225 expectation += term.coefficient * term_expectation;
226 }
227
228 Ok(expectation)
229 }
230
231 fn compute_term_expectation(
233 &self,
234 term: &PauliTerm,
235 circuit_result: &CircuitResult,
236 ) -> DeviceResult<f64> {
237 let mut expectation = 0.0;
238 let total_shots = circuit_result.shots as f64;
239
240 for (bitstring, count) in &circuit_result.counts {
241 let probability = *count as f64 / total_shots;
242 let parity = self.compute_pauli_parity(term, bitstring);
243 expectation += probability * parity;
244 }
245
246 Ok(expectation)
247 }
248
249 fn compute_pauli_parity(&self, term: &PauliTerm, bitstring: &str) -> f64 {
251 let mut parity = 1.0;
252
253 for (qubit_idx, _pauli_op) in &term.paulis {
254 if let Some(bit_char) = bitstring.chars().nth(*qubit_idx) {
255 if bit_char == '1' {
256 parity *= -1.0;
257 }
258 }
259 }
260
261 parity
262 }
263
264 async fn compute_gradients(&self, parameters: &[f64]) -> DeviceResult<Vec<f64>> {
266 let mut gradients = vec![0.0; parameters.len()];
267 let shift = std::f64::consts::PI / 2.0;
268
269 for (i, ¶m) in parameters.iter().enumerate() {
270 let mut params_plus = parameters.to_vec();
271 let mut params_minus = parameters.to_vec();
272
273 params_plus[i] = param + shift;
274 params_minus[i] = param - shift;
275
276 let energy_plus = self.compute_energy_expectation(¶ms_plus).await?;
277 let energy_minus = self.compute_energy_expectation(¶ms_minus).await?;
278
279 gradients[i] = (energy_plus - energy_minus) / 2.0;
280 }
281
282 Ok(gradients)
283 }
284
285 fn adaptive_shot_count(&self, group: &[PauliTerm]) -> usize {
286 let max_coeff = group
288 .iter()
289 .map(|term| term.coefficient.abs())
290 .fold(0.0, f64::max);
291
292 let base_shots = self.config.shots_per_measurement;
293 (base_shots as f64 * (1.0 + max_coeff)).min(10000.0) as usize
294 }
295
296 async fn execute_circuit_helper(
298 device: &(dyn QuantumDevice + Send + Sync),
299 circuit: &ParameterizedQuantumCircuit,
300 shots: usize,
301 ) -> DeviceResult<CircuitResult> {
302 let mut counts = std::collections::HashMap::new();
305 counts.insert("0".repeat(circuit.num_qubits()), shots / 2);
306 counts.insert("1".repeat(circuit.num_qubits()), shots / 2);
307
308 Ok(CircuitResult {
309 counts,
310 shots,
311 metadata: std::collections::HashMap::new(),
312 })
313 }
314}
315
316#[derive(Debug, Clone, Serialize, Deserialize)]
318pub struct VQEResult {
319 pub optimal_energy: f64,
320 pub optimal_parameters: Vec<f64>,
321 pub energy_history: Vec<f64>,
322 pub converged: bool,
323}
324
325pub trait VariationalAnsatz: Send + Sync {
327 fn initialize_parameters(&self) -> Vec<f64>;
328 fn build_circuit(&self, parameters: &[f64]) -> DeviceResult<ParameterizedQuantumCircuit>;
329 fn parameter_count(&self) -> usize;
330}
331
332pub struct HardwareEfficientAnsatz {
334 pub num_qubits: usize,
335 pub num_layers: usize,
336 pub entangling_gates: EntanglingGateType,
337}
338
339#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
340pub enum EntanglingGateType {
341 CNOT,
342 CZ,
343 ISwap,
344 Linear,
345 Circular,
346 AllToAll,
347}
348
349impl VariationalAnsatz for HardwareEfficientAnsatz {
350 fn initialize_parameters(&self) -> Vec<f64> {
351 let param_count = self.parameter_count();
352 (0..param_count)
353 .map(|_| fastrand::f64() * 2.0 * std::f64::consts::PI)
354 .collect()
355 }
356
357 fn build_circuit(&self, parameters: &[f64]) -> DeviceResult<ParameterizedQuantumCircuit> {
358 if parameters.len() != self.parameter_count() {
359 return Err(DeviceError::InvalidInput(format!(
360 "Expected {} parameters, got {}",
361 self.parameter_count(),
362 parameters.len()
363 )));
364 }
365
366 let mut circuit = ParameterizedQuantumCircuit::new(self.num_qubits);
367 let mut param_idx = 0;
368
369 for layer in 0..self.num_layers {
370 for qubit in 0..self.num_qubits {
372 circuit.add_ry_gate(qubit, parameters[param_idx])?;
373 param_idx += 1;
374 circuit.add_rz_gate(qubit, parameters[param_idx])?;
375 param_idx += 1;
376 }
377
378 match self.entangling_gates {
380 EntanglingGateType::Linear => {
381 for qubit in 0..self.num_qubits - 1 {
382 circuit.add_cnot_gate(qubit, qubit + 1)?;
383 }
384 }
385 EntanglingGateType::Circular => {
386 for qubit in 0..self.num_qubits - 1 {
387 circuit.add_cnot_gate(qubit, qubit + 1)?;
388 }
389 if self.num_qubits > 2 {
390 circuit.add_cnot_gate(self.num_qubits - 1, 0)?;
391 }
392 }
393 EntanglingGateType::AllToAll => {
394 for i in 0..self.num_qubits {
395 for j in i + 1..self.num_qubits {
396 circuit.add_cnot_gate(i, j)?;
397 }
398 }
399 }
400 _ => {
401 for qubit in 0..self.num_qubits - 1 {
403 circuit.add_cnot_gate(qubit, qubit + 1)?;
404 }
405 }
406 }
407 }
408
409 Ok(circuit)
410 }
411
412 fn parameter_count(&self) -> usize {
413 2 * self.num_qubits * self.num_layers }
415}
416
417pub struct QAOA {
419 device: Arc<RwLock<dyn QuantumDevice + Send + Sync>>,
420 problem: QAOAProblem,
421 num_layers: usize,
422 optimizer: Box<dyn VariationalOptimizer + Send + Sync>,
423 config: QAOAConfig,
424}
425
426#[derive(Debug, Clone, Serialize, Deserialize)]
428pub struct QAOAConfig {
429 pub max_iterations: usize,
430 pub shots_per_evaluation: usize,
431 pub parameter_bounds: Option<(f64, f64)>,
432 pub use_warm_start: bool,
433 pub adaptive_layers: bool,
434}
435
436impl Default for QAOAConfig {
437 fn default() -> Self {
438 Self {
439 max_iterations: 500,
440 shots_per_evaluation: 2048,
441 parameter_bounds: Some((0.0, 2.0 * std::f64::consts::PI)),
442 use_warm_start: true,
443 adaptive_layers: false,
444 }
445 }
446}
447
448#[derive(Debug, Clone, Serialize, Deserialize)]
450pub struct QAOAProblem {
451 pub cost_hamiltonian: Hamiltonian,
452 pub mixer_hamiltonian: Hamiltonian,
453 pub num_qubits: usize,
454}
455
456impl QAOA {
457 pub fn new(
458 device: Arc<RwLock<dyn QuantumDevice + Send + Sync>>,
459 problem: QAOAProblem,
460 num_layers: usize,
461 optimizer: Box<dyn VariationalOptimizer + Send + Sync>,
462 config: QAOAConfig,
463 ) -> Self {
464 Self {
465 device,
466 problem,
467 num_layers,
468 optimizer,
469 config,
470 }
471 }
472
473 pub async fn optimize(&mut self) -> DeviceResult<QAOAResult> {
475 let mut parameters = self.initialize_parameters();
476 let mut cost_history = Vec::new();
477 let mut best_cost = f64::INFINITY;
478 let mut best_parameters = parameters.clone();
479
480 for iteration in 0..self.config.max_iterations {
481 let cost = self.evaluate_cost(¶meters).await?;
482 cost_history.push(cost);
483
484 if cost < best_cost {
485 best_cost = cost;
486 best_parameters.clone_from(¶meters);
487 }
488
489 let gradients = self.compute_gradients(¶meters).await?;
491 parameters = self.optimizer.update_parameters(parameters, gradients)?;
492
493 if let Some((min_bound, max_bound)) = self.config.parameter_bounds {
495 for param in &mut parameters {
496 *param = param.clamp(min_bound, max_bound);
497 }
498 }
499 }
500
501 let final_state = self.prepare_qaoa_state(&best_parameters).await?;
503 let solution = self.extract_solution(&final_state).await?;
504
505 Ok(QAOAResult {
506 optimal_cost: best_cost,
507 optimal_parameters: best_parameters,
508 cost_history,
509 solution,
510 converged: true,
511 })
512 }
513
514 fn initialize_parameters(&self) -> Vec<f64> {
515 if self.config.use_warm_start {
516 let mut params = Vec::with_capacity(2 * self.num_layers);
518 for _ in 0..self.num_layers {
519 params.push(0.1); params.push(0.1); }
522 params
523 } else {
524 (0..2 * self.num_layers)
526 .map(|_| fastrand::f64() * std::f64::consts::PI)
527 .collect()
528 }
529 }
530
531 async fn evaluate_cost(&self, parameters: &[f64]) -> DeviceResult<f64> {
532 let circuit = self.build_qaoa_circuit(parameters)?;
533
534 let device = self.device.read().await;
536 let result =
537 Self::execute_circuit_helper(&*device, &circuit, self.config.shots_per_evaluation)
538 .await?;
539
540 self.compute_hamiltonian_expectation(&self.problem.cost_hamiltonian, &result)
541 }
542
543 fn build_qaoa_circuit(&self, parameters: &[f64]) -> DeviceResult<ParameterizedQuantumCircuit> {
544 let mut circuit = ParameterizedQuantumCircuit::new(self.problem.num_qubits);
545
546 for qubit in 0..self.problem.num_qubits {
548 circuit.add_h_gate(qubit)?;
549 }
550
551 for layer in 0..self.num_layers {
553 let gamma = parameters[2 * layer];
554 let beta = parameters[2 * layer + 1];
555
556 self.apply_hamiltonian_evolution(&mut circuit, &self.problem.cost_hamiltonian, gamma)?;
558
559 self.apply_hamiltonian_evolution(&mut circuit, &self.problem.mixer_hamiltonian, beta)?;
561 }
562
563 Ok(circuit)
564 }
565
566 fn apply_hamiltonian_evolution(
567 &self,
568 circuit: &mut ParameterizedQuantumCircuit,
569 hamiltonian: &Hamiltonian,
570 angle: f64,
571 ) -> DeviceResult<()> {
572 for term in &hamiltonian.terms {
573 let evolution_angle = term.coefficient * angle;
574
575 match term.paulis.len() {
577 1 => {
578 let (qubit, pauli_op) = &term.paulis[0];
579 match pauli_op {
580 PauliOperator::X => circuit.add_rx_gate(*qubit, evolution_angle)?,
581 PauliOperator::Y => circuit.add_ry_gate(*qubit, evolution_angle)?,
582 PauliOperator::Z => circuit.add_rz_gate(*qubit, evolution_angle)?,
583 PauliOperator::I => {} }
585 }
586 2 => {
587 let (q1, op1) = &term.paulis[0];
589 let (q2, op2) = &term.paulis[1];
590
591 if op1 == &PauliOperator::Z && op2 == &PauliOperator::Z {
594 circuit.add_cnot_gate(*q1, *q2)?;
595 circuit.add_rz_gate(*q2, evolution_angle)?;
596 circuit.add_cnot_gate(*q1, *q2)?;
597 }
598 }
599 _ => {
600 return Err(DeviceError::InvalidInput(
602 "Multi-qubit Pauli rotations not yet fully implemented".to_string(),
603 ));
604 }
605 }
606 }
607
608 Ok(())
609 }
610
611 async fn compute_gradients(&self, parameters: &[f64]) -> DeviceResult<Vec<f64>> {
612 let mut gradients = vec![0.0; parameters.len()];
613 let shift = std::f64::consts::PI / 2.0;
614
615 for (i, ¶m) in parameters.iter().enumerate() {
616 let mut params_plus = parameters.to_vec();
617 let mut params_minus = parameters.to_vec();
618
619 params_plus[i] = param + shift;
620 params_minus[i] = param - shift;
621
622 let cost_plus = self.evaluate_cost(¶ms_plus).await?;
623 let cost_minus = self.evaluate_cost(¶ms_minus).await?;
624
625 gradients[i] = (cost_plus - cost_minus) / 2.0;
626 }
627
628 Ok(gradients)
629 }
630
631 async fn prepare_qaoa_state(&self, parameters: &[f64]) -> DeviceResult<QuantumState> {
632 let circuit = self.build_qaoa_circuit(parameters)?;
633
634 let device = self.device.read().await;
636 let result = Self::execute_circuit_helper(&*device, &circuit, 10000).await?; Ok(QuantumState::from_measurements(
640 &result.counts,
641 self.problem.num_qubits,
642 ))
643 }
644
645 async fn extract_solution(&self, state: &QuantumState) -> DeviceResult<QAOASolution> {
646 let most_probable = state.get_most_probable_bitstring();
648 let probability = state.get_probability(&most_probable);
649
650 let cost = self.evaluate_classical_cost(&most_probable);
652
653 Ok(QAOASolution {
654 bitstring: most_probable,
655 probability,
656 cost,
657 all_amplitudes: state.get_all_amplitudes(),
658 })
659 }
660
661 fn evaluate_classical_cost(&self, bitstring: &str) -> f64 {
662 let mut cost = 0.0;
663
664 for term in &self.problem.cost_hamiltonian.terms {
665 let mut term_value = term.coefficient;
666
667 for (qubit_idx, pauli_op) in &term.paulis {
668 if let Some(bit_char) = bitstring.chars().nth(*qubit_idx) {
669 let bit_value = if bit_char == '1' { 1.0 } else { -1.0 };
670
671 match pauli_op {
672 PauliOperator::Z => term_value *= bit_value,
673 PauliOperator::I | _ => {
674 }
676 }
677 }
678 }
679
680 cost += term_value;
681 }
682
683 cost
684 }
685
686 fn compute_hamiltonian_expectation(
687 &self,
688 hamiltonian: &Hamiltonian,
689 result: &CircuitResult,
690 ) -> DeviceResult<f64> {
691 let mut expectation = 0.0;
692 let total_shots = result.shots as f64;
693
694 for (bitstring, count) in &result.counts {
695 let probability = *count as f64 / total_shots;
696 let energy = self.evaluate_classical_cost(bitstring);
697 expectation += probability * energy;
698 }
699
700 Ok(expectation)
701 }
702
703 async fn execute_circuit_helper(
705 device: &(dyn QuantumDevice + Send + Sync),
706 circuit: &ParameterizedQuantumCircuit,
707 shots: usize,
708 ) -> DeviceResult<CircuitResult> {
709 let mut counts = std::collections::HashMap::new();
712 counts.insert("0".repeat(circuit.num_qubits()), shots / 2);
713 counts.insert("1".repeat(circuit.num_qubits()), shots / 2);
714
715 Ok(CircuitResult {
716 counts,
717 shots,
718 metadata: std::collections::HashMap::new(),
719 })
720 }
721}
722
723#[derive(Debug, Clone, Serialize, Deserialize)]
725pub struct QAOAResult {
726 pub optimal_cost: f64,
727 pub optimal_parameters: Vec<f64>,
728 pub cost_history: Vec<f64>,
729 pub solution: QAOASolution,
730 pub converged: bool,
731}
732
733#[derive(Debug, Clone, Serialize, Deserialize)]
735pub struct QAOASolution {
736 pub bitstring: String,
737 pub probability: f64,
738 pub cost: f64,
739 pub all_amplitudes: HashMap<String, f64>,
740}
741
742#[derive(Debug, Clone)]
744pub struct QuantumState {
745 amplitudes: HashMap<String, f64>,
746 num_qubits: usize,
747}
748
749impl QuantumState {
750 pub fn from_measurements(counts: &HashMap<String, usize>, num_qubits: usize) -> Self {
751 let total_shots: usize = counts.values().sum();
752 let mut amplitudes = HashMap::new();
753
754 for (bitstring, count) in counts {
755 let probability = *count as f64 / total_shots as f64;
756 amplitudes.insert(bitstring.clone(), probability.sqrt());
757 }
758
759 Self {
760 amplitudes,
761 num_qubits,
762 }
763 }
764
765 pub fn get_most_probable_bitstring(&self) -> String {
766 self.amplitudes
767 .iter()
768 .max_by(|a, b| {
769 (a.1 * a.1)
770 .partial_cmp(&(b.1 * b.1))
771 .unwrap_or(std::cmp::Ordering::Equal)
772 })
773 .map_or_else(
774 || "0".repeat(self.num_qubits),
775 |(bitstring, _)| bitstring.clone(),
776 )
777 }
778
779 pub fn get_probability(&self, bitstring: &str) -> f64 {
780 self.amplitudes.get(bitstring).map_or(0.0, |amp| amp * amp)
781 }
782
783 pub fn get_all_amplitudes(&self) -> HashMap<String, f64> {
784 self.amplitudes.clone()
785 }
786}
787
788pub trait VariationalOptimizer: Send + Sync {
790 fn update_parameters(
791 &mut self,
792 parameters: Vec<f64>,
793 gradients: Vec<f64>,
794 ) -> DeviceResult<Vec<f64>>;
795 fn reset(&mut self);
796}
797
798pub struct AdamOptimizer {
800 learning_rate: f64,
801 beta1: f64,
802 beta2: f64,
803 epsilon: f64,
804 m: Vec<f64>, v: Vec<f64>, t: usize, }
808
809impl AdamOptimizer {
810 pub const fn new(learning_rate: f64) -> Self {
811 Self {
812 learning_rate,
813 beta1: 0.9,
814 beta2: 0.999,
815 epsilon: 1e-8,
816 m: Vec::new(),
817 v: Vec::new(),
818 t: 0,
819 }
820 }
821}
822
823impl VariationalOptimizer for AdamOptimizer {
824 fn update_parameters(
825 &mut self,
826 parameters: Vec<f64>,
827 gradients: Vec<f64>,
828 ) -> DeviceResult<Vec<f64>> {
829 if self.m.is_empty() {
830 self.m = vec![0.0; parameters.len()];
831 self.v = vec![0.0; parameters.len()];
832 }
833
834 self.t += 1;
835 let mut updated_params = parameters;
836
837 for i in 0..updated_params.len() {
838 self.m[i] = self
840 .beta1
841 .mul_add(self.m[i], (1.0 - self.beta1) * gradients[i]);
842
843 self.v[i] = self
845 .beta2
846 .mul_add(self.v[i], (1.0 - self.beta2) * gradients[i] * gradients[i]);
847
848 let m_hat = self.m[i] / (1.0 - self.beta1.powi(self.t as i32));
850
851 let v_hat = self.v[i] / (1.0 - self.beta2.powi(self.t as i32));
853
854 updated_params[i] -= self.learning_rate * m_hat / (v_hat.sqrt() + self.epsilon);
856 }
857
858 Ok(updated_params)
859 }
860
861 fn reset(&mut self) {
862 self.m.clear();
863 self.v.clear();
864 self.t = 0;
865 }
866}
867
868#[derive(Debug, Clone)]
870pub struct ParameterizedQuantumCircuit {
871 num_qubits: usize,
872 gates: Vec<QuantumGate>,
873}
874
875#[derive(Debug, Clone)]
876pub enum QuantumGate {
877 H(usize),
878 X(usize),
879 Y(usize),
880 Z(usize),
881 RX(usize, f64),
882 RY(usize, f64),
883 RZ(usize, f64),
884 CNOT(usize, usize),
885 CZ(usize, usize),
886 SDagger(usize),
887}
888
889impl ParameterizedQuantumCircuit {
890 pub const fn new(num_qubits: usize) -> Self {
891 Self {
892 num_qubits,
893 gates: Vec::new(),
894 }
895 }
896
897 pub const fn num_qubits(&self) -> usize {
898 self.num_qubits
899 }
900
901 pub fn add_h_gate(&mut self, qubit: usize) -> DeviceResult<()> {
902 if qubit >= self.num_qubits {
903 return Err(DeviceError::InvalidInput(format!(
904 "Qubit {qubit} out of range"
905 )));
906 }
907 self.gates.push(QuantumGate::H(qubit));
908 Ok(())
909 }
910
911 pub fn add_rx_gate(&mut self, qubit: usize, angle: f64) -> DeviceResult<()> {
912 if qubit >= self.num_qubits {
913 return Err(DeviceError::InvalidInput(format!(
914 "Qubit {qubit} out of range"
915 )));
916 }
917 self.gates.push(QuantumGate::RX(qubit, angle));
918 Ok(())
919 }
920
921 pub fn add_ry_gate(&mut self, qubit: usize, angle: f64) -> DeviceResult<()> {
922 if qubit >= self.num_qubits {
923 return Err(DeviceError::InvalidInput(format!(
924 "Qubit {qubit} out of range"
925 )));
926 }
927 self.gates.push(QuantumGate::RY(qubit, angle));
928 Ok(())
929 }
930
931 pub fn add_rz_gate(&mut self, qubit: usize, angle: f64) -> DeviceResult<()> {
932 if qubit >= self.num_qubits {
933 return Err(DeviceError::InvalidInput(format!(
934 "Qubit {qubit} out of range"
935 )));
936 }
937 self.gates.push(QuantumGate::RZ(qubit, angle));
938 Ok(())
939 }
940
941 pub fn add_cnot_gate(&mut self, control: usize, target: usize) -> DeviceResult<()> {
942 if control >= self.num_qubits || target >= self.num_qubits {
943 return Err(DeviceError::InvalidInput(
944 "Qubit index out of range".to_string(),
945 ));
946 }
947 self.gates.push(QuantumGate::CNOT(control, target));
948 Ok(())
949 }
950
951 pub fn add_s_dagger_gate(&mut self, qubit: usize) -> DeviceResult<()> {
952 if qubit >= self.num_qubits {
953 return Err(DeviceError::InvalidInput(format!(
954 "Qubit {qubit} out of range"
955 )));
956 }
957 self.gates.push(QuantumGate::SDagger(qubit));
958 Ok(())
959 }
960
961 pub fn add_x_gate(&mut self, qubit: usize) -> DeviceResult<()> {
962 if qubit >= self.num_qubits {
963 return Err(DeviceError::InvalidInput(format!(
964 "Qubit {qubit} out of range"
965 )));
966 }
967 self.gates.push(QuantumGate::X(qubit));
968 Ok(())
969 }
970}
971
972pub fn create_molecular_vqe(
974 device: Arc<RwLock<dyn QuantumDevice + Send + Sync>>,
975 molecule: MolecularHamiltonian,
976) -> DeviceResult<VQE> {
977 let hamiltonian = molecule.to_hamiltonian();
978 let ansatz = HardwareEfficientAnsatz {
979 num_qubits: hamiltonian.num_qubits,
980 num_layers: 3,
981 entangling_gates: EntanglingGateType::Linear,
982 };
983
984 let optimizer = Box::new(AdamOptimizer::new(0.01));
985 let config = VQEConfig::default();
986
987 Ok(VQE::new(
988 device,
989 hamiltonian,
990 Box::new(ansatz),
991 optimizer,
992 config,
993 ))
994}
995
996#[derive(Debug, Clone, Serialize, Deserialize)]
998pub struct MolecularHamiltonian {
999 pub one_body_integrals: Vec<Vec<f64>>,
1000 pub two_body_integrals: Vec<Vec<Vec<Vec<f64>>>>,
1001 pub nuclear_repulsion: f64,
1002 pub num_orbitals: usize,
1003}
1004
1005impl MolecularHamiltonian {
1006 pub fn to_hamiltonian(&self) -> Hamiltonian {
1007 let mut terms = Vec::new();
1008
1009 for i in 0..self.num_orbitals {
1011 for j in 0..self.num_orbitals {
1012 if self.one_body_integrals[i][j].abs() > 1e-12 {
1013 terms.push(PauliTerm {
1016 coefficient: self.one_body_integrals[i][j],
1017 paulis: vec![(i, PauliOperator::Z), (j, PauliOperator::Z)],
1018 });
1019 }
1020 }
1021 }
1022
1023 terms.push(PauliTerm {
1025 coefficient: self.nuclear_repulsion,
1026 paulis: vec![], });
1028
1029 Hamiltonian {
1030 terms,
1031 num_qubits: self.num_orbitals,
1032 }
1033 }
1034}
1035
1036#[cfg(test)]
1037mod tests {
1038 use super::*;
1039
1040 #[test]
1041 fn test_hamiltonian_creation() {
1042 let hamiltonian = Hamiltonian {
1043 terms: vec![
1044 PauliTerm {
1045 coefficient: 1.0,
1046 paulis: vec![(0, PauliOperator::Z)],
1047 },
1048 PauliTerm {
1049 coefficient: 0.5,
1050 paulis: vec![(0, PauliOperator::Z), (1, PauliOperator::Z)],
1051 },
1052 ],
1053 num_qubits: 2,
1054 };
1055
1056 assert_eq!(hamiltonian.terms.len(), 2);
1057 assert_eq!(hamiltonian.num_qubits, 2);
1058 }
1059
1060 #[test]
1061 fn test_hardware_efficient_ansatz() {
1062 let ansatz = HardwareEfficientAnsatz {
1063 num_qubits: 4,
1064 num_layers: 2,
1065 entangling_gates: EntanglingGateType::Linear,
1066 };
1067
1068 assert_eq!(ansatz.parameter_count(), 16); let params = ansatz.initialize_parameters();
1071 assert_eq!(params.len(), 16);
1072 }
1073
1074 #[test]
1075 fn test_adam_optimizer() {
1076 let mut optimizer = AdamOptimizer::new(0.01);
1077 let params = vec![1.0, 2.0, 3.0];
1078 let gradients = vec![0.1, -0.2, 0.05];
1079
1080 let updated = optimizer
1081 .update_parameters(params.clone(), gradients)
1082 .expect("Adam optimizer update should succeed");
1083 assert_eq!(updated.len(), 3);
1084
1085 assert_ne!(updated[0], params[0]);
1087 assert_ne!(updated[1], params[1]);
1088 assert_ne!(updated[2], params[2]);
1089 }
1090
1091 #[test]
1092 fn test_quantum_state() {
1093 let mut counts = HashMap::new();
1094 counts.insert("00".to_string(), 500);
1095 counts.insert("11".to_string(), 500);
1096
1097 let state = QuantumState::from_measurements(&counts, 2);
1098 let most_probable = state.get_most_probable_bitstring();
1099
1100 assert!(most_probable == "00" || most_probable == "11");
1102
1103 let prob_00 = state.get_probability("00");
1104 let prob_11 = state.get_probability("11");
1105
1106 assert!((prob_00 - 0.5).abs() < 0.01);
1107 assert!((prob_11 - 0.5).abs() < 0.01);
1108 }
1109}