1use crate::error::{Result, SimulatorError};
8use crate::pauli::{PauliOperatorSum, PauliString};
9use quantrs2_core::gate::GateOp;
10use scirs2_core::ndarray::{Array1, Array2};
11use scirs2_core::random::prelude::*;
12use scirs2_core::Complex64;
13use std::f64::consts::PI;
14
15#[cfg(feature = "optimize")]
16use crate::optirs_integration::{OptiRSConfig, OptiRSQuantumOptimizer};
17
18#[derive(Debug, Clone, Copy)]
20pub enum GradientMethod {
21 ParameterShift,
23 FiniteDifference { step_size: f64 },
25 SPSA { step_size: f64 },
27}
28
29#[derive(Debug, Clone)]
31pub struct AutoDiffContext {
32 pub parameters: Vec<f64>,
34 pub parameter_names: Vec<String>,
36 pub method: GradientMethod,
38 pub gradients: Vec<f64>,
40 pub grad_evaluations: usize,
42 pub func_evaluations: usize,
44}
45
46impl AutoDiffContext {
47 #[must_use]
49 pub fn new(parameters: Vec<f64>, method: GradientMethod) -> Self {
50 let num_params = parameters.len();
51 Self {
52 parameters,
53 parameter_names: (0..num_params).map(|i| format!("θ{i}")).collect(),
54 method,
55 gradients: vec![0.0; num_params],
56 grad_evaluations: 0,
57 func_evaluations: 0,
58 }
59 }
60
61 #[must_use]
63 pub fn with_parameter_names(mut self, names: Vec<String>) -> Self {
64 assert_eq!(names.len(), self.parameters.len());
65 self.parameter_names = names;
66 self
67 }
68
69 pub fn update_parameters(&mut self, new_params: Vec<f64>) {
71 assert_eq!(new_params.len(), self.parameters.len());
72 self.parameters = new_params;
73 }
74
75 #[must_use]
77 pub fn get_parameter(&self, name: &str) -> Option<f64> {
78 self.parameter_names
79 .iter()
80 .position(|n| n == name)
81 .map(|i| self.parameters[i])
82 }
83
84 pub fn set_parameter(&mut self, name: &str, value: f64) -> Result<()> {
86 if let Some(i) = self.parameter_names.iter().position(|n| n == name) {
87 self.parameters[i] = value;
88 Ok(())
89 } else {
90 Err(SimulatorError::InvalidInput(format!(
91 "Parameter '{name}' not found"
92 )))
93 }
94 }
95}
96
97pub trait ParametricGate: Send + Sync {
99 fn name(&self) -> &str;
101
102 fn qubits(&self) -> Vec<usize>;
104
105 fn parameter_indices(&self) -> Vec<usize>;
107
108 fn matrix(&self, params: &[f64]) -> Result<Array2<Complex64>>;
110
111 fn gradient(&self, params: &[f64], param_idx: usize) -> Result<Array2<Complex64>>;
113
114 fn parameter_shift_gradient(
116 &self,
117 params: &[f64],
118 param_idx: usize,
119 ) -> Result<(Array2<Complex64>, Array2<Complex64>)> {
120 let shift = PI / 2.0;
121 let mut params_plus = params.to_vec();
122 let mut params_minus = params.to_vec();
123
124 if param_idx < params.len() {
125 params_plus[param_idx] += shift;
126 params_minus[param_idx] -= shift;
127 }
128
129 let matrix_plus = self.matrix(¶ms_plus)?;
130 let matrix_minus = self.matrix(¶ms_minus)?;
131
132 Ok((matrix_plus, matrix_minus))
133 }
134}
135
136pub struct ParametricRX {
138 pub qubit: usize,
139 pub param_idx: usize,
140}
141
142impl ParametricGate for ParametricRX {
143 fn name(&self) -> &'static str {
144 "RX"
145 }
146
147 fn qubits(&self) -> Vec<usize> {
148 vec![self.qubit]
149 }
150
151 fn parameter_indices(&self) -> Vec<usize> {
152 vec![self.param_idx]
153 }
154
155 fn matrix(&self, params: &[f64]) -> Result<Array2<Complex64>> {
156 let theta = params[self.param_idx];
157 let cos_half = (theta / 2.0).cos();
158 let sin_half = (theta / 2.0).sin();
159
160 Ok(scirs2_core::ndarray::array![
161 [Complex64::new(cos_half, 0.), Complex64::new(0., -sin_half)],
162 [Complex64::new(0., -sin_half), Complex64::new(cos_half, 0.)]
163 ])
164 }
165
166 fn gradient(&self, params: &[f64], param_idx: usize) -> Result<Array2<Complex64>> {
167 if param_idx != self.param_idx {
168 return Ok(Array2::zeros((2, 2)));
169 }
170
171 let theta = params[self.param_idx];
172 let cos_half = (theta / 2.0).cos();
173 let sin_half = (theta / 2.0).sin();
174
175 Ok(scirs2_core::ndarray::array![
177 [
178 Complex64::new(-sin_half / 2.0, 0.),
179 Complex64::new(0., -cos_half / 2.0)
180 ],
181 [
182 Complex64::new(0., -cos_half / 2.0),
183 Complex64::new(-sin_half / 2.0, 0.)
184 ]
185 ])
186 }
187}
188
189pub struct ParametricRY {
190 pub qubit: usize,
191 pub param_idx: usize,
192}
193
194impl ParametricGate for ParametricRY {
195 fn name(&self) -> &'static str {
196 "RY"
197 }
198
199 fn qubits(&self) -> Vec<usize> {
200 vec![self.qubit]
201 }
202
203 fn parameter_indices(&self) -> Vec<usize> {
204 vec![self.param_idx]
205 }
206
207 fn matrix(&self, params: &[f64]) -> Result<Array2<Complex64>> {
208 let theta = params[self.param_idx];
209 let cos_half = (theta / 2.0).cos();
210 let sin_half = (theta / 2.0).sin();
211
212 Ok(scirs2_core::ndarray::array![
213 [Complex64::new(cos_half, 0.), Complex64::new(-sin_half, 0.)],
214 [Complex64::new(sin_half, 0.), Complex64::new(cos_half, 0.)]
215 ])
216 }
217
218 fn gradient(&self, params: &[f64], param_idx: usize) -> Result<Array2<Complex64>> {
219 if param_idx != self.param_idx {
220 return Ok(Array2::zeros((2, 2)));
221 }
222
223 let theta = params[self.param_idx];
224 let cos_half = (theta / 2.0).cos();
225 let sin_half = (theta / 2.0).sin();
226
227 Ok(scirs2_core::ndarray::array![
228 [
229 Complex64::new(-sin_half / 2.0, 0.),
230 Complex64::new(-cos_half / 2.0, 0.)
231 ],
232 [
233 Complex64::new(cos_half / 2.0, 0.),
234 Complex64::new(-sin_half / 2.0, 0.)
235 ]
236 ])
237 }
238}
239
240pub struct ParametricCNOT {
246 pub control: usize,
247 pub target: usize,
248}
249
250impl ParametricGate for ParametricCNOT {
251 fn name(&self) -> &'static str {
252 "CNOT"
253 }
254
255 fn qubits(&self) -> Vec<usize> {
256 vec![self.control, self.target]
257 }
258
259 fn parameter_indices(&self) -> Vec<usize> {
260 Vec::new()
261 }
262
263 fn matrix(&self, _params: &[f64]) -> Result<Array2<Complex64>> {
264 Ok(scirs2_core::ndarray::array![
267 [
268 Complex64::new(1., 0.),
269 Complex64::new(0., 0.),
270 Complex64::new(0., 0.),
271 Complex64::new(0., 0.)
272 ],
273 [
274 Complex64::new(0., 0.),
275 Complex64::new(1., 0.),
276 Complex64::new(0., 0.),
277 Complex64::new(0., 0.)
278 ],
279 [
280 Complex64::new(0., 0.),
281 Complex64::new(0., 0.),
282 Complex64::new(0., 0.),
283 Complex64::new(1., 0.)
284 ],
285 [
286 Complex64::new(0., 0.),
287 Complex64::new(0., 0.),
288 Complex64::new(1., 0.),
289 Complex64::new(0., 0.)
290 ]
291 ])
292 }
293
294 fn gradient(&self, _params: &[f64], _param_idx: usize) -> Result<Array2<Complex64>> {
295 Ok(Array2::zeros((4, 4)))
298 }
299}
300
301pub struct ParametricRZ {
302 pub qubit: usize,
303 pub param_idx: usize,
304}
305
306impl ParametricGate for ParametricRZ {
307 fn name(&self) -> &'static str {
308 "RZ"
309 }
310
311 fn qubits(&self) -> Vec<usize> {
312 vec![self.qubit]
313 }
314
315 fn parameter_indices(&self) -> Vec<usize> {
316 vec![self.param_idx]
317 }
318
319 fn matrix(&self, params: &[f64]) -> Result<Array2<Complex64>> {
320 let theta = params[self.param_idx];
321 let exp_pos = Complex64::from_polar(1.0, theta / 2.0);
322 let exp_neg = Complex64::from_polar(1.0, -theta / 2.0);
323
324 Ok(scirs2_core::ndarray::array![
325 [exp_neg, Complex64::new(0., 0.)],
326 [Complex64::new(0., 0.), exp_pos]
327 ])
328 }
329
330 fn gradient(&self, params: &[f64], param_idx: usize) -> Result<Array2<Complex64>> {
331 if param_idx != self.param_idx {
332 return Ok(Array2::zeros((2, 2)));
333 }
334
335 let theta = params[self.param_idx];
336 let exp_pos = Complex64::from_polar(1.0, theta / 2.0);
337 let exp_neg = Complex64::from_polar(1.0, -theta / 2.0);
338
339 Ok(scirs2_core::ndarray::array![
340 [exp_neg * Complex64::new(0., -0.5), Complex64::new(0., 0.)],
341 [Complex64::new(0., 0.), exp_pos * Complex64::new(0., 0.5)]
342 ])
343 }
344}
345
346pub struct ParametricCircuit {
348 pub gates: Vec<Box<dyn ParametricGate>>,
350 pub num_qubits: usize,
352 pub num_parameters: usize,
354}
355
356impl ParametricCircuit {
357 #[must_use]
359 pub fn new(num_qubits: usize) -> Self {
360 Self {
361 gates: Vec::new(),
362 num_qubits,
363 num_parameters: 0,
364 }
365 }
366
367 pub fn add_gate(&mut self, gate: Box<dyn ParametricGate>) {
369 for ¶m_idx in &gate.parameter_indices() {
371 self.num_parameters = self.num_parameters.max(param_idx + 1);
372 }
373 self.gates.push(gate);
374 }
375
376 pub fn rx(&mut self, qubit: usize, param_idx: usize) {
378 self.add_gate(Box::new(ParametricRX { qubit, param_idx }));
379 }
380
381 pub fn ry(&mut self, qubit: usize, param_idx: usize) {
383 self.add_gate(Box::new(ParametricRY { qubit, param_idx }));
384 }
385
386 pub fn rz(&mut self, qubit: usize, param_idx: usize) {
388 self.add_gate(Box::new(ParametricRZ { qubit, param_idx }));
389 }
390
391 pub fn cnot(&mut self, control: usize, target: usize) {
393 self.add_gate(Box::new(ParametricCNOT { control, target }));
394 }
395
396 pub fn evaluate(&self, params: &[f64]) -> Result<Array1<Complex64>> {
404 if params.len() != self.num_parameters {
405 return Err(SimulatorError::InvalidInput(format!(
406 "Expected {} parameters, got {}",
407 self.num_parameters,
408 params.len()
409 )));
410 }
411
412 let dim = 1usize << self.num_qubits;
413 let mut state = Array1::zeros(dim);
414 state[0] = Complex64::new(1.0, 0.0); for gate in &self.gates {
417 let matrix = gate.matrix(params)?;
418 let qubits = gate.qubits();
419
420 match qubits.as_slice() {
421 [target] => {
422 apply_single_qubit_matrix(&mut state, &matrix, *target, self.num_qubits)?;
423 }
424 [control, target] => {
425 apply_two_qubit_matrix(
426 &mut state,
427 &matrix,
428 *control,
429 *target,
430 self.num_qubits,
431 )?;
432 }
433 _ => {
434 return Err(SimulatorError::UnsupportedOperation(format!(
435 "Parametric circuit evaluation supports one- and two-qubit gates, but '{}' acts on {} qubits",
436 gate.name(),
437 qubits.len()
438 )));
439 }
440 }
441 }
442
443 Ok(state)
444 }
445
446 pub fn gradient_expectation(
448 &self,
449 observable: &PauliOperatorSum,
450 params: &[f64],
451 method: GradientMethod,
452 ) -> Result<Vec<f64>> {
453 match method {
454 GradientMethod::ParameterShift => self.parameter_shift_gradient(observable, params),
455 GradientMethod::FiniteDifference { step_size } => {
456 self.finite_difference_gradient(observable, params, step_size)
457 }
458 GradientMethod::SPSA { step_size } => self.spsa_gradient(observable, params, step_size),
459 }
460 }
461
462 fn parameter_shift_gradient(
464 &self,
465 observable: &PauliOperatorSum,
466 params: &[f64],
467 ) -> Result<Vec<f64>> {
468 let mut gradients = vec![0.0; self.num_parameters];
469
470 for param_idx in 0..self.num_parameters {
473 let shift = PI / 2.0;
474
475 let mut params_plus = params.to_vec();
477 params_plus[param_idx] += shift;
478 let state_plus = self.evaluate(¶ms_plus)?;
479 let expectation_plus = compute_expectation_value(&state_plus, observable)?;
480
481 let mut params_minus = params.to_vec();
483 params_minus[param_idx] -= shift;
484 let state_minus = self.evaluate(¶ms_minus)?;
485 let expectation_minus = compute_expectation_value(&state_minus, observable)?;
486
487 gradients[param_idx] = (expectation_plus - expectation_minus) / 2.0;
489 }
490
491 Ok(gradients)
492 }
493
494 fn finite_difference_gradient(
496 &self,
497 observable: &PauliOperatorSum,
498 params: &[f64],
499 step_size: f64,
500 ) -> Result<Vec<f64>> {
501 let mut gradients = vec![0.0; self.num_parameters];
502
503 for param_idx in 0..self.num_parameters {
504 let mut params_plus = params.to_vec();
506 params_plus[param_idx] += step_size;
507 let state_plus = self.evaluate(¶ms_plus)?;
508 let expectation_plus = compute_expectation_value(&state_plus, observable)?;
509
510 let state = self.evaluate(params)?;
512 let expectation = compute_expectation_value(&state, observable)?;
513
514 gradients[param_idx] = (expectation_plus - expectation) / step_size;
515 }
516
517 Ok(gradients)
518 }
519
520 fn spsa_gradient(
522 &self,
523 observable: &PauliOperatorSum,
524 params: &[f64],
525 step_size: f64,
526 ) -> Result<Vec<f64>> {
527 let mut rng = thread_rng();
528
529 let mut perturbation = vec![0.0; self.num_parameters];
531 for p in &mut perturbation {
532 *p = if rng.random::<bool>() { 1.0 } else { -1.0 };
533 }
534
535 let mut params_plus = params.to_vec();
537 let mut params_minus = params.to_vec();
538 for i in 0..self.num_parameters {
539 params_plus[i] += step_size * perturbation[i];
540 params_minus[i] -= step_size * perturbation[i];
541 }
542
543 let state_plus = self.evaluate(¶ms_plus)?;
544 let state_minus = self.evaluate(¶ms_minus)?;
545 let expectation_plus = compute_expectation_value(&state_plus, observable)?;
546 let expectation_minus = compute_expectation_value(&state_minus, observable)?;
547
548 let diff = (expectation_plus - expectation_minus) / (2.0 * step_size);
550 let gradients = perturbation.iter().map(|&p| diff / p).collect();
551
552 Ok(gradients)
553 }
554}
555
556pub struct VQEWithAutodiff {
558 pub ansatz: ParametricCircuit,
560 pub hamiltonian: PauliOperatorSum,
562 pub context: AutoDiffContext,
564 pub history: Vec<VQEIteration>,
566 pub convergence: ConvergenceCriteria,
568}
569
570#[derive(Clone)]
572pub struct VQEIteration {
573 pub iteration: usize,
575 pub parameters: Vec<f64>,
577 pub energy: f64,
579 pub gradient_norm: f64,
581 pub func_evals: usize,
583 pub grad_evals: usize,
585}
586
587pub struct ConvergenceCriteria {
589 pub max_iterations: usize,
591 pub energy_tolerance: f64,
593 pub gradient_tolerance: f64,
595 pub max_func_evals: usize,
597}
598
599impl Default for ConvergenceCriteria {
600 fn default() -> Self {
601 Self {
602 max_iterations: 1000,
603 energy_tolerance: 1e-6,
604 gradient_tolerance: 1e-6,
605 max_func_evals: 10_000,
606 }
607 }
608}
609
610impl VQEWithAutodiff {
611 #[must_use]
613 pub fn new(
614 ansatz: ParametricCircuit,
615 hamiltonian: PauliOperatorSum,
616 initial_params: Vec<f64>,
617 gradient_method: GradientMethod,
618 ) -> Self {
619 let context = AutoDiffContext::new(initial_params, gradient_method);
620 Self {
621 ansatz,
622 hamiltonian,
623 context,
624 history: Vec::new(),
625 convergence: ConvergenceCriteria::default(),
626 }
627 }
628
629 #[must_use]
631 pub const fn with_convergence(mut self, convergence: ConvergenceCriteria) -> Self {
632 self.convergence = convergence;
633 self
634 }
635
636 pub fn evaluate_energy(&mut self) -> Result<f64> {
638 let state = self.ansatz.evaluate(&self.context.parameters)?;
639 let energy = compute_expectation_value(&state, &self.hamiltonian)?;
640 self.context.func_evaluations += 1;
641 Ok(energy)
642 }
643
644 pub fn compute_gradient(&mut self) -> Result<Vec<f64>> {
646 let gradients = self.ansatz.gradient_expectation(
647 &self.hamiltonian,
648 &self.context.parameters,
649 self.context.method,
650 )?;
651 self.context.gradients.clone_from(&gradients);
652 self.context.grad_evaluations += 1;
653 Ok(gradients)
654 }
655
656 pub fn step(&mut self, learning_rate: f64) -> Result<VQEIteration> {
658 let energy = self.evaluate_energy()?;
659 let gradients = self.compute_gradient()?;
660
661 for (i, &grad) in gradients.iter().enumerate() {
663 self.context.parameters[i] -= learning_rate * grad;
664 }
665
666 let gradient_norm = gradients.iter().map(|g| g * g).sum::<f64>().sqrt();
667
668 let iteration = VQEIteration {
669 iteration: self.history.len(),
670 parameters: self.context.parameters.clone(),
671 energy,
672 gradient_norm,
673 func_evals: self.context.func_evaluations,
674 grad_evals: self.context.grad_evaluations,
675 };
676
677 self.history.push(iteration.clone());
678 Ok(iteration)
679 }
680
681 pub fn optimize(&mut self, learning_rate: f64) -> Result<VQEResult> {
683 while !self.is_converged()? {
684 let iteration = self.step(learning_rate)?;
685
686 if iteration.iteration >= self.convergence.max_iterations {
687 break;
688 }
689 if iteration.func_evals >= self.convergence.max_func_evals {
690 break;
691 }
692 }
693
694 let final_iteration = self.history.last().ok_or_else(|| {
695 SimulatorError::InvalidOperation("VQE optimization produced no iterations".to_string())
696 })?;
697 Ok(VQEResult {
698 optimal_parameters: final_iteration.parameters.clone(),
699 optimal_energy: final_iteration.energy,
700 iterations: self.history.len(),
701 converged: self.is_converged()?,
702 history: self.history.clone(),
703 })
704 }
705
706 fn is_converged(&self) -> Result<bool> {
708 if self.history.len() < 2 {
709 return Ok(false);
710 }
711
712 let current = &self.history[self.history.len() - 1];
713 let previous = &self.history[self.history.len() - 2];
714
715 let energy_converged =
716 (current.energy - previous.energy).abs() < self.convergence.energy_tolerance;
717 let gradient_converged = current.gradient_norm < self.convergence.gradient_tolerance;
718
719 Ok(energy_converged && gradient_converged)
720 }
721
722 #[cfg(feature = "optimize")]
748 pub fn optimize_with_optirs(&mut self, config: OptiRSConfig) -> Result<VQEResult> {
749 use std::time::Instant;
750
751 let start_time = Instant::now();
752 let mut optimizer = OptiRSQuantumOptimizer::new(config)?;
753
754 while !self.is_converged()? && !optimizer.has_converged() {
755 let energy = self.evaluate_energy()?;
757 let gradients = self.compute_gradient()?;
758
759 let new_params =
761 optimizer.optimize_step(&self.context.parameters, &gradients, energy)?;
762
763 self.context.parameters = new_params;
765
766 let gradient_norm = gradients.iter().map(|g| g * g).sum::<f64>().sqrt();
768 let iteration = VQEIteration {
769 iteration: self.history.len(),
770 parameters: self.context.parameters.clone(),
771 energy,
772 gradient_norm,
773 func_evals: self.context.func_evaluations,
774 grad_evals: self.context.grad_evaluations,
775 };
776 self.history.push(iteration);
777
778 if self.history.len() >= self.convergence.max_iterations {
780 break;
781 }
782 if self.context.func_evaluations >= self.convergence.max_func_evals {
783 break;
784 }
785 }
786
787 let _optimization_time = start_time.elapsed();
788 let final_iteration = self.history.last().ok_or_else(|| {
789 SimulatorError::InvalidOperation(
790 "VQE optimization with OptiRS produced no iterations".to_string(),
791 )
792 })?;
793
794 Ok(VQEResult {
795 optimal_parameters: final_iteration.parameters.clone(),
796 optimal_energy: final_iteration.energy,
797 iterations: self.history.len(),
798 converged: self.is_converged()?,
799 history: self.history.clone(),
800 })
801 }
802}
803
804pub struct VQEResult {
806 pub optimal_parameters: Vec<f64>,
808 pub optimal_energy: f64,
810 pub iterations: usize,
812 pub converged: bool,
814 pub history: Vec<VQEIteration>,
816}
817
818fn apply_single_qubit_matrix(
826 state: &mut Array1<Complex64>,
827 gate: &Array2<Complex64>,
828 qubit: usize,
829 num_qubits: usize,
830) -> Result<()> {
831 if qubit >= num_qubits {
832 return Err(SimulatorError::InvalidInput(format!(
833 "Gate targets qubit {qubit} but circuit has only {num_qubits} qubits"
834 )));
835 }
836 if gate.dim() != (2, 2) {
837 return Err(SimulatorError::InvalidInput(format!(
838 "Single-qubit gate matrix must be 2x2, got {:?}",
839 gate.dim()
840 )));
841 }
842
843 let stride = 1usize << qubit;
844 let dim = state.len();
845 let mut index = 0;
846 while index < dim {
847 if index & stride == 0 {
848 let i0 = index;
849 let i1 = index | stride;
850 let a0 = state[i0];
851 let a1 = state[i1];
852 state[i0] = gate[[0, 0]] * a0 + gate[[0, 1]] * a1;
853 state[i1] = gate[[1, 0]] * a0 + gate[[1, 1]] * a1;
854 }
855 index += 1;
856 }
857
858 Ok(())
859}
860
861fn apply_two_qubit_matrix(
868 state: &mut Array1<Complex64>,
869 gate: &Array2<Complex64>,
870 control: usize,
871 target: usize,
872 num_qubits: usize,
873) -> Result<()> {
874 if control >= num_qubits || target >= num_qubits {
875 return Err(SimulatorError::InvalidInput(format!(
876 "Two-qubit gate targets qubits {control} and {target} but circuit has only {num_qubits} qubits"
877 )));
878 }
879 if control == target {
880 return Err(SimulatorError::InvalidInput(
881 "Two-qubit gate requires two distinct qubits".to_string(),
882 ));
883 }
884 if gate.dim() != (4, 4) {
885 return Err(SimulatorError::InvalidInput(format!(
886 "Two-qubit gate matrix must be 4x4, got {:?}",
887 gate.dim()
888 )));
889 }
890
891 let control_mask = 1usize << control;
892 let target_mask = 1usize << target;
893 let dim = state.len();
894
895 for index in 0..dim {
896 if index & control_mask == 0 && index & target_mask == 0 {
898 let i00 = index;
899 let i01 = index | target_mask;
900 let i10 = index | control_mask;
901 let i11 = index | control_mask | target_mask;
902
903 let amplitudes = [state[i00], state[i01], state[i10], state[i11]];
904 let mut updated = [Complex64::new(0.0, 0.0); 4];
905 for (row, slot) in updated.iter_mut().enumerate() {
906 let mut acc = Complex64::new(0.0, 0.0);
907 for (col, &litude) in amplitudes.iter().enumerate() {
908 acc += gate[[row, col]] * amplitude;
909 }
910 *slot = acc;
911 }
912
913 state[i00] = updated[0];
914 state[i01] = updated[1];
915 state[i10] = updated[2];
916 state[i11] = updated[3];
917 }
918 }
919
920 Ok(())
921}
922
923fn compute_expectation_value(
925 state: &Array1<Complex64>,
926 observable: &PauliOperatorSum,
927) -> Result<f64> {
928 let mut expectation = 0.0;
929
930 for term in &observable.terms {
931 let pauli_expectation = compute_pauli_expectation_from_state(state, term)?;
933 expectation += term.coefficient.re * pauli_expectation.re;
934 }
935
936 Ok(expectation)
937}
938
939fn compute_pauli_expectation_from_state(
941 state: &Array1<Complex64>,
942 pauli_string: &PauliString,
943) -> Result<Complex64> {
944 let num_qubits = pauli_string.num_qubits;
945 let dim = 1 << num_qubits;
946 let mut result = Complex64::new(0.0, 0.0);
947
948 for (i, &litude) in state.iter().enumerate() {
949 if i >= dim {
950 break;
951 }
952
953 let mut coeff = Complex64::new(1.0, 0.0);
955 let mut target_state = i;
956
957 for (qubit, &pauli_op) in pauli_string.operators.iter().enumerate() {
958 let bit = (i >> qubit) & 1;
959 use crate::pauli::PauliOperator;
960
961 match pauli_op {
962 PauliOperator::I => {} PauliOperator::X => {
964 target_state ^= 1 << qubit;
966 }
967 PauliOperator::Y => {
968 target_state ^= 1 << qubit;
970 coeff *= if bit == 0 {
971 Complex64::new(0.0, 1.0)
972 } else {
973 Complex64::new(0.0, -1.0)
974 };
975 }
976 PauliOperator::Z => {
977 if bit == 1 {
979 coeff *= Complex64::new(-1.0, 0.0);
980 }
981 }
982 }
983 }
984
985 if target_state < dim {
986 result += amplitude.conj() * coeff * state[target_state];
987 }
988 }
989
990 Ok(result * pauli_string.coefficient)
991}
992
993pub mod ansatze {
995 use super::ParametricCircuit;
996
997 #[must_use]
1006 pub fn hardware_efficient(num_qubits: usize, num_layers: usize) -> ParametricCircuit {
1007 let mut circuit = ParametricCircuit::new(num_qubits);
1008 let mut param_idx = 0;
1009
1010 for _layer in 0..num_layers {
1011 for qubit in 0..num_qubits {
1013 circuit.ry(qubit, param_idx);
1014 param_idx += 1;
1015 circuit.rz(qubit, param_idx);
1016 param_idx += 1;
1017 }
1018
1019 for qubit in 0..num_qubits.saturating_sub(1) {
1025 circuit.cnot(qubit, qubit + 1);
1026 }
1027 }
1028
1029 circuit
1030 }
1031
1032 #[must_use]
1034 pub fn qaoa_maxcut(
1035 num_qubits: usize,
1036 num_layers: usize,
1037 edges: &[(usize, usize)],
1038 ) -> ParametricCircuit {
1039 let mut circuit = ParametricCircuit::new(num_qubits);
1040 let mut param_idx = 0;
1041
1042 for qubit in 0..num_qubits {
1044 circuit.ry(qubit, param_idx); }
1046
1047 for _layer in 0..num_layers {
1048 for &(i, j) in edges {
1050 circuit.rz(i, param_idx);
1053 circuit.rz(j, param_idx);
1054 param_idx += 1;
1055 }
1056
1057 for qubit in 0..num_qubits {
1059 circuit.rx(qubit, param_idx);
1060 param_idx += 1;
1061 }
1062 }
1063
1064 circuit
1065 }
1066}
1067
1068#[cfg(test)]
1069mod tests {
1070 use super::*;
1071
1072 #[test]
1073 fn test_parametric_rx_matrix() {
1074 let rx_gate = ParametricRX {
1075 qubit: 0,
1076 param_idx: 0,
1077 };
1078 let params = vec![PI / 2.0];
1079 let matrix = rx_gate
1080 .matrix(¶ms)
1081 .expect("RX gate matrix computation should succeed");
1082
1083 let expected_val = 1.0 / 2.0_f64.sqrt();
1085 assert!((matrix[[0, 0]].re - expected_val).abs() < 1e-10);
1086 assert!((matrix[[0, 1]].im + expected_val).abs() < 1e-10);
1087 }
1088
1089 #[test]
1090 fn test_autodiff_context() {
1091 let params = vec![1.0, 2.0, 3.0];
1092 let mut context = AutoDiffContext::new(params.clone(), GradientMethod::ParameterShift);
1093
1094 assert_eq!(context.parameters, params);
1095 assert_eq!(context.gradients.len(), 3);
1096
1097 context.update_parameters(vec![4.0, 5.0, 6.0]);
1098 assert_eq!(context.parameters, vec![4.0, 5.0, 6.0]);
1099 }
1100
1101 #[test]
1102 fn test_parametric_circuit_creation() {
1103 let mut circuit = ParametricCircuit::new(2);
1104 circuit.rx(0, 0);
1105 circuit.ry(1, 1);
1106
1107 assert_eq!(circuit.gates.len(), 2);
1108 assert_eq!(circuit.num_parameters, 2);
1109 }
1110
1111 #[test]
1112 fn test_hardware_efficient_ansatz() {
1113 let ansatz = ansatze::hardware_efficient(3, 2);
1114 assert_eq!(ansatz.num_qubits, 3);
1115 assert!(ansatz.num_parameters > 0);
1116 }
1117
1118 #[test]
1124 fn test_hardware_efficient_ansatz_includes_entangling_layer() {
1125 let num_qubits = 2;
1126 let num_layers = 1;
1127 let ansatz = ansatze::hardware_efficient(num_qubits, num_layers);
1128
1129 assert_eq!(ansatz.gates.len(), 5);
1131 let cnot_count = ansatz.gates.iter().filter(|g| g.name() == "CNOT").count();
1132 assert_eq!(
1133 cnot_count,
1134 num_qubits - 1,
1135 "hardware_efficient must add a linear CNOT entangling ladder"
1136 );
1137
1138 let params = vec![PI / 2.0, 0.0, 0.0, 0.0];
1145 let state = ansatz
1146 .evaluate(¶ms)
1147 .expect("ansatz evaluation should succeed");
1148
1149 let mut rho00 = Complex64::new(0.0, 0.0);
1151 let mut rho01 = Complex64::new(0.0, 0.0);
1152 let mut rho11 = Complex64::new(0.0, 0.0);
1153 for other_bit in 0..2usize {
1154 let idx0 = other_bit << 1; let idx1 = (1) | (other_bit << 1); rho00 += state[idx0] * state[idx0].conj();
1157 rho11 += state[idx1] * state[idx1].conj();
1158 rho01 += state[idx0] * state[idx1].conj();
1159 }
1160 let purity = (rho00 * rho00 + rho11 * rho11 + rho01 * rho01.conj() * 2.0).re;
1161 assert!(
1162 purity < 0.999,
1163 "expected an entangled (mixed reduced state) result, got purity {purity}"
1164 );
1165 }
1166
1167 struct FixedGate {
1170 matrix: Array2<Complex64>,
1171 wires: Vec<usize>,
1172 }
1173
1174 impl ParametricGate for FixedGate {
1175 fn name(&self) -> &str {
1176 "FIXED"
1177 }
1178
1179 fn qubits(&self) -> Vec<usize> {
1180 self.wires.clone()
1181 }
1182
1183 fn parameter_indices(&self) -> Vec<usize> {
1184 Vec::new()
1185 }
1186
1187 fn matrix(&self, _params: &[f64]) -> Result<Array2<Complex64>> {
1188 Ok(self.matrix.clone())
1189 }
1190
1191 fn gradient(&self, _params: &[f64], _param_idx: usize) -> Result<Array2<Complex64>> {
1192 Ok(Array2::zeros(self.matrix.dim()))
1193 }
1194 }
1195
1196 #[test]
1197 fn test_evaluate_hadamard_forward_state() {
1198 let inv_sqrt2 = 1.0 / 2.0_f64.sqrt();
1200 let h_matrix = scirs2_core::ndarray::array![
1201 [
1202 Complex64::new(inv_sqrt2, 0.0),
1203 Complex64::new(inv_sqrt2, 0.0)
1204 ],
1205 [
1206 Complex64::new(inv_sqrt2, 0.0),
1207 Complex64::new(-inv_sqrt2, 0.0)
1208 ]
1209 ];
1210
1211 let mut circuit = ParametricCircuit::new(1);
1212 circuit.add_gate(Box::new(FixedGate {
1213 matrix: h_matrix,
1214 wires: vec![0],
1215 }));
1216
1217 let state = circuit
1218 .evaluate(&[])
1219 .expect("forward evaluation should succeed");
1220
1221 assert!((state[0].re - inv_sqrt2).abs() < 1e-10, "amp(|0>) wrong");
1223 assert!((state[1].re - inv_sqrt2).abs() < 1e-10, "amp(|1>) wrong");
1224 assert!(
1225 state[1].norm() > 1e-3,
1226 "forward pass returned the |0> placeholder instead of the real state"
1227 );
1228 }
1229
1230 #[test]
1231 fn test_evaluate_rx_pi_forward_state() {
1232 let mut circuit = ParametricCircuit::new(1);
1234 circuit.rx(0, 0);
1235
1236 let state = circuit
1237 .evaluate(&[PI])
1238 .expect("forward evaluation should succeed");
1239
1240 assert!(state[0].norm() < 1e-10, "amp(|0>) should vanish for RX(pi)");
1241 assert!((state[1].im + 1.0).abs() < 1e-10, "amp(|1>) should be -i");
1242 }
1243
1244 #[test]
1245 fn test_evaluate_bell_forward_state() {
1246 let inv_sqrt2 = 1.0 / 2.0_f64.sqrt();
1248 let h_matrix = scirs2_core::ndarray::array![
1249 [
1250 Complex64::new(inv_sqrt2, 0.0),
1251 Complex64::new(inv_sqrt2, 0.0)
1252 ],
1253 [
1254 Complex64::new(inv_sqrt2, 0.0),
1255 Complex64::new(-inv_sqrt2, 0.0)
1256 ]
1257 ];
1258 let cnot = scirs2_core::ndarray::array![
1259 [
1260 Complex64::new(1.0, 0.0),
1261 Complex64::new(0.0, 0.0),
1262 Complex64::new(0.0, 0.0),
1263 Complex64::new(0.0, 0.0)
1264 ],
1265 [
1266 Complex64::new(0.0, 0.0),
1267 Complex64::new(1.0, 0.0),
1268 Complex64::new(0.0, 0.0),
1269 Complex64::new(0.0, 0.0)
1270 ],
1271 [
1272 Complex64::new(0.0, 0.0),
1273 Complex64::new(0.0, 0.0),
1274 Complex64::new(0.0, 0.0),
1275 Complex64::new(1.0, 0.0)
1276 ],
1277 [
1278 Complex64::new(0.0, 0.0),
1279 Complex64::new(0.0, 0.0),
1280 Complex64::new(1.0, 0.0),
1281 Complex64::new(0.0, 0.0)
1282 ]
1283 ];
1284
1285 let mut circuit = ParametricCircuit::new(2);
1286 circuit.add_gate(Box::new(FixedGate {
1287 matrix: h_matrix,
1288 wires: vec![0],
1289 }));
1290 circuit.add_gate(Box::new(FixedGate {
1291 matrix: cnot,
1292 wires: vec![0, 1],
1293 }));
1294
1295 let state = circuit
1296 .evaluate(&[])
1297 .expect("forward evaluation should succeed");
1298
1299 assert!((state[0].re - inv_sqrt2).abs() < 1e-10, "amp(|00>) wrong");
1302 assert!(state[1].norm() < 1e-10, "amp(|01>) should vanish");
1303 assert!(state[2].norm() < 1e-10, "amp(|10>) should vanish");
1304 assert!((state[3].re - inv_sqrt2).abs() < 1e-10, "amp(|11>) wrong");
1305 }
1306}