1use ndarray::{Array1, Array2};
8use num_complex::Complex64;
9use std::f64::consts::PI;
10
11use crate::error::{Result, SimulatorError};
12use crate::pauli::{PauliOperatorSum, PauliString};
13use crate::statevector::StateVectorSimulator;
14use quantrs2_core::gate::GateOp;
15
16#[derive(Debug, Clone, Copy)]
18pub enum GradientMethod {
19 ParameterShift,
21 FiniteDifference { step_size: f64 },
23 SPSA { step_size: f64 },
25}
26
27#[derive(Debug, Clone)]
29pub struct AutoDiffContext {
30 pub parameters: Vec<f64>,
32 pub parameter_names: Vec<String>,
34 pub method: GradientMethod,
36 pub gradients: Vec<f64>,
38 pub grad_evaluations: usize,
40 pub func_evaluations: usize,
42}
43
44impl AutoDiffContext {
45 pub fn new(parameters: Vec<f64>, method: GradientMethod) -> Self {
47 let num_params = parameters.len();
48 Self {
49 parameters,
50 parameter_names: (0..num_params).map(|i| format!("θ{}", i)).collect(),
51 method,
52 gradients: vec![0.0; num_params],
53 grad_evaluations: 0,
54 func_evaluations: 0,
55 }
56 }
57
58 pub fn with_parameter_names(mut self, names: Vec<String>) -> Self {
60 assert_eq!(names.len(), self.parameters.len());
61 self.parameter_names = names;
62 self
63 }
64
65 pub fn update_parameters(&mut self, new_params: Vec<f64>) {
67 assert_eq!(new_params.len(), self.parameters.len());
68 self.parameters = new_params;
69 }
70
71 pub fn get_parameter(&self, name: &str) -> Option<f64> {
73 self.parameter_names
74 .iter()
75 .position(|n| n == name)
76 .map(|i| self.parameters[i])
77 }
78
79 pub fn set_parameter(&mut self, name: &str, value: f64) -> Result<()> {
81 if let Some(i) = self.parameter_names.iter().position(|n| n == name) {
82 self.parameters[i] = value;
83 Ok(())
84 } else {
85 Err(SimulatorError::InvalidInput(format!(
86 "Parameter '{}' not found",
87 name
88 )))
89 }
90 }
91}
92
93pub trait ParametricGate: Send + Sync {
95 fn name(&self) -> &str;
97
98 fn qubits(&self) -> Vec<usize>;
100
101 fn parameter_indices(&self) -> Vec<usize>;
103
104 fn matrix(&self, params: &[f64]) -> Result<Array2<Complex64>>;
106
107 fn gradient(&self, params: &[f64], param_idx: usize) -> Result<Array2<Complex64>>;
109
110 fn parameter_shift_gradient(
112 &self,
113 params: &[f64],
114 param_idx: usize,
115 ) -> Result<(Array2<Complex64>, Array2<Complex64>)> {
116 let shift = PI / 2.0;
117 let mut params_plus = params.to_vec();
118 let mut params_minus = params.to_vec();
119
120 if param_idx < params.len() {
121 params_plus[param_idx] += shift;
122 params_minus[param_idx] -= shift;
123 }
124
125 let matrix_plus = self.matrix(¶ms_plus)?;
126 let matrix_minus = self.matrix(¶ms_minus)?;
127
128 Ok((matrix_plus, matrix_minus))
129 }
130}
131
132#[derive(Debug, Clone)]
134pub struct ParametricRX {
135 pub qubit: usize,
136 pub param_idx: usize,
137}
138
139impl ParametricGate for ParametricRX {
140 fn name(&self) -> &str {
141 "RX"
142 }
143
144 fn qubits(&self) -> Vec<usize> {
145 vec![self.qubit]
146 }
147
148 fn parameter_indices(&self) -> Vec<usize> {
149 vec![self.param_idx]
150 }
151
152 fn matrix(&self, params: &[f64]) -> Result<Array2<Complex64>> {
153 let theta = params[self.param_idx];
154 let cos_half = (theta / 2.0).cos();
155 let sin_half = (theta / 2.0).sin();
156
157 Ok(ndarray::array![
158 [Complex64::new(cos_half, 0.), Complex64::new(0., -sin_half)],
159 [Complex64::new(0., -sin_half), Complex64::new(cos_half, 0.)]
160 ])
161 }
162
163 fn gradient(&self, params: &[f64], param_idx: usize) -> Result<Array2<Complex64>> {
164 if param_idx != self.param_idx {
165 return Ok(Array2::zeros((2, 2)));
166 }
167
168 let theta = params[self.param_idx];
169 let cos_half = (theta / 2.0).cos();
170 let sin_half = (theta / 2.0).sin();
171
172 Ok(ndarray::array![
174 [
175 Complex64::new(-sin_half / 2.0, 0.),
176 Complex64::new(0., -cos_half / 2.0)
177 ],
178 [
179 Complex64::new(0., -cos_half / 2.0),
180 Complex64::new(-sin_half / 2.0, 0.)
181 ]
182 ])
183 }
184}
185
186#[derive(Debug, Clone)]
187pub struct ParametricRY {
188 pub qubit: usize,
189 pub param_idx: usize,
190}
191
192impl ParametricGate for ParametricRY {
193 fn name(&self) -> &str {
194 "RY"
195 }
196
197 fn qubits(&self) -> Vec<usize> {
198 vec![self.qubit]
199 }
200
201 fn parameter_indices(&self) -> Vec<usize> {
202 vec![self.param_idx]
203 }
204
205 fn matrix(&self, params: &[f64]) -> Result<Array2<Complex64>> {
206 let theta = params[self.param_idx];
207 let cos_half = (theta / 2.0).cos();
208 let sin_half = (theta / 2.0).sin();
209
210 Ok(ndarray::array![
211 [Complex64::new(cos_half, 0.), Complex64::new(-sin_half, 0.)],
212 [Complex64::new(sin_half, 0.), Complex64::new(cos_half, 0.)]
213 ])
214 }
215
216 fn gradient(&self, params: &[f64], param_idx: usize) -> Result<Array2<Complex64>> {
217 if param_idx != self.param_idx {
218 return Ok(Array2::zeros((2, 2)));
219 }
220
221 let theta = params[self.param_idx];
222 let cos_half = (theta / 2.0).cos();
223 let sin_half = (theta / 2.0).sin();
224
225 Ok(ndarray::array![
226 [
227 Complex64::new(-sin_half / 2.0, 0.),
228 Complex64::new(-cos_half / 2.0, 0.)
229 ],
230 [
231 Complex64::new(cos_half / 2.0, 0.),
232 Complex64::new(-sin_half / 2.0, 0.)
233 ]
234 ])
235 }
236}
237
238#[derive(Debug, Clone)]
239pub struct ParametricRZ {
240 pub qubit: usize,
241 pub param_idx: usize,
242}
243
244impl ParametricGate for ParametricRZ {
245 fn name(&self) -> &str {
246 "RZ"
247 }
248
249 fn qubits(&self) -> Vec<usize> {
250 vec![self.qubit]
251 }
252
253 fn parameter_indices(&self) -> Vec<usize> {
254 vec![self.param_idx]
255 }
256
257 fn matrix(&self, params: &[f64]) -> Result<Array2<Complex64>> {
258 let theta = params[self.param_idx];
259 let exp_pos = Complex64::from_polar(1.0, theta / 2.0);
260 let exp_neg = Complex64::from_polar(1.0, -theta / 2.0);
261
262 Ok(ndarray::array![
263 [exp_neg, Complex64::new(0., 0.)],
264 [Complex64::new(0., 0.), exp_pos]
265 ])
266 }
267
268 fn gradient(&self, params: &[f64], param_idx: usize) -> Result<Array2<Complex64>> {
269 if param_idx != self.param_idx {
270 return Ok(Array2::zeros((2, 2)));
271 }
272
273 let theta = params[self.param_idx];
274 let exp_pos = Complex64::from_polar(1.0, theta / 2.0);
275 let exp_neg = Complex64::from_polar(1.0, -theta / 2.0);
276
277 Ok(ndarray::array![
278 [exp_neg * Complex64::new(0., -0.5), Complex64::new(0., 0.)],
279 [Complex64::new(0., 0.), exp_pos * Complex64::new(0., 0.5)]
280 ])
281 }
282}
283
284pub struct ParametricCircuit {
286 pub gates: Vec<Box<dyn ParametricGate>>,
288 pub num_qubits: usize,
290 pub num_parameters: usize,
292}
293
294impl ParametricCircuit {
295 pub fn new(num_qubits: usize) -> Self {
297 Self {
298 gates: Vec::new(),
299 num_qubits,
300 num_parameters: 0,
301 }
302 }
303
304 pub fn add_gate(&mut self, gate: Box<dyn ParametricGate>) {
306 for ¶m_idx in &gate.parameter_indices() {
308 self.num_parameters = self.num_parameters.max(param_idx + 1);
309 }
310 self.gates.push(gate);
311 }
312
313 pub fn rx(&mut self, qubit: usize, param_idx: usize) {
315 self.add_gate(Box::new(ParametricRX { qubit, param_idx }));
316 }
317
318 pub fn ry(&mut self, qubit: usize, param_idx: usize) {
320 self.add_gate(Box::new(ParametricRY { qubit, param_idx }));
321 }
322
323 pub fn rz(&mut self, qubit: usize, param_idx: usize) {
325 self.add_gate(Box::new(ParametricRZ { qubit, param_idx }));
326 }
327
328 pub fn evaluate(&self, params: &[f64]) -> Result<Array1<Complex64>> {
330 if params.len() != self.num_parameters {
331 return Err(SimulatorError::InvalidInput(format!(
332 "Expected {} parameters, got {}",
333 self.num_parameters,
334 params.len()
335 )));
336 }
337
338 let mut simulator = StateVectorSimulator::new();
340
341 for gate in &self.gates {
343 let matrix = gate.matrix(params)?;
344 let qubits = gate.qubits();
345
346 if qubits.len() == 1 {
347 } else if qubits.len() == 2 {
350 }
353 }
354
355 let mut state = Array1::zeros(1 << self.num_qubits);
357 state[0] = Complex64::new(1.0, 0.0); Ok(state)
359 }
360
361 pub fn gradient_expectation(
363 &self,
364 observable: &PauliOperatorSum,
365 params: &[f64],
366 method: GradientMethod,
367 ) -> Result<Vec<f64>> {
368 match method {
369 GradientMethod::ParameterShift => self.parameter_shift_gradient(observable, params),
370 GradientMethod::FiniteDifference { step_size } => {
371 self.finite_difference_gradient(observable, params, step_size)
372 }
373 GradientMethod::SPSA { step_size } => self.spsa_gradient(observable, params, step_size),
374 }
375 }
376
377 fn parameter_shift_gradient(
379 &self,
380 observable: &PauliOperatorSum,
381 params: &[f64],
382 ) -> Result<Vec<f64>> {
383 let mut gradients = vec![0.0; self.num_parameters];
384
385 for param_idx in 0..self.num_parameters {
389 let shift = PI / 2.0;
390
391 let mut params_plus = params.to_vec();
393 params_plus[param_idx] += shift;
394 let state_plus = self.evaluate(¶ms_plus)?;
395 let expectation_plus = compute_expectation_value(&state_plus, observable)?;
396
397 let mut params_minus = params.to_vec();
399 params_minus[param_idx] -= shift;
400 let state_minus = self.evaluate(¶ms_minus)?;
401 let expectation_minus = compute_expectation_value(&state_minus, observable)?;
402
403 gradients[param_idx] = (expectation_plus - expectation_minus) / 2.0;
405 }
406
407 Ok(gradients)
408 }
409
410 fn finite_difference_gradient(
412 &self,
413 observable: &PauliOperatorSum,
414 params: &[f64],
415 step_size: f64,
416 ) -> Result<Vec<f64>> {
417 let mut gradients = vec![0.0; self.num_parameters];
418
419 for param_idx in 0..self.num_parameters {
420 let mut params_plus = params.to_vec();
422 params_plus[param_idx] += step_size;
423 let state_plus = self.evaluate(¶ms_plus)?;
424 let expectation_plus = compute_expectation_value(&state_plus, observable)?;
425
426 let state = self.evaluate(params)?;
428 let expectation = compute_expectation_value(&state, observable)?;
429
430 gradients[param_idx] = (expectation_plus - expectation) / step_size;
432 }
433
434 Ok(gradients)
435 }
436
437 fn spsa_gradient(
439 &self,
440 observable: &PauliOperatorSum,
441 params: &[f64],
442 step_size: f64,
443 ) -> Result<Vec<f64>> {
444 let mut perturbation = vec![0.0; self.num_parameters];
446 for p in &mut perturbation {
447 *p = if fastrand::bool() { 1.0 } else { -1.0 };
448 }
449
450 let mut params_plus = params.to_vec();
452 let mut params_minus = params.to_vec();
453
454 for i in 0..self.num_parameters {
455 params_plus[i] += step_size * perturbation[i];
456 params_minus[i] -= step_size * perturbation[i];
457 }
458
459 let state_plus = self.evaluate(¶ms_plus)?;
460 let state_minus = self.evaluate(¶ms_minus)?;
461
462 let expectation_plus = compute_expectation_value(&state_plus, observable)?;
463 let expectation_minus = compute_expectation_value(&state_minus, observable)?;
464
465 let diff = (expectation_plus - expectation_minus) / (2.0 * step_size);
467 let gradients = perturbation.iter().map(|&p| diff / p).collect();
468
469 Ok(gradients)
470 }
471}
472
473pub struct VQEWithAutodiff {
475 pub ansatz: ParametricCircuit,
477 pub hamiltonian: PauliOperatorSum,
479 pub context: AutoDiffContext,
481 pub history: Vec<VQEIteration>,
483 pub convergence: ConvergenceCriteria,
485}
486
487#[derive(Debug, Clone)]
489pub struct VQEIteration {
490 pub iteration: usize,
492 pub parameters: Vec<f64>,
494 pub energy: f64,
496 pub gradient_norm: f64,
498 pub func_evals: usize,
500 pub grad_evals: usize,
502}
503
504#[derive(Debug, Clone)]
506pub struct ConvergenceCriteria {
507 pub max_iterations: usize,
509 pub energy_tolerance: f64,
511 pub gradient_tolerance: f64,
513 pub max_func_evals: usize,
515}
516
517impl Default for ConvergenceCriteria {
518 fn default() -> Self {
519 Self {
520 max_iterations: 1000,
521 energy_tolerance: 1e-6,
522 gradient_tolerance: 1e-6,
523 max_func_evals: 10000,
524 }
525 }
526}
527
528impl VQEWithAutodiff {
529 pub fn new(
531 ansatz: ParametricCircuit,
532 hamiltonian: PauliOperatorSum,
533 initial_params: Vec<f64>,
534 gradient_method: GradientMethod,
535 ) -> Self {
536 let context = AutoDiffContext::new(initial_params, gradient_method);
537
538 Self {
539 ansatz,
540 hamiltonian,
541 context,
542 history: Vec::new(),
543 convergence: ConvergenceCriteria::default(),
544 }
545 }
546
547 pub fn with_convergence(mut self, convergence: ConvergenceCriteria) -> Self {
549 self.convergence = convergence;
550 self
551 }
552
553 pub fn evaluate_energy(&mut self) -> Result<f64> {
555 let state = self.ansatz.evaluate(&self.context.parameters)?;
556 let energy = compute_expectation_value(&state, &self.hamiltonian)?;
557 self.context.func_evaluations += 1;
558 Ok(energy)
559 }
560
561 pub fn compute_gradient(&mut self) -> Result<Vec<f64>> {
563 let gradients = self.ansatz.gradient_expectation(
564 &self.hamiltonian,
565 &self.context.parameters,
566 self.context.method,
567 )?;
568 self.context.gradients = gradients.clone();
569 self.context.grad_evaluations += 1;
570 Ok(gradients)
571 }
572
573 pub fn step(&mut self, learning_rate: f64) -> Result<VQEIteration> {
575 let energy = self.evaluate_energy()?;
576 let gradients = self.compute_gradient()?;
577
578 for (i, &grad) in gradients.iter().enumerate() {
580 self.context.parameters[i] -= learning_rate * grad;
581 }
582
583 let gradient_norm = gradients.iter().map(|g| g * g).sum::<f64>().sqrt();
584
585 let iteration = VQEIteration {
586 iteration: self.history.len(),
587 parameters: self.context.parameters.clone(),
588 energy,
589 gradient_norm,
590 func_evals: self.context.func_evaluations,
591 grad_evals: self.context.grad_evaluations,
592 };
593
594 self.history.push(iteration.clone());
595 Ok(iteration)
596 }
597
598 pub fn optimize(&mut self, learning_rate: f64) -> Result<VQEResult> {
600 while !self.is_converged()? {
601 let iteration = self.step(learning_rate)?;
602
603 if iteration.iteration >= self.convergence.max_iterations {
604 break;
605 }
606
607 if iteration.func_evals >= self.convergence.max_func_evals {
608 break;
609 }
610 }
611
612 let final_iteration = self.history.last().unwrap();
613
614 Ok(VQEResult {
615 optimal_parameters: final_iteration.parameters.clone(),
616 optimal_energy: final_iteration.energy,
617 iterations: self.history.len(),
618 converged: self.is_converged()?,
619 history: self.history.clone(),
620 })
621 }
622
623 fn is_converged(&self) -> Result<bool> {
625 if self.history.len() < 2 {
626 return Ok(false);
627 }
628
629 let current = &self.history[self.history.len() - 1];
630 let previous = &self.history[self.history.len() - 2];
631
632 let energy_converged =
633 (current.energy - previous.energy).abs() < self.convergence.energy_tolerance;
634 let gradient_converged = current.gradient_norm < self.convergence.gradient_tolerance;
635
636 Ok(energy_converged && gradient_converged)
637 }
638}
639
640#[derive(Debug, Clone)]
642pub struct VQEResult {
643 pub optimal_parameters: Vec<f64>,
645 pub optimal_energy: f64,
647 pub iterations: usize,
649 pub converged: bool,
651 pub history: Vec<VQEIteration>,
653}
654
655fn compute_expectation_value(
659 state: &Array1<Complex64>,
660 observable: &PauliOperatorSum,
661) -> Result<f64> {
662 let mut expectation = 0.0;
663
664 for term in &observable.terms {
665 let pauli_expectation = compute_pauli_expectation_from_state(state, term)?;
667 expectation += term.coefficient.re * pauli_expectation.re;
668 }
669
670 Ok(expectation)
671}
672
673fn compute_pauli_expectation_from_state(
675 state: &Array1<Complex64>,
676 pauli_string: &PauliString,
677) -> Result<Complex64> {
678 let num_qubits = pauli_string.num_qubits;
679 let dim = 1 << num_qubits;
680 let mut result = Complex64::new(0.0, 0.0);
681
682 for (i, &litude) in state.iter().enumerate() {
683 if i >= dim {
684 break;
685 }
686
687 let mut coeff = Complex64::new(1.0, 0.0);
689 let mut target_state = i;
690
691 for (qubit, &pauli_op) in pauli_string.operators.iter().enumerate() {
692 let bit = (i >> qubit) & 1;
693
694 use crate::pauli::PauliOperator;
695 match pauli_op {
696 PauliOperator::I => {} PauliOperator::X => {
698 target_state ^= 1 << qubit;
700 }
701 PauliOperator::Y => {
702 target_state ^= 1 << qubit;
704 coeff *= if bit == 0 {
705 Complex64::new(0.0, 1.0)
706 } else {
707 Complex64::new(0.0, -1.0)
708 };
709 }
710 PauliOperator::Z => {
711 if bit == 1 {
713 coeff *= Complex64::new(-1.0, 0.0);
714 }
715 }
716 }
717 }
718
719 if target_state < dim {
720 result += amplitude.conj() * coeff * state[target_state];
721 }
722 }
723
724 Ok(result * pauli_string.coefficient)
725}
726
727pub mod ansatze {
729 use super::*;
730
731 pub fn hardware_efficient(num_qubits: usize, num_layers: usize) -> ParametricCircuit {
733 let mut circuit = ParametricCircuit::new(num_qubits);
734 let mut param_idx = 0;
735
736 for layer in 0..num_layers {
737 for qubit in 0..num_qubits {
739 let _ = circuit.ry(qubit, param_idx);
740 param_idx += 1;
741 let _ = circuit.rz(qubit, param_idx);
742 param_idx += 1;
743 }
744
745 }
748
749 circuit
750 }
751
752 pub fn qaoa_maxcut(
754 num_qubits: usize,
755 num_layers: usize,
756 edges: &[(usize, usize)],
757 ) -> ParametricCircuit {
758 let mut circuit = ParametricCircuit::new(num_qubits);
759 let mut param_idx = 0;
760
761 for qubit in 0..num_qubits {
763 let _ = circuit.ry(qubit, param_idx); }
765
766 for _layer in 0..num_layers {
767 for &(i, j) in edges {
769 let _ = circuit.rz(i, param_idx);
772 let _ = circuit.rz(j, param_idx);
773 param_idx += 1;
774 }
775
776 for qubit in 0..num_qubits {
778 let _ = circuit.rx(qubit, param_idx);
779 param_idx += 1;
780 }
781 }
782
783 circuit
784 }
785}
786
787#[cfg(test)]
788mod tests {
789 use super::*;
790
791 #[test]
792 fn test_parametric_rx_matrix() {
793 let rx_gate = ParametricRX {
794 qubit: 0,
795 param_idx: 0,
796 };
797 let params = vec![PI / 2.0];
798
799 let matrix = rx_gate.matrix(¶ms).unwrap();
800
801 let expected_val = 1.0 / 2.0_f64.sqrt();
803 assert!((matrix[[0, 0]].re - expected_val).abs() < 1e-10);
804 assert!((matrix[[0, 1]].im + expected_val).abs() < 1e-10);
805 }
806
807 #[test]
808 fn test_autodiff_context() {
809 let params = vec![1.0, 2.0, 3.0];
810 let mut context = AutoDiffContext::new(params.clone(), GradientMethod::ParameterShift);
811
812 assert_eq!(context.parameters, params);
813 assert_eq!(context.gradients.len(), 3);
814
815 context.update_parameters(vec![4.0, 5.0, 6.0]);
816 assert_eq!(context.parameters, vec![4.0, 5.0, 6.0]);
817 }
818
819 #[test]
820 fn test_parametric_circuit_creation() {
821 let mut circuit = ParametricCircuit::new(2);
822 let _ = circuit.rx(0, 0);
823 let _ = circuit.ry(1, 1);
824
825 assert_eq!(circuit.gates.len(), 2);
826 assert_eq!(circuit.num_parameters, 2);
827 }
828
829 #[test]
830 fn test_hardware_efficient_ansatz() {
831 let ansatz = ansatze::hardware_efficient(3, 2);
832 assert_eq!(ansatz.num_qubits, 3);
833 assert!(ansatz.num_parameters > 0);
834 }
835}