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 crate::quantum_ml::circuit_simulation::simulate_and_sample(circuit, shots)
310 }
311}
312
313#[derive(Debug, Clone, Serialize, Deserialize)]
315pub struct VQEResult {
316 pub optimal_energy: f64,
317 pub optimal_parameters: Vec<f64>,
318 pub energy_history: Vec<f64>,
319 pub converged: bool,
320}
321
322pub trait VariationalAnsatz: Send + Sync {
324 fn initialize_parameters(&self) -> Vec<f64>;
325 fn build_circuit(&self, parameters: &[f64]) -> DeviceResult<ParameterizedQuantumCircuit>;
326 fn parameter_count(&self) -> usize;
327}
328
329pub struct HardwareEfficientAnsatz {
331 pub num_qubits: usize,
332 pub num_layers: usize,
333 pub entangling_gates: EntanglingGateType,
334}
335
336#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
337pub enum EntanglingGateType {
338 CNOT,
339 CZ,
340 ISwap,
341 Linear,
342 Circular,
343 AllToAll,
344}
345
346impl VariationalAnsatz for HardwareEfficientAnsatz {
347 fn initialize_parameters(&self) -> Vec<f64> {
348 let param_count = self.parameter_count();
349 (0..param_count)
350 .map(|_| fastrand::f64() * 2.0 * std::f64::consts::PI)
351 .collect()
352 }
353
354 fn build_circuit(&self, parameters: &[f64]) -> DeviceResult<ParameterizedQuantumCircuit> {
355 if parameters.len() != self.parameter_count() {
356 return Err(DeviceError::InvalidInput(format!(
357 "Expected {} parameters, got {}",
358 self.parameter_count(),
359 parameters.len()
360 )));
361 }
362
363 let mut circuit = ParameterizedQuantumCircuit::new(self.num_qubits);
364 let mut param_idx = 0;
365
366 for layer in 0..self.num_layers {
367 for qubit in 0..self.num_qubits {
369 circuit.add_ry_gate(qubit, parameters[param_idx])?;
370 param_idx += 1;
371 circuit.add_rz_gate(qubit, parameters[param_idx])?;
372 param_idx += 1;
373 }
374
375 match self.entangling_gates {
377 EntanglingGateType::Linear => {
378 for qubit in 0..self.num_qubits - 1 {
379 circuit.add_cnot_gate(qubit, qubit + 1)?;
380 }
381 }
382 EntanglingGateType::Circular => {
383 for qubit in 0..self.num_qubits - 1 {
384 circuit.add_cnot_gate(qubit, qubit + 1)?;
385 }
386 if self.num_qubits > 2 {
387 circuit.add_cnot_gate(self.num_qubits - 1, 0)?;
388 }
389 }
390 EntanglingGateType::AllToAll => {
391 for i in 0..self.num_qubits {
392 for j in i + 1..self.num_qubits {
393 circuit.add_cnot_gate(i, j)?;
394 }
395 }
396 }
397 _ => {
398 for qubit in 0..self.num_qubits - 1 {
400 circuit.add_cnot_gate(qubit, qubit + 1)?;
401 }
402 }
403 }
404 }
405
406 Ok(circuit)
407 }
408
409 fn parameter_count(&self) -> usize {
410 2 * self.num_qubits * self.num_layers }
412}
413
414pub struct QAOA {
416 device: Arc<RwLock<dyn QuantumDevice + Send + Sync>>,
417 problem: QAOAProblem,
418 num_layers: usize,
419 optimizer: Box<dyn VariationalOptimizer + Send + Sync>,
420 config: QAOAConfig,
421}
422
423#[derive(Debug, Clone, Serialize, Deserialize)]
425pub struct QAOAConfig {
426 pub max_iterations: usize,
427 pub shots_per_evaluation: usize,
428 pub parameter_bounds: Option<(f64, f64)>,
429 pub use_warm_start: bool,
430 pub adaptive_layers: bool,
431}
432
433impl Default for QAOAConfig {
434 fn default() -> Self {
435 Self {
436 max_iterations: 500,
437 shots_per_evaluation: 2048,
438 parameter_bounds: Some((0.0, 2.0 * std::f64::consts::PI)),
439 use_warm_start: true,
440 adaptive_layers: false,
441 }
442 }
443}
444
445#[derive(Debug, Clone, Serialize, Deserialize)]
447pub struct QAOAProblem {
448 pub cost_hamiltonian: Hamiltonian,
449 pub mixer_hamiltonian: Hamiltonian,
450 pub num_qubits: usize,
451}
452
453impl QAOA {
454 pub fn new(
455 device: Arc<RwLock<dyn QuantumDevice + Send + Sync>>,
456 problem: QAOAProblem,
457 num_layers: usize,
458 optimizer: Box<dyn VariationalOptimizer + Send + Sync>,
459 config: QAOAConfig,
460 ) -> Self {
461 Self {
462 device,
463 problem,
464 num_layers,
465 optimizer,
466 config,
467 }
468 }
469
470 pub async fn optimize(&mut self) -> DeviceResult<QAOAResult> {
472 let mut parameters = self.initialize_parameters();
473 let mut cost_history = Vec::new();
474 let mut best_cost = f64::INFINITY;
475 let mut best_parameters = parameters.clone();
476
477 for iteration in 0..self.config.max_iterations {
478 let cost = self.evaluate_cost(¶meters).await?;
479 cost_history.push(cost);
480
481 if cost < best_cost {
482 best_cost = cost;
483 best_parameters.clone_from(¶meters);
484 }
485
486 let gradients = self.compute_gradients(¶meters).await?;
488 parameters = self.optimizer.update_parameters(parameters, gradients)?;
489
490 if let Some((min_bound, max_bound)) = self.config.parameter_bounds {
492 for param in &mut parameters {
493 *param = param.clamp(min_bound, max_bound);
494 }
495 }
496 }
497
498 let final_state = self.prepare_qaoa_state(&best_parameters).await?;
500 let solution = self.extract_solution(&final_state).await?;
501
502 Ok(QAOAResult {
503 optimal_cost: best_cost,
504 optimal_parameters: best_parameters,
505 cost_history,
506 solution,
507 converged: true,
508 })
509 }
510
511 fn initialize_parameters(&self) -> Vec<f64> {
512 if self.config.use_warm_start {
513 let mut params = Vec::with_capacity(2 * self.num_layers);
515 for _ in 0..self.num_layers {
516 params.push(0.1); params.push(0.1); }
519 params
520 } else {
521 (0..2 * self.num_layers)
523 .map(|_| fastrand::f64() * std::f64::consts::PI)
524 .collect()
525 }
526 }
527
528 async fn evaluate_cost(&self, parameters: &[f64]) -> DeviceResult<f64> {
529 let circuit = self.build_qaoa_circuit(parameters)?;
530
531 let device = self.device.read().await;
533 let result =
534 Self::execute_circuit_helper(&*device, &circuit, self.config.shots_per_evaluation)
535 .await?;
536
537 self.compute_hamiltonian_expectation(&self.problem.cost_hamiltonian, &result)
538 }
539
540 fn build_qaoa_circuit(&self, parameters: &[f64]) -> DeviceResult<ParameterizedQuantumCircuit> {
541 let mut circuit = ParameterizedQuantumCircuit::new(self.problem.num_qubits);
542
543 for qubit in 0..self.problem.num_qubits {
545 circuit.add_h_gate(qubit)?;
546 }
547
548 for layer in 0..self.num_layers {
550 let gamma = parameters[2 * layer];
551 let beta = parameters[2 * layer + 1];
552
553 self.apply_hamiltonian_evolution(&mut circuit, &self.problem.cost_hamiltonian, gamma)?;
555
556 self.apply_hamiltonian_evolution(&mut circuit, &self.problem.mixer_hamiltonian, beta)?;
558 }
559
560 Ok(circuit)
561 }
562
563 fn apply_hamiltonian_evolution(
564 &self,
565 circuit: &mut ParameterizedQuantumCircuit,
566 hamiltonian: &Hamiltonian,
567 angle: f64,
568 ) -> DeviceResult<()> {
569 for term in &hamiltonian.terms {
570 let evolution_angle = term.coefficient * angle;
571
572 match term.paulis.len() {
574 1 => {
575 let (qubit, pauli_op) = &term.paulis[0];
576 match pauli_op {
577 PauliOperator::X => circuit.add_rx_gate(*qubit, evolution_angle)?,
578 PauliOperator::Y => circuit.add_ry_gate(*qubit, evolution_angle)?,
579 PauliOperator::Z => circuit.add_rz_gate(*qubit, evolution_angle)?,
580 PauliOperator::I => {} }
582 }
583 2 => {
584 let (q1, op1) = &term.paulis[0];
586 let (q2, op2) = &term.paulis[1];
587
588 if op1 == &PauliOperator::Z && op2 == &PauliOperator::Z {
591 circuit.add_cnot_gate(*q1, *q2)?;
592 circuit.add_rz_gate(*q2, evolution_angle)?;
593 circuit.add_cnot_gate(*q1, *q2)?;
594 }
595 }
596 _ => {
597 return Err(DeviceError::InvalidInput(
599 "Multi-qubit Pauli rotations not yet fully implemented".to_string(),
600 ));
601 }
602 }
603 }
604
605 Ok(())
606 }
607
608 async fn compute_gradients(&self, parameters: &[f64]) -> DeviceResult<Vec<f64>> {
609 let mut gradients = vec![0.0; parameters.len()];
610 let shift = std::f64::consts::PI / 2.0;
611
612 for (i, ¶m) in parameters.iter().enumerate() {
613 let mut params_plus = parameters.to_vec();
614 let mut params_minus = parameters.to_vec();
615
616 params_plus[i] = param + shift;
617 params_minus[i] = param - shift;
618
619 let cost_plus = self.evaluate_cost(¶ms_plus).await?;
620 let cost_minus = self.evaluate_cost(¶ms_minus).await?;
621
622 gradients[i] = (cost_plus - cost_minus) / 2.0;
623 }
624
625 Ok(gradients)
626 }
627
628 async fn prepare_qaoa_state(&self, parameters: &[f64]) -> DeviceResult<QuantumState> {
629 let circuit = self.build_qaoa_circuit(parameters)?;
630
631 let device = self.device.read().await;
633 let result = Self::execute_circuit_helper(&*device, &circuit, 10000).await?; Ok(QuantumState::from_measurements(
637 &result.counts,
638 self.problem.num_qubits,
639 ))
640 }
641
642 async fn extract_solution(&self, state: &QuantumState) -> DeviceResult<QAOASolution> {
643 let most_probable = state.get_most_probable_bitstring();
645 let probability = state.get_probability(&most_probable);
646
647 let cost = self.evaluate_classical_cost(&most_probable);
649
650 Ok(QAOASolution {
651 bitstring: most_probable,
652 probability,
653 cost,
654 all_amplitudes: state.get_all_amplitudes(),
655 })
656 }
657
658 fn evaluate_classical_cost(&self, bitstring: &str) -> f64 {
659 let mut cost = 0.0;
660
661 for term in &self.problem.cost_hamiltonian.terms {
662 let mut term_value = term.coefficient;
663
664 for (qubit_idx, pauli_op) in &term.paulis {
665 if let Some(bit_char) = bitstring.chars().nth(*qubit_idx) {
666 let bit_value = if bit_char == '1' { 1.0 } else { -1.0 };
667
668 match pauli_op {
669 PauliOperator::Z => term_value *= bit_value,
670 PauliOperator::I | _ => {
671 }
673 }
674 }
675 }
676
677 cost += term_value;
678 }
679
680 cost
681 }
682
683 fn compute_hamiltonian_expectation(
684 &self,
685 hamiltonian: &Hamiltonian,
686 result: &CircuitResult,
687 ) -> DeviceResult<f64> {
688 let mut expectation = 0.0;
689 let total_shots = result.shots as f64;
690
691 for (bitstring, count) in &result.counts {
692 let probability = *count as f64 / total_shots;
693 let energy = self.evaluate_classical_cost(bitstring);
694 expectation += probability * energy;
695 }
696
697 Ok(expectation)
698 }
699
700 async fn execute_circuit_helper(
702 _device: &(dyn QuantumDevice + Send + Sync),
703 circuit: &ParameterizedQuantumCircuit,
704 shots: usize,
705 ) -> DeviceResult<CircuitResult> {
706 crate::quantum_ml::circuit_simulation::simulate_and_sample(circuit, shots)
714 }
715}
716
717#[derive(Debug, Clone, Serialize, Deserialize)]
719pub struct QAOAResult {
720 pub optimal_cost: f64,
721 pub optimal_parameters: Vec<f64>,
722 pub cost_history: Vec<f64>,
723 pub solution: QAOASolution,
724 pub converged: bool,
725}
726
727#[derive(Debug, Clone, Serialize, Deserialize)]
729pub struct QAOASolution {
730 pub bitstring: String,
731 pub probability: f64,
732 pub cost: f64,
733 pub all_amplitudes: HashMap<String, f64>,
734}
735
736#[derive(Debug, Clone)]
738pub struct QuantumState {
739 amplitudes: HashMap<String, f64>,
740 num_qubits: usize,
741}
742
743impl QuantumState {
744 pub fn from_measurements(counts: &HashMap<String, usize>, num_qubits: usize) -> Self {
745 let total_shots: usize = counts.values().sum();
746 let mut amplitudes = HashMap::new();
747
748 for (bitstring, count) in counts {
749 let probability = *count as f64 / total_shots as f64;
750 amplitudes.insert(bitstring.clone(), probability.sqrt());
751 }
752
753 Self {
754 amplitudes,
755 num_qubits,
756 }
757 }
758
759 pub fn get_most_probable_bitstring(&self) -> String {
760 self.amplitudes
761 .iter()
762 .max_by(|a, b| {
763 (a.1 * a.1)
764 .partial_cmp(&(b.1 * b.1))
765 .unwrap_or(std::cmp::Ordering::Equal)
766 })
767 .map_or_else(
768 || "0".repeat(self.num_qubits),
769 |(bitstring, _)| bitstring.clone(),
770 )
771 }
772
773 pub fn get_probability(&self, bitstring: &str) -> f64 {
774 self.amplitudes.get(bitstring).map_or(0.0, |amp| amp * amp)
775 }
776
777 pub fn get_all_amplitudes(&self) -> HashMap<String, f64> {
778 self.amplitudes.clone()
779 }
780}
781
782pub trait VariationalOptimizer: Send + Sync {
784 fn update_parameters(
785 &mut self,
786 parameters: Vec<f64>,
787 gradients: Vec<f64>,
788 ) -> DeviceResult<Vec<f64>>;
789 fn reset(&mut self);
790}
791
792pub struct AdamOptimizer {
794 learning_rate: f64,
795 beta1: f64,
796 beta2: f64,
797 epsilon: f64,
798 m: Vec<f64>, v: Vec<f64>, t: usize, }
802
803impl AdamOptimizer {
804 pub const fn new(learning_rate: f64) -> Self {
805 Self {
806 learning_rate,
807 beta1: 0.9,
808 beta2: 0.999,
809 epsilon: 1e-8,
810 m: Vec::new(),
811 v: Vec::new(),
812 t: 0,
813 }
814 }
815}
816
817impl VariationalOptimizer for AdamOptimizer {
818 fn update_parameters(
819 &mut self,
820 parameters: Vec<f64>,
821 gradients: Vec<f64>,
822 ) -> DeviceResult<Vec<f64>> {
823 if self.m.is_empty() {
824 self.m = vec![0.0; parameters.len()];
825 self.v = vec![0.0; parameters.len()];
826 }
827
828 self.t += 1;
829 let mut updated_params = parameters;
830
831 for i in 0..updated_params.len() {
832 self.m[i] = self
834 .beta1
835 .mul_add(self.m[i], (1.0 - self.beta1) * gradients[i]);
836
837 self.v[i] = self
839 .beta2
840 .mul_add(self.v[i], (1.0 - self.beta2) * gradients[i] * gradients[i]);
841
842 let m_hat = self.m[i] / (1.0 - self.beta1.powi(self.t as i32));
844
845 let v_hat = self.v[i] / (1.0 - self.beta2.powi(self.t as i32));
847
848 updated_params[i] -= self.learning_rate * m_hat / (v_hat.sqrt() + self.epsilon);
850 }
851
852 Ok(updated_params)
853 }
854
855 fn reset(&mut self) {
856 self.m.clear();
857 self.v.clear();
858 self.t = 0;
859 }
860}
861
862#[derive(Debug, Clone)]
864pub struct ParameterizedQuantumCircuit {
865 num_qubits: usize,
866 gates: Vec<QuantumGate>,
867}
868
869#[derive(Debug, Clone)]
870pub enum QuantumGate {
871 H(usize),
872 X(usize),
873 Y(usize),
874 Z(usize),
875 RX(usize, f64),
876 RY(usize, f64),
877 RZ(usize, f64),
878 CNOT(usize, usize),
879 CZ(usize, usize),
880 SDagger(usize),
881}
882
883impl ParameterizedQuantumCircuit {
884 pub const fn new(num_qubits: usize) -> Self {
885 Self {
886 num_qubits,
887 gates: Vec::new(),
888 }
889 }
890
891 pub const fn num_qubits(&self) -> usize {
892 self.num_qubits
893 }
894
895 pub fn gates(&self) -> &[QuantumGate] {
901 &self.gates
902 }
903
904 pub fn with_parameters(&self, parameters: &[f64]) -> DeviceResult<Self> {
913 let needed = self
914 .gates
915 .iter()
916 .filter(|g| {
917 matches!(
918 g,
919 QuantumGate::RX(..) | QuantumGate::RY(..) | QuantumGate::RZ(..)
920 )
921 })
922 .count();
923 if parameters.len() < needed {
924 return Err(DeviceError::InvalidInput(format!(
925 "Circuit has {needed} parameterized gates but only {} parameters were provided",
926 parameters.len()
927 )));
928 }
929
930 let mut new_gates = Vec::with_capacity(self.gates.len());
931 let mut idx = 0;
932 for gate in &self.gates {
933 let replaced = match gate {
934 QuantumGate::RX(q, _) => {
935 let g = QuantumGate::RX(*q, parameters[idx]);
936 idx += 1;
937 g
938 }
939 QuantumGate::RY(q, _) => {
940 let g = QuantumGate::RY(*q, parameters[idx]);
941 idx += 1;
942 g
943 }
944 QuantumGate::RZ(q, _) => {
945 let g = QuantumGate::RZ(*q, parameters[idx]);
946 idx += 1;
947 g
948 }
949 other => other.clone(),
950 };
951 new_gates.push(replaced);
952 }
953
954 Ok(Self {
955 num_qubits: self.num_qubits,
956 gates: new_gates,
957 })
958 }
959
960 pub fn add_h_gate(&mut self, qubit: usize) -> DeviceResult<()> {
961 if qubit >= self.num_qubits {
962 return Err(DeviceError::InvalidInput(format!(
963 "Qubit {qubit} out of range"
964 )));
965 }
966 self.gates.push(QuantumGate::H(qubit));
967 Ok(())
968 }
969
970 pub fn add_rx_gate(&mut self, qubit: usize, angle: f64) -> DeviceResult<()> {
971 if qubit >= self.num_qubits {
972 return Err(DeviceError::InvalidInput(format!(
973 "Qubit {qubit} out of range"
974 )));
975 }
976 self.gates.push(QuantumGate::RX(qubit, angle));
977 Ok(())
978 }
979
980 pub fn add_ry_gate(&mut self, qubit: usize, angle: f64) -> DeviceResult<()> {
981 if qubit >= self.num_qubits {
982 return Err(DeviceError::InvalidInput(format!(
983 "Qubit {qubit} out of range"
984 )));
985 }
986 self.gates.push(QuantumGate::RY(qubit, angle));
987 Ok(())
988 }
989
990 pub fn add_rz_gate(&mut self, qubit: usize, angle: f64) -> DeviceResult<()> {
991 if qubit >= self.num_qubits {
992 return Err(DeviceError::InvalidInput(format!(
993 "Qubit {qubit} out of range"
994 )));
995 }
996 self.gates.push(QuantumGate::RZ(qubit, angle));
997 Ok(())
998 }
999
1000 pub fn add_cnot_gate(&mut self, control: usize, target: usize) -> DeviceResult<()> {
1001 if control >= self.num_qubits || target >= self.num_qubits {
1002 return Err(DeviceError::InvalidInput(
1003 "Qubit index out of range".to_string(),
1004 ));
1005 }
1006 self.gates.push(QuantumGate::CNOT(control, target));
1007 Ok(())
1008 }
1009
1010 pub fn add_s_dagger_gate(&mut self, qubit: usize) -> DeviceResult<()> {
1011 if qubit >= self.num_qubits {
1012 return Err(DeviceError::InvalidInput(format!(
1013 "Qubit {qubit} out of range"
1014 )));
1015 }
1016 self.gates.push(QuantumGate::SDagger(qubit));
1017 Ok(())
1018 }
1019
1020 pub fn add_x_gate(&mut self, qubit: usize) -> DeviceResult<()> {
1021 if qubit >= self.num_qubits {
1022 return Err(DeviceError::InvalidInput(format!(
1023 "Qubit {qubit} out of range"
1024 )));
1025 }
1026 self.gates.push(QuantumGate::X(qubit));
1027 Ok(())
1028 }
1029}
1030
1031pub fn create_molecular_vqe(
1033 device: Arc<RwLock<dyn QuantumDevice + Send + Sync>>,
1034 molecule: MolecularHamiltonian,
1035) -> DeviceResult<VQE> {
1036 let hamiltonian = molecule.to_hamiltonian();
1037 let ansatz = HardwareEfficientAnsatz {
1038 num_qubits: hamiltonian.num_qubits,
1039 num_layers: 3,
1040 entangling_gates: EntanglingGateType::Linear,
1041 };
1042
1043 let optimizer = Box::new(AdamOptimizer::new(0.01));
1044 let config = VQEConfig::default();
1045
1046 Ok(VQE::new(
1047 device,
1048 hamiltonian,
1049 Box::new(ansatz),
1050 optimizer,
1051 config,
1052 ))
1053}
1054
1055#[derive(Debug, Clone, Serialize, Deserialize)]
1057pub struct MolecularHamiltonian {
1058 pub one_body_integrals: Vec<Vec<f64>>,
1059 pub two_body_integrals: Vec<Vec<Vec<Vec<f64>>>>,
1060 pub nuclear_repulsion: f64,
1061 pub num_orbitals: usize,
1062}
1063
1064impl MolecularHamiltonian {
1065 pub fn to_hamiltonian(&self) -> Hamiltonian {
1066 let mut terms = Vec::new();
1067
1068 for i in 0..self.num_orbitals {
1070 for j in 0..self.num_orbitals {
1071 if self.one_body_integrals[i][j].abs() > 1e-12 {
1072 terms.push(PauliTerm {
1075 coefficient: self.one_body_integrals[i][j],
1076 paulis: vec![(i, PauliOperator::Z), (j, PauliOperator::Z)],
1077 });
1078 }
1079 }
1080 }
1081
1082 terms.push(PauliTerm {
1084 coefficient: self.nuclear_repulsion,
1085 paulis: vec![], });
1087
1088 Hamiltonian {
1089 terms,
1090 num_qubits: self.num_orbitals,
1091 }
1092 }
1093}
1094
1095#[cfg(test)]
1096mod tests {
1097 use super::*;
1098
1099 #[test]
1100 fn test_hamiltonian_creation() {
1101 let hamiltonian = Hamiltonian {
1102 terms: vec![
1103 PauliTerm {
1104 coefficient: 1.0,
1105 paulis: vec![(0, PauliOperator::Z)],
1106 },
1107 PauliTerm {
1108 coefficient: 0.5,
1109 paulis: vec![(0, PauliOperator::Z), (1, PauliOperator::Z)],
1110 },
1111 ],
1112 num_qubits: 2,
1113 };
1114
1115 assert_eq!(hamiltonian.terms.len(), 2);
1116 assert_eq!(hamiltonian.num_qubits, 2);
1117 }
1118
1119 #[test]
1120 fn test_hardware_efficient_ansatz() {
1121 let ansatz = HardwareEfficientAnsatz {
1122 num_qubits: 4,
1123 num_layers: 2,
1124 entangling_gates: EntanglingGateType::Linear,
1125 };
1126
1127 assert_eq!(ansatz.parameter_count(), 16); let params = ansatz.initialize_parameters();
1130 assert_eq!(params.len(), 16);
1131 }
1132
1133 #[test]
1134 fn test_adam_optimizer() {
1135 let mut optimizer = AdamOptimizer::new(0.01);
1136 let params = vec![1.0, 2.0, 3.0];
1137 let gradients = vec![0.1, -0.2, 0.05];
1138
1139 let updated = optimizer
1140 .update_parameters(params.clone(), gradients)
1141 .expect("Adam optimizer update should succeed");
1142 assert_eq!(updated.len(), 3);
1143
1144 assert_ne!(updated[0], params[0]);
1146 assert_ne!(updated[1], params[1]);
1147 assert_ne!(updated[2], params[2]);
1148 }
1149
1150 #[test]
1151 fn test_quantum_state() {
1152 let mut counts = HashMap::new();
1153 counts.insert("00".to_string(), 500);
1154 counts.insert("11".to_string(), 500);
1155
1156 let state = QuantumState::from_measurements(&counts, 2);
1157 let most_probable = state.get_most_probable_bitstring();
1158
1159 assert!(most_probable == "00" || most_probable == "11");
1161
1162 let prob_00 = state.get_probability("00");
1163 let prob_11 = state.get_probability("11");
1164
1165 assert!((prob_00 - 0.5).abs() < 0.01);
1166 assert!((prob_11 - 0.5).abs() < 0.01);
1167 }
1168}