1use crate::builder::Circuit;
7use quantrs2_core::{
8 error::{QuantRS2Error, QuantRS2Result},
9 gate::single::{RotationX, RotationY, RotationZ},
10 gate::GateOp,
11 qubit::QubitId,
12};
13use scirs2_core::Complex64;
14use std::collections::HashMap;
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18pub enum RotationAxis {
19 Y,
20 Z,
21 X,
22}
23
24#[derive(Debug, Clone)]
27pub struct ParameterizedGateRecord {
28 pub gate_index: usize,
30 pub qubit: QubitId,
32 pub axis: RotationAxis,
34 pub param_index: usize,
36}
37
38#[derive(Debug, Clone)]
45pub struct VQECircuit<const N: usize> {
46 pub circuit: Circuit<N>,
48 pub parameters: Vec<f64>,
50 pub parameter_names: Vec<String>,
52 parameter_map: HashMap<String, usize>,
54 param_gate_records: Vec<ParameterizedGateRecord>,
57}
58
59#[derive(Debug, Clone, PartialEq)]
61pub enum VQEAnsatz {
62 HardwareEfficient { layers: usize },
64 UCCSD {
66 occupied_orbitals: usize,
67 virtual_orbitals: usize,
68 },
69 RealSpace { geometry: Vec<(f64, f64, f64)> },
71 Custom,
73}
74
75#[derive(Debug, Clone)]
77pub struct VQEObservable {
78 pub terms: Vec<(f64, Vec<(usize, PauliOperator)>)>,
80}
81
82#[derive(Debug, Clone, Copy, PartialEq, Eq)]
84pub enum PauliOperator {
85 I, X, Y, Z, }
90
91#[derive(Debug, Clone)]
93pub struct VQEResult {
94 pub optimal_parameters: Vec<f64>,
96 pub ground_state_energy: f64,
98 pub iterations: usize,
100 pub converged: bool,
102 pub gradient_norm: f64,
104}
105
106impl<const N: usize> VQECircuit<N> {
107 pub fn new(ansatz: VQEAnsatz) -> QuantRS2Result<Self> {
109 let mut circuit = Circuit::new();
110 let mut parameters = Vec::new();
111 let mut parameter_names = Vec::new();
112 let mut parameter_map = HashMap::new();
113 let mut param_gate_records: Vec<ParameterizedGateRecord> = Vec::new();
114
115 match ansatz {
116 VQEAnsatz::HardwareEfficient { layers } => {
117 Self::build_hardware_efficient_ansatz(
118 &mut circuit,
119 &mut parameters,
120 &mut parameter_names,
121 &mut parameter_map,
122 &mut param_gate_records,
123 layers,
124 )?;
125 }
126 VQEAnsatz::UCCSD {
127 occupied_orbitals,
128 virtual_orbitals,
129 } => {
130 Self::build_uccsd_ansatz(
131 &mut circuit,
132 &mut parameters,
133 &mut parameter_names,
134 &mut parameter_map,
135 &mut param_gate_records,
136 occupied_orbitals,
137 virtual_orbitals,
138 )?;
139 }
140 VQEAnsatz::RealSpace { geometry } => {
141 Self::build_real_space_ansatz(
142 &mut circuit,
143 &mut parameters,
144 &mut parameter_names,
145 &mut parameter_map,
146 &mut param_gate_records,
147 &geometry,
148 )?;
149 }
150 VQEAnsatz::Custom => {
151 }
153 }
154
155 Ok(Self {
156 circuit,
157 parameters,
158 parameter_names,
159 parameter_map,
160 param_gate_records,
161 })
162 }
163
164 fn build_hardware_efficient_ansatz(
166 circuit: &mut Circuit<N>,
167 parameters: &mut Vec<f64>,
168 parameter_names: &mut Vec<String>,
169 parameter_map: &mut HashMap<String, usize>,
170 param_gate_records: &mut Vec<ParameterizedGateRecord>,
171 layers: usize,
172 ) -> QuantRS2Result<()> {
173 for layer in 0..layers {
174 for qubit in 0..N {
176 let param_name = format!("ry_{layer}_q{qubit}");
178 let param_idx = parameters.len();
179 parameter_names.push(param_name.clone());
180 parameter_map.insert(param_name, param_idx);
181 parameters.push(0.0);
182
183 let gate_idx = circuit.gates().len();
184 circuit.ry(QubitId(qubit as u32), 0.0)?;
185 param_gate_records.push(ParameterizedGateRecord {
186 gate_index: gate_idx,
187 qubit: QubitId(qubit as u32),
188 axis: RotationAxis::Y,
189 param_index: param_idx,
190 });
191
192 let param_name = format!("rz_{layer}_q{qubit}");
194 let param_idx = parameters.len();
195 parameter_names.push(param_name.clone());
196 parameter_map.insert(param_name, param_idx);
197 parameters.push(0.0);
198
199 let gate_idx = circuit.gates().len();
200 circuit.rz(QubitId(qubit as u32), 0.0)?;
201 param_gate_records.push(ParameterizedGateRecord {
202 gate_index: gate_idx,
203 qubit: QubitId(qubit as u32),
204 axis: RotationAxis::Z,
205 param_index: param_idx,
206 });
207 }
208
209 for qubit in 0..(N - 1) {
211 circuit.cnot(QubitId(qubit as u32), QubitId((qubit + 1) as u32))?;
212 }
213 }
214
215 Ok(())
216 }
217
218 fn build_uccsd_ansatz(
220 circuit: &mut Circuit<N>,
221 parameters: &mut Vec<f64>,
222 parameter_names: &mut Vec<String>,
223 parameter_map: &mut HashMap<String, usize>,
224 param_gate_records: &mut Vec<ParameterizedGateRecord>,
225 occupied_orbitals: usize,
226 virtual_orbitals: usize,
227 ) -> QuantRS2Result<()> {
228 if occupied_orbitals + virtual_orbitals > N {
229 return Err(QuantRS2Error::InvalidInput(format!(
230 "Total orbitals ({}) exceeds number of qubits ({})",
231 occupied_orbitals + virtual_orbitals,
232 N
233 )));
234 }
235
236 for i in 0..occupied_orbitals {
238 circuit.x(QubitId(i as u32))?;
239 }
240
241 for i in 0..occupied_orbitals {
243 for a in occupied_orbitals..(occupied_orbitals + virtual_orbitals) {
244 let param_name = format!("t1_{i}_{a}");
245 let param_idx = parameters.len();
246 parameter_names.push(param_name.clone());
247 parameter_map.insert(param_name, param_idx);
248 parameters.push(0.0);
249
250 circuit.cnot(QubitId(i as u32), QubitId(a as u32))?;
251 let gate_idx = circuit.gates().len();
252 circuit.ry(QubitId(a as u32), 0.0)?;
253 param_gate_records.push(ParameterizedGateRecord {
254 gate_index: gate_idx,
255 qubit: QubitId(a as u32),
256 axis: RotationAxis::Y,
257 param_index: param_idx,
258 });
259 circuit.cnot(QubitId(i as u32), QubitId(a as u32))?;
260 }
261 }
262
263 for i in 0..occupied_orbitals {
265 for j in (i + 1)..occupied_orbitals {
266 for a in occupied_orbitals..(occupied_orbitals + virtual_orbitals) {
267 for b in (a + 1)..(occupied_orbitals + virtual_orbitals) {
268 if a < N && b < N {
269 let param_name = format!("t2_{i}_{j}_{a}_{b}");
270 let param_idx = parameters.len();
271 parameter_names.push(param_name.clone());
272 parameter_map.insert(param_name, param_idx);
273 parameters.push(0.0);
274
275 circuit.cnot(QubitId(i as u32), QubitId(a as u32))?;
276 circuit.cnot(QubitId(j as u32), QubitId(b as u32))?;
277 let gate_idx = circuit.gates().len();
278 circuit.ry(QubitId(a as u32), 0.0)?;
279 param_gate_records.push(ParameterizedGateRecord {
280 gate_index: gate_idx,
281 qubit: QubitId(a as u32),
282 axis: RotationAxis::Y,
283 param_index: param_idx,
284 });
285 circuit.cnot(QubitId(j as u32), QubitId(b as u32))?;
286 circuit.cnot(QubitId(i as u32), QubitId(a as u32))?;
287 }
288 }
289 }
290 }
291 }
292
293 Ok(())
294 }
295
296 fn build_real_space_ansatz(
298 circuit: &mut Circuit<N>,
299 parameters: &mut Vec<f64>,
300 parameter_names: &mut Vec<String>,
301 parameter_map: &mut HashMap<String, usize>,
302 param_gate_records: &mut Vec<ParameterizedGateRecord>,
303 geometry: &[(f64, f64, f64)],
304 ) -> QuantRS2Result<()> {
305 if geometry.len() > N {
306 return Err(QuantRS2Error::InvalidInput(format!(
307 "Geometry has {} sites but circuit only has {} qubits",
308 geometry.len(),
309 N
310 )));
311 }
312
313 for (i, &(x1, y1, z1)) in geometry.iter().enumerate() {
315 for (j, &(x2, y2, z2)) in geometry.iter().enumerate().skip(i + 1) {
316 let distance = (z2 - z1)
317 .mul_add(z2 - z1, (y2 - y1).mul_add(y2 - y1, (x2 - x1).powi(2)))
318 .sqrt();
319
320 if distance < 3.0 {
322 let param_name = format!("j_{i}_{j}");
323 let param_idx = parameters.len();
324 parameter_names.push(param_name.clone());
325 parameter_map.insert(param_name, param_idx);
326 parameters.push(0.0);
327
328 circuit.cnot(QubitId(i as u32), QubitId(j as u32))?;
329 let gate_idx = circuit.gates().len();
330 circuit.rz(QubitId(j as u32), 0.0)?;
331 param_gate_records.push(ParameterizedGateRecord {
332 gate_index: gate_idx,
333 qubit: QubitId(j as u32),
334 axis: RotationAxis::Z,
335 param_index: param_idx,
336 });
337 circuit.cnot(QubitId(i as u32), QubitId(j as u32))?;
338 }
339 }
340 }
341
342 Ok(())
343 }
344
345 pub fn set_parameters(&mut self, new_parameters: &[f64]) -> QuantRS2Result<()> {
352 if new_parameters.len() != self.parameters.len() {
353 return Err(QuantRS2Error::InvalidInput(format!(
354 "Expected {} parameters, got {}",
355 self.parameters.len(),
356 new_parameters.len()
357 )));
358 }
359
360 self.parameters = new_parameters.to_vec();
361
362 let record_map: HashMap<usize, &ParameterizedGateRecord> = self
364 .param_gate_records
365 .iter()
366 .map(|r| (r.gate_index, r))
367 .collect();
368
369 let old_gates = self.circuit.gates_as_boxes();
371
372 let new_gates: Vec<Box<dyn GateOp>> = old_gates
374 .into_iter()
375 .enumerate()
376 .map(|(idx, gate)| -> Box<dyn GateOp> {
377 if let Some(record) = record_map.get(&idx) {
378 let angle = self.parameters[record.param_index];
379 match record.axis {
380 RotationAxis::Y => Box::new(RotationY {
381 target: record.qubit,
382 theta: angle,
383 }),
384 RotationAxis::Z => Box::new(RotationZ {
385 target: record.qubit,
386 theta: angle,
387 }),
388 RotationAxis::X => Box::new(RotationX {
389 target: record.qubit,
390 theta: angle,
391 }),
392 }
393 } else {
394 gate
395 }
396 })
397 .collect();
398
399 self.circuit = Circuit::<N>::from_gates(new_gates)?;
401
402 Ok(())
403 }
404
405 #[must_use]
407 pub fn get_parameter(&self, name: &str) -> Option<f64> {
408 self.parameter_map
409 .get(name)
410 .map(|&index| self.parameters[index])
411 }
412
413 pub fn set_parameter(&mut self, name: &str, value: f64) -> QuantRS2Result<()> {
415 let index = self
416 .parameter_map
417 .get(name)
418 .ok_or_else(|| QuantRS2Error::InvalidInput(format!("Parameter '{name}' not found")))?;
419
420 self.parameters[*index] = value;
421 Ok(())
422 }
423
424 pub fn add_parameterized_ry(
428 &mut self,
429 qubit: QubitId,
430 parameter_name: &str,
431 ) -> QuantRS2Result<()> {
432 if self.parameter_map.contains_key(parameter_name) {
433 return Err(QuantRS2Error::InvalidInput(format!(
434 "Parameter '{parameter_name}' already exists"
435 )));
436 }
437
438 let param_idx = self.parameters.len();
439 self.parameter_names.push(parameter_name.to_string());
440 self.parameter_map
441 .insert(parameter_name.to_string(), param_idx);
442 self.parameters.push(0.0);
443
444 let gate_idx = self.circuit.gates().len();
445 self.circuit.ry(qubit, 0.0)?;
446 self.param_gate_records.push(ParameterizedGateRecord {
447 gate_index: gate_idx,
448 qubit,
449 axis: RotationAxis::Y,
450 param_index: param_idx,
451 });
452
453 Ok(())
454 }
455
456 pub fn add_parameterized_rz(
460 &mut self,
461 qubit: QubitId,
462 parameter_name: &str,
463 ) -> QuantRS2Result<()> {
464 if self.parameter_map.contains_key(parameter_name) {
465 return Err(QuantRS2Error::InvalidInput(format!(
466 "Parameter '{parameter_name}' already exists"
467 )));
468 }
469
470 let param_idx = self.parameters.len();
471 self.parameter_names.push(parameter_name.to_string());
472 self.parameter_map
473 .insert(parameter_name.to_string(), param_idx);
474 self.parameters.push(0.0);
475
476 let gate_idx = self.circuit.gates().len();
477 self.circuit.rz(qubit, 0.0)?;
478 self.param_gate_records.push(ParameterizedGateRecord {
479 gate_index: gate_idx,
480 qubit,
481 axis: RotationAxis::Z,
482 param_index: param_idx,
483 });
484
485 Ok(())
486 }
487
488 #[must_use]
490 pub fn num_parameters(&self) -> usize {
491 self.parameters.len()
492 }
493}
494
495impl VQEObservable {
496 #[must_use]
498 pub const fn new() -> Self {
499 Self { terms: Vec::new() }
500 }
501
502 pub fn add_pauli_term(&mut self, coefficient: f64, pauli_string: Vec<(usize, PauliOperator)>) {
504 self.terms.push((coefficient, pauli_string));
505 }
506
507 #[must_use]
509 pub fn heisenberg_model(num_qubits: usize, j_coupling: f64) -> Self {
510 let mut observable = Self::new();
511
512 for i in 0..(num_qubits - 1) {
513 observable.add_pauli_term(
515 j_coupling,
516 vec![(i, PauliOperator::X), (i + 1, PauliOperator::X)],
517 );
518 observable.add_pauli_term(
520 j_coupling,
521 vec![(i, PauliOperator::Y), (i + 1, PauliOperator::Y)],
522 );
523 observable.add_pauli_term(
525 j_coupling,
526 vec![(i, PauliOperator::Z), (i + 1, PauliOperator::Z)],
527 );
528 }
529
530 observable
531 }
532
533 #[must_use]
535 pub fn tfim(num_qubits: usize, j_coupling: f64, h_field: f64) -> Self {
536 let mut observable = Self::new();
537
538 for i in 0..(num_qubits - 1) {
540 observable.add_pauli_term(
541 -j_coupling,
542 vec![(i, PauliOperator::Z), (i + 1, PauliOperator::Z)],
543 );
544 }
545
546 for i in 0..num_qubits {
548 observable.add_pauli_term(-h_field, vec![(i, PauliOperator::X)]);
549 }
550
551 observable
552 }
553
554 #[must_use]
556 pub fn molecular_hamiltonian(
557 one_body: &[(usize, usize, f64)],
558 two_body: &[(usize, usize, usize, usize, f64)],
559 ) -> Self {
560 let mut observable = Self::new();
561
562 for &(i, j, coeff) in one_body {
564 if i == j {
565 observable.add_pauli_term(coeff, vec![(i, PauliOperator::Z)]);
567 } else {
568 observable
570 .add_pauli_term(coeff, vec![(i, PauliOperator::X), (j, PauliOperator::X)]);
571 observable
572 .add_pauli_term(coeff, vec![(i, PauliOperator::Y), (j, PauliOperator::Y)]);
573 }
574 }
575
576 for &(i, j, k, l, coeff) in two_body {
578 observable.add_pauli_term(
581 coeff,
582 vec![
583 (i, PauliOperator::Z),
584 (j, PauliOperator::Z),
585 (k, PauliOperator::Z),
586 (l, PauliOperator::Z),
587 ],
588 );
589 }
590
591 observable
592 }
593}
594
595impl Default for VQEObservable {
596 fn default() -> Self {
597 Self::new()
598 }
599}
600
601pub struct VQEOptimizer {
603 pub max_iterations: usize,
605 pub tolerance: f64,
607 pub learning_rate: f64,
609 pub optimizer_type: VQEOptimizerType,
611}
612
613#[derive(Debug, Clone, PartialEq)]
615pub enum VQEOptimizerType {
616 GradientDescent,
618 Adam { beta1: f64, beta2: f64 },
620 BFGS,
622 NelderMead,
624 SPSA { alpha: f64, gamma: f64 },
626}
627
628impl VQEOptimizer {
629 #[must_use]
631 pub const fn new(optimizer_type: VQEOptimizerType) -> Self {
632 Self {
633 max_iterations: 1000,
634 tolerance: 1e-6,
635 learning_rate: 0.01,
636 optimizer_type,
637 }
638 }
639
640 pub fn optimize<const N: usize>(
642 &self,
643 circuit: &mut VQECircuit<N>,
644 observable: &VQEObservable,
645 ) -> QuantRS2Result<VQEResult> {
646 let mut current_energy = self.evaluate_energy(circuit, observable)?;
653 let mut best_parameters = circuit.parameters.clone();
654 let mut best_energy = current_energy;
655
656 for iteration in 0..self.max_iterations {
657 let gradients = self.compute_gradients(circuit, observable)?;
659
660 for (i, gradient) in gradients.iter().enumerate() {
662 circuit.parameters[i] -= self.learning_rate * gradient;
663 }
664
665 current_energy = self.evaluate_energy(circuit, observable)?;
667
668 if current_energy < best_energy {
669 best_energy = current_energy;
670 best_parameters.clone_from(&circuit.parameters);
671 }
672
673 let gradient_norm = gradients.iter().map(|g| g * g).sum::<f64>().sqrt();
675 if gradient_norm < self.tolerance {
676 circuit.parameters = best_parameters;
677 return Ok(VQEResult {
678 optimal_parameters: circuit.parameters.clone(),
679 ground_state_energy: best_energy,
680 iterations: iteration + 1,
681 converged: true,
682 gradient_norm,
683 });
684 }
685 }
686
687 circuit.parameters = best_parameters;
688 Ok(VQEResult {
689 optimal_parameters: circuit.parameters.clone(),
690 ground_state_energy: best_energy,
691 iterations: self.max_iterations,
692 converged: false,
693 gradient_norm: 0.0, })
695 }
696
697 const fn evaluate_energy<const N: usize>(
699 &self,
700 _circuit: &VQECircuit<N>,
701 _observable: &VQEObservable,
702 ) -> QuantRS2Result<f64> {
703 Ok(-1.0)
710 }
711
712 fn compute_gradients<const N: usize>(
714 &self,
715 circuit: &VQECircuit<N>,
716 _observable: &VQEObservable,
717 ) -> QuantRS2Result<Vec<f64>> {
718 Ok(vec![0.001; circuit.parameters.len()])
725 }
726}
727
728impl Default for VQEOptimizer {
729 fn default() -> Self {
730 Self::new(VQEOptimizerType::GradientDescent)
731 }
732}
733
734#[cfg(test)]
735mod tests {
736 use super::*;
737
738 #[test]
739 fn test_hardware_efficient_ansatz() {
740 let circuit = VQECircuit::<4>::new(VQEAnsatz::HardwareEfficient { layers: 2 })
741 .expect("create VQE circuit");
742 assert!(!circuit.parameters.is_empty());
743 assert_eq!(circuit.parameter_names.len(), circuit.parameters.len());
744 }
745
746 #[test]
747 fn test_observable_creation() {
748 let obs = VQEObservable::heisenberg_model(4, 1.0);
749 assert!(!obs.terms.is_empty());
750 }
751
752 #[test]
753 fn test_parameter_management() {
754 let mut circuit =
755 VQECircuit::<2>::new(VQEAnsatz::Custom).expect("create custom VQE circuit");
756 circuit
757 .add_parameterized_ry(QubitId(0), "theta1")
758 .expect("add parameterized RY gate");
759 circuit
760 .set_parameter("theta1", 0.5)
761 .expect("set parameter theta1");
762 assert_eq!(circuit.get_parameter("theta1"), Some(0.5));
763 }
764
765 #[test]
766 fn test_set_parameters_updates_circuit_gates() {
767 use std::f64::consts::PI;
768
769 let mut vqe = VQECircuit::<2>::new(VQEAnsatz::Custom).expect("custom VQE");
771 vqe.add_parameterized_ry(QubitId(0), "theta")
772 .expect("add RY");
773 vqe.add_parameterized_rz(QubitId(1), "phi").expect("add RZ");
774
775 assert_eq!(vqe.num_parameters(), 2);
776
777 assert_eq!(vqe.get_parameter("theta"), Some(0.0));
779 assert_eq!(vqe.get_parameter("phi"), Some(0.0));
780
781 vqe.set_parameters(&[PI / 4.0, PI / 2.0])
783 .expect("set params");
784
785 assert!((vqe.get_parameter("theta").unwrap() - PI / 4.0).abs() < 1e-12);
787 assert!((vqe.get_parameter("phi").unwrap() - PI / 2.0).abs() < 1e-12);
788
789 assert_eq!(vqe.circuit.gates().len(), 2);
791
792 let gate_names: Vec<&str> = vqe.circuit.gates().iter().map(|g| g.name()).collect();
795 assert_eq!(gate_names, vec!["RY", "RZ"]);
796 }
797
798 #[test]
799 fn test_set_parameters_hardware_efficient() {
800 use std::f64::consts::PI;
801
802 let mut vqe = VQECircuit::<2>::new(VQEAnsatz::HardwareEfficient { layers: 1 })
803 .expect("hardware-efficient VQE");
804
805 let n_params = vqe.num_parameters();
806 assert!(n_params > 0);
807
808 let new_params: Vec<f64> = vec![PI / 3.0; n_params];
810 vqe.set_parameters(&new_params).expect("set all params");
811
812 for &p in &vqe.parameters {
814 assert!((p - PI / 3.0).abs() < 1e-12);
815 }
816 }
817
818 #[test]
819 fn test_set_parameters_wrong_length_fails() {
820 let mut vqe = VQECircuit::<2>::new(VQEAnsatz::Custom).expect("custom VQE");
821 vqe.add_parameterized_ry(QubitId(0), "theta")
822 .expect("add RY");
823
824 let result = vqe.set_parameters(&[0.1, 0.2]);
826 assert!(result.is_err());
827 }
828}