1use crate::error::{MLError, Result};
8use crate::optimization::Optimizer;
9use quantrs2_circuit::prelude::Circuit;
10use scirs2_core::ndarray::Array1;
11use scirs2_core::random::prelude::*;
12use scirs2_core::Complex64;
13
14#[derive(Debug, Clone, Copy)]
16pub enum VariationalAlgorithm {
17 VQE,
19
20 QAOA,
22
23 QSVM,
25
26 QNN,
28
29 Custom,
31}
32
33#[derive(Debug, Clone, Copy)]
35pub enum AnsatzType {
36 HardwareEfficient,
38
39 UCCSD,
41
42 QAOA,
44
45 Custom,
47}
48
49#[derive(Debug, Clone)]
51pub struct VariationalCircuit {
52 pub num_qubits: usize,
54
55 pub num_params: usize,
57
58 pub parameters: Array1<f64>,
60
61 pub num_layers: usize,
63
64 pub ansatz_type: AnsatzType,
66}
67
68impl VariationalCircuit {
69 pub fn new(
71 num_qubits: usize,
72 num_params: usize,
73 num_layers: usize,
74 ansatz_type: AnsatzType,
75 ) -> Result<Self> {
76 let parameters = Array1::from_vec(
78 (0..num_params)
79 .map(|_| thread_rng().random::<f64>() * 2.0 * std::f64::consts::PI)
80 .collect(),
81 );
82
83 Ok(VariationalCircuit {
84 num_qubits,
85 num_params,
86 parameters,
87 num_layers,
88 ansatz_type,
89 })
90 }
91
92 pub fn create_circuit<const N: usize>(&self) -> Result<Circuit<N>> {
94 let mut circuit = Circuit::<N>::new();
98
99 for i in 0..N.min(self.num_qubits) {
100 circuit.h(i)?;
102
103 if i < self.parameters.len() {
104 circuit.rz(i, self.parameters[i])?;
105 }
106 }
107
108 match self.ansatz_type {
110 AnsatzType::HardwareEfficient => {
111 for i in 0..N.min(self.num_qubits) - 1 {
113 circuit.cnot(i, i + 1)?;
114 }
115 }
116 AnsatzType::UCCSD => {
117 for i in 0..N.min(self.num_qubits) / 2 {
119 let j = N.min(self.num_qubits) / 2 + i;
120 if j < N {
121 circuit.cnot(i, j)?;
122 }
123 }
124 }
125 AnsatzType::QAOA => {
126 for i in 0..N.min(self.num_qubits) {
128 for j in i + 1..N.min(self.num_qubits) {
129 circuit.cnot(i, j)?;
130 }
131 }
132 }
133 AnsatzType::Custom => {
134 if N >= 3 {
136 circuit.cnot(0, 1)?;
137 circuit.cnot(1, 2)?;
138 if N > 3 {
139 circuit.cnot(2, 3)?;
140 }
141 }
142 }
143 }
144
145 Ok(circuit)
146 }
147
148 pub fn compute_expectation(&self, hamiltonian: &[(f64, Vec<(usize, usize)>)]) -> Result<f64> {
161 let n = self.num_qubits;
162 if n == 0 {
163 return Ok(0.0);
164 }
165 let dim = 1usize << n;
166
167 let mut state = vec![Complex64::new(0.0, 0.0); dim];
169 state[0] = Complex64::new(1.0, 0.0);
170
171 let mut param_idx = 0usize;
172
173 for _layer in 0..self.num_layers.max(1) {
174 for q in 0..n {
176 apply_single_qubit_h(&mut state, q, n);
177 }
178
179 for q in 0..n {
181 let theta = if self.parameters.is_empty() {
182 0.0
183 } else {
184 self.parameters[param_idx % self.parameters.len()]
185 };
186 param_idx += 1;
187 apply_single_qubit_rz(&mut state, q, n, theta);
188 }
189
190 for q in 0..n.saturating_sub(1) {
192 apply_cnot(&mut state, q, q + 1, n);
193 }
194 }
195
196 let mut expectation = 0.0f64;
198
199 for (coef, pauli_terms) in hamiltonian {
200 let mut psi_p = vec![Complex64::new(0.0, 0.0); dim];
205 for j in 0..dim {
206 if state[j].norm_sqr() < 1e-30 {
207 continue;
208 }
209 let mut coeff = state[j];
210 let mut target = j;
211 for &(qubit, pauli_type) in pauli_terms {
212 if qubit >= n {
213 continue;
214 }
215 let bit = (j >> qubit) & 1;
216 match pauli_type {
217 0 => {} 1 => {
219 target ^= 1 << qubit;
221 }
222 2 => {
223 target ^= 1 << qubit;
225 coeff *= if bit == 0 {
226 Complex64::new(0.0, 1.0)
227 } else {
228 Complex64::new(0.0, -1.0)
229 };
230 }
231 3 => {
232 if bit == 1 {
234 coeff *= Complex64::new(-1.0, 0.0);
235 }
236 }
237 _ => {} }
239 }
240 if target < dim {
241 psi_p[target] += coeff;
242 }
243 }
244 let pauli_exp: Complex64 = state
246 .iter()
247 .zip(psi_p.iter())
248 .map(|(a, b)| a.conj() * b)
249 .sum();
250 expectation += coef * pauli_exp.re;
251 }
252
253 Ok(expectation)
254 }
255
256 pub fn evaluate(&self, objective: &dyn Fn(&VariationalCircuit) -> Result<f64>) -> Result<f64> {
258 objective(self)
259 }
260
261 pub fn optimize(
269 &mut self,
270 objective: &dyn Fn(&VariationalCircuit) -> Result<f64>,
271 optimizer: &Optimizer,
272 max_iterations: usize,
273 ) -> Result<f64> {
274 let shift = std::f64::consts::PI / 2.0;
275 let lr = match optimizer {
276 Optimizer::GradientDescent { learning_rate } => *learning_rate,
277 Optimizer::Adam { learning_rate, .. } => *learning_rate,
278 Optimizer::SPSA { learning_rate, .. } => *learning_rate,
279 Optimizer::QuantumNaturalGradient { learning_rate, .. } => *learning_rate,
280 Optimizer::SciRS2 { config, .. } => {
281 config.get("learning_rate").copied().unwrap_or(0.01)
282 }
283 };
284
285 let mut best_value = self.evaluate(objective)?;
286
287 for _ in 0..max_iterations {
288 let n_params = self.parameters.len();
289 let mut gradient = vec![0.0f64; n_params];
290
291 for i in 0..n_params {
292 let mut plus = self.clone();
293 plus.parameters[i] += shift;
294 let mut minus = self.clone();
295 minus.parameters[i] -= shift;
296 gradient[i] = (plus.evaluate(objective)? - minus.evaluate(objective)?) * 0.5;
297 }
298
299 for i in 0..n_params {
300 self.parameters[i] -= lr * gradient[i];
301 }
302
303 let new_value = self.evaluate(objective)?;
304 if new_value < best_value {
305 best_value = new_value;
306 }
307 }
308
309 Ok(best_value)
310 }
311}
312
313fn apply_single_qubit_h(state: &mut Vec<Complex64>, q: usize, n: usize) {
319 let inv_sqrt2 = 1.0 / std::f64::consts::SQRT_2;
320 let dim = 1 << n;
321 let half_step = 1 << q;
322 let step = half_step << 1;
323 let mut base = 0;
324 while base < dim {
325 for i in base..base + half_step {
326 let a = state[i];
327 let b = state[i + half_step];
328 state[i] = Complex64::new((a.re + b.re) * inv_sqrt2, (a.im + b.im) * inv_sqrt2);
329 state[i + half_step] =
330 Complex64::new((a.re - b.re) * inv_sqrt2, (a.im - b.im) * inv_sqrt2);
331 }
332 base += step;
333 }
334}
335
336fn apply_single_qubit_rz(state: &mut Vec<Complex64>, q: usize, n: usize, theta: f64) {
338 let dim = 1 << n;
339 let phase0 = Complex64::from_polar(1.0, -theta * 0.5);
340 let phase1 = Complex64::from_polar(1.0, theta * 0.5);
341 for i in 0..dim {
342 let bit = (i >> q) & 1;
343 state[i] *= if bit == 0 { phase0 } else { phase1 };
344 }
345}
346
347fn apply_cnot(state: &mut Vec<Complex64>, control: usize, target: usize, n: usize) {
349 let dim = 1 << n;
350 for i in 0..dim {
351 if (i >> control) & 1 == 1 {
352 let j = i ^ (1 << target);
353 if i < j {
354 state.swap(i, j);
355 }
356 }
357 }
358}
359
360#[cfg(test)]
361mod tests {
362 use super::*;
363
364 #[test]
365 fn test_optimize_reduces_objective() {
366 let num_qubits = 2;
371 let num_params = 4;
372 let num_layers = 1;
373 let mut vc = VariationalCircuit::new(
374 num_qubits,
375 num_params,
376 num_layers,
377 AnsatzType::HardwareEfficient,
378 )
379 .expect("circuit creation failed");
380 vc.parameters = Array1::from_vec(vec![1.0, 1.0, 1.0, 1.0]);
382
383 let objective = |vc: &VariationalCircuit| -> Result<f64> {
384 Ok(vc.parameters.iter().map(|p| p * p).sum())
385 };
386
387 let initial = objective(&vc).expect("initial evaluation failed");
388 let optimizer = Optimizer::GradientDescent { learning_rate: 0.1 };
389 let result = vc
390 .optimize(&objective, &optimizer, 20)
391 .expect("optimization failed");
392
393 assert!(
394 result < initial,
395 "optimize should reduce objective: {result} vs initial {initial}"
396 );
397 }
398
399 #[test]
400 fn test_compute_expectation_identity_hamiltonian() {
401 let vc = VariationalCircuit::new(2, 2, 1, AnsatzType::HardwareEfficient)
403 .expect("circuit creation failed");
404 let hamiltonian = vec![(1.0, vec![(0_usize, 0_usize)])]; let exp = vc
406 .compute_expectation(&hamiltonian)
407 .expect("expectation failed");
408 assert!((exp - 1.0).abs() < 1e-10, "⟨I⟩ should be 1.0, got {exp}");
409 }
410}