1use super::execution::{BatchCircuit, BatchCircuitExecutor};
4use super::BatchStateVector;
5use crate::error::{QuantRS2Error, QuantRS2Result};
6use ndarray::{Array1, Array2};
7use num_complex::Complex64;
8use rayon::prelude::*;
9use std::sync::Arc;
10
11extern crate scirs2_optimize;
13use scirs2_optimize::unconstrained::{minimize, Method, OptimizeResult, Options};
14
15pub struct BatchParameterOptimizer {
17 executor: BatchCircuitExecutor,
19 config: OptimizationConfig,
21 gradient_cache: Option<GradientCache>,
23}
24
25#[derive(Debug, Clone)]
27pub struct OptimizationConfig {
28 pub max_iterations: usize,
30 pub tolerance: f64,
32 pub learning_rate: f64,
34 pub parallel_gradients: bool,
36 pub method: Method,
38 pub enable_cache: bool,
40}
41
42impl Default for OptimizationConfig {
43 fn default() -> Self {
44 Self {
45 max_iterations: 100,
46 tolerance: 1e-6,
47 learning_rate: 0.1,
48 parallel_gradients: true,
49 method: Method::BFGS,
50 enable_cache: true,
51 }
52 }
53}
54
55#[derive(Debug, Clone)]
57struct GradientCache {
58 gradients: Vec<Array1<f64>>,
60 parameters: Vec<Vec<f64>>,
62 max_size: usize,
64}
65
66impl BatchParameterOptimizer {
67 pub fn new(executor: BatchCircuitExecutor, config: OptimizationConfig) -> Self {
69 let gradient_cache = if config.enable_cache {
70 Some(GradientCache {
71 gradients: Vec::new(),
72 parameters: Vec::new(),
73 max_size: 100,
74 })
75 } else {
76 None
77 };
78
79 Self {
80 executor,
81 config,
82 gradient_cache,
83 }
84 }
85
86 pub fn optimize_batch(
88 &mut self,
89 circuit_fn: impl Fn(&[f64]) -> QuantRS2Result<BatchCircuit> + Sync + Send + Clone + 'static,
90 initial_params: &[f64],
91 cost_fn: impl Fn(&BatchStateVector) -> f64 + Sync + Send + Clone + 'static,
92 initial_states: &BatchStateVector,
93 ) -> QuantRS2Result<OptimizeResult<f64>> {
94 let _num_params = initial_params.len();
95
96 let executor = Arc::new(self.executor.clone());
98 let states = Arc::new(initial_states.clone());
99 let circuit_fn = Arc::new(circuit_fn);
100 let cost_fn = Arc::new(cost_fn);
101
102 let objective = {
103 let executor = executor.clone();
104 let states = states.clone();
105 let circuit_fn = circuit_fn.clone();
106 let cost_fn = cost_fn.clone();
107
108 move |params: &ndarray::ArrayView1<f64>| -> f64 {
109 let params_slice = params.as_slice().unwrap();
110 let circuit = match (*circuit_fn)(params_slice) {
111 Ok(c) => c,
112 Err(_) => return f64::INFINITY,
113 };
114
115 let mut batch_copy = (*states).clone();
116 match executor.execute_batch(&circuit, &mut batch_copy) {
117 Ok(_) => (*cost_fn)(&batch_copy),
118 Err(_) => f64::INFINITY,
119 }
120 }
121 };
122
123 let options = Options {
125 max_iter: self.config.max_iterations,
126 ftol: self.config.tolerance,
127 gtol: self.config.tolerance,
128 ..Default::default()
129 };
130
131 let result = minimize(objective, initial_params, self.config.method, Some(options));
133
134 match result {
135 Ok(opt_result) => Ok(opt_result),
136 Err(e) => Err(QuantRS2Error::InvalidInput(format!(
137 "Optimization failed: {:?}",
138 e
139 ))),
140 }
141 }
142
143 pub fn compute_gradients_batch(
145 &mut self,
146 circuit_fn: impl Fn(&[f64]) -> QuantRS2Result<BatchCircuit> + Sync + Send,
147 params: &[f64],
148 cost_fn: impl Fn(&BatchStateVector) -> f64 + Sync + Send,
149 initial_states: &BatchStateVector,
150 shift: f64,
151 ) -> QuantRS2Result<Vec<f64>> {
152 if let Some(cache) = &self.gradient_cache {
154 for (i, cached_params) in cache.parameters.iter().enumerate() {
155 if params
156 .iter()
157 .zip(cached_params)
158 .all(|(a, b)| (a - b).abs() < 1e-10)
159 {
160 return Ok(cache.gradients[i].to_vec());
161 }
162 }
163 }
164
165 let num_params = params.len();
166
167 if self.config.parallel_gradients {
168 let executor = self.executor.clone();
171 let gradients: Vec<f64> = (0..num_params)
172 .into_par_iter()
173 .map(|i| {
174 compute_single_gradient_static(
175 &executor,
176 &circuit_fn,
177 params,
178 i,
179 &cost_fn,
180 initial_states,
181 shift,
182 )
183 .unwrap_or(0.0)
184 })
185 .collect();
186
187 if let Some(cache) = &mut self.gradient_cache {
189 if cache.gradients.len() >= cache.max_size {
190 cache.gradients.remove(0);
191 cache.parameters.remove(0);
192 }
193 cache.gradients.push(Array1::from_vec(gradients.clone()));
194 cache.parameters.push(params.to_vec());
195 }
196
197 Ok(gradients)
198 } else {
199 let mut gradients = vec![0.0; num_params];
201
202 for i in 0..num_params {
203 gradients[i] = self.compute_single_gradient(
204 &circuit_fn,
205 params,
206 i,
207 &cost_fn,
208 initial_states,
209 shift,
210 )?;
211 }
212
213 Ok(gradients)
214 }
215 }
216
217 fn compute_single_gradient(
219 &mut self,
220 circuit_fn: impl Fn(&[f64]) -> QuantRS2Result<BatchCircuit>,
221 params: &[f64],
222 param_idx: usize,
223 cost_fn: impl Fn(&BatchStateVector) -> f64,
224 initial_states: &BatchStateVector,
225 shift: f64,
226 ) -> QuantRS2Result<f64> {
227 compute_single_gradient_static(
228 &self.executor,
229 &circuit_fn,
230 params,
231 param_idx,
232 &cost_fn,
233 initial_states,
234 shift,
235 )
236 }
237
238 pub fn optimize_parallel_batch(
240 &mut self,
241 circuit_fn: impl Fn(&[f64]) -> QuantRS2Result<BatchCircuit> + Sync + Send + Clone + 'static,
242 initial_param_sets: &[Vec<f64>],
243 cost_fn: impl Fn(&BatchStateVector) -> f64 + Sync + Send + Clone + 'static,
244 initial_states: &BatchStateVector,
245 ) -> QuantRS2Result<Vec<OptimizeResult<f64>>> {
246 let results: Vec<_> = initial_param_sets
247 .par_iter()
248 .map(|params| {
249 let mut optimizer = self.clone();
250 optimizer.optimize_batch(
251 circuit_fn.clone(),
252 params,
253 cost_fn.clone(),
254 initial_states,
255 )
256 })
257 .collect::<QuantRS2Result<Vec<_>>>()?;
258
259 Ok(results)
260 }
261}
262
263impl Clone for BatchParameterOptimizer {
264 fn clone(&self) -> Self {
265 Self {
266 executor: self.executor.clone(),
267 config: self.config.clone(),
268 gradient_cache: self.gradient_cache.clone(),
269 }
270 }
271}
272
273impl Clone for BatchCircuitExecutor {
274 fn clone(&self) -> Self {
275 Self {
276 config: self.config.clone(),
277 gpu_backend: self.gpu_backend.clone(),
278 thread_pool: None, }
280 }
281}
282
283fn compute_single_gradient_static(
285 executor: &BatchCircuitExecutor,
286 circuit_fn: &impl Fn(&[f64]) -> QuantRS2Result<BatchCircuit>,
287 params: &[f64],
288 param_idx: usize,
289 cost_fn: &impl Fn(&BatchStateVector) -> f64,
290 initial_states: &BatchStateVector,
291 shift: f64,
292) -> QuantRS2Result<f64> {
293 let mut params_plus = params.to_vec();
295 let mut params_minus = params.to_vec();
296
297 params_plus[param_idx] += shift;
298 params_minus[param_idx] -= shift;
299
300 let circuit_plus = circuit_fn(¶ms_plus)?;
302 let circuit_minus = circuit_fn(¶ms_minus)?;
303
304 let mut states_plus = initial_states.clone();
305 let mut states_minus = initial_states.clone();
306
307 let result_plus = executor.execute_batch(&circuit_plus, &mut states_plus);
308 let result_minus = executor.execute_batch(&circuit_minus, &mut states_minus);
309
310 result_plus?;
311 result_minus?;
312
313 let cost_plus = cost_fn(&states_plus);
314 let cost_minus = cost_fn(&states_minus);
315
316 Ok((cost_plus - cost_minus) / (2.0 * shift))
317}
318
319pub struct BatchVQE {
321 optimizer: BatchParameterOptimizer,
323 hamiltonian: Array2<Complex64>,
325}
326
327impl BatchVQE {
328 pub fn new(
330 executor: BatchCircuitExecutor,
331 hamiltonian: Array2<Complex64>,
332 config: OptimizationConfig,
333 ) -> Self {
334 Self {
335 optimizer: BatchParameterOptimizer::new(executor, config),
336 hamiltonian,
337 }
338 }
339
340 pub fn optimize(
342 &mut self,
343 ansatz_fn: impl Fn(&[f64]) -> QuantRS2Result<BatchCircuit> + Sync + Send + Clone + 'static,
344 initial_params: &[f64],
345 num_samples: usize,
346 n_qubits: usize,
347 ) -> QuantRS2Result<VQEResult> {
348 let batch = BatchStateVector::new(num_samples, n_qubits, Default::default())?;
350
351 let hamiltonian = self.hamiltonian.clone();
353 let cost_fn = move |states: &BatchStateVector| -> f64 {
354 let mut total_energy = 0.0;
355
356 for i in 0..states.batch_size() {
357 if let Ok(state) = states.get_state(i) {
358 let energy = compute_energy(&state, &hamiltonian);
359 total_energy += energy;
360 }
361 }
362
363 total_energy / states.batch_size() as f64
364 };
365
366 let result = self
368 .optimizer
369 .optimize_batch(ansatz_fn, initial_params, cost_fn, &batch)?;
370
371 Ok(VQEResult {
372 optimal_params: result.x.to_vec(),
373 ground_state_energy: result.fun,
374 iterations: result.iterations,
375 converged: result.success,
376 })
377 }
378}
379
380#[derive(Debug, Clone)]
382pub struct VQEResult {
383 pub optimal_params: Vec<f64>,
385 pub ground_state_energy: f64,
387 pub iterations: usize,
389 pub converged: bool,
391}
392
393fn compute_energy(state: &Array1<Complex64>, hamiltonian: &Array2<Complex64>) -> f64 {
395 let temp = hamiltonian.dot(state);
396 let energy = state
397 .iter()
398 .zip(temp.iter())
399 .map(|(a, b)| a.conj() * b)
400 .sum::<Complex64>();
401
402 energy.re
403}
404
405pub struct BatchQAOA {
407 optimizer: BatchParameterOptimizer,
409 cost_hamiltonian: Array2<Complex64>,
411 mixer_hamiltonian: Array2<Complex64>,
413 p: usize,
415}
416
417impl BatchQAOA {
418 pub fn new(
420 executor: BatchCircuitExecutor,
421 cost_hamiltonian: Array2<Complex64>,
422 mixer_hamiltonian: Array2<Complex64>,
423 p: usize,
424 config: OptimizationConfig,
425 ) -> Self {
426 Self {
427 optimizer: BatchParameterOptimizer::new(executor, config),
428 cost_hamiltonian,
429 mixer_hamiltonian,
430 p,
431 }
432 }
433
434 pub fn optimize(
436 &mut self,
437 initial_params: &[f64],
438 num_samples: usize,
439 n_qubits: usize,
440 ) -> QuantRS2Result<QAOAResult> {
441 if initial_params.len() != 2 * self.p {
442 return Err(QuantRS2Error::InvalidInput(format!(
443 "Expected {} parameters, got {}",
444 2 * self.p,
445 initial_params.len()
446 )));
447 }
448
449 let _p = self.p;
451 let _cost_ham = self.cost_hamiltonian.clone();
452 let _mixer_ham = self.mixer_hamiltonian.clone();
453
454 let qaoa_circuit = move |_params: &[f64]| -> QuantRS2Result<BatchCircuit> {
455 let circuit = BatchCircuit::new(n_qubits);
457 Ok(circuit)
459 };
460
461 let batch = BatchStateVector::new(num_samples, n_qubits, Default::default())?;
463 let cost_hamiltonian = self.cost_hamiltonian.clone();
467 let cost_fn = move |states: &BatchStateVector| -> f64 {
468 let mut total_cost = 0.0;
469
470 for i in 0..states.batch_size() {
471 if let Ok(state) = states.get_state(i) {
472 let cost = compute_energy(&state, &cost_hamiltonian);
473 total_cost += cost;
474 }
475 }
476
477 total_cost / states.batch_size() as f64
478 };
479
480 let result =
482 self.optimizer
483 .optimize_batch(qaoa_circuit, initial_params, cost_fn, &batch)?;
484
485 Ok(QAOAResult {
486 optimal_params: result.x.to_vec(),
487 optimal_cost: result.fun,
488 iterations: result.iterations,
489 converged: result.success,
490 })
491 }
492}
493
494#[derive(Debug, Clone)]
496pub struct QAOAResult {
497 pub optimal_params: Vec<f64>,
499 pub optimal_cost: f64,
501 pub iterations: usize,
503 pub converged: bool,
505}
506
507#[cfg(test)]
508mod tests {
509 use super::*;
510 use crate::gate::single::Hadamard;
511 use crate::qubit::QubitId;
512 use ndarray::array;
513
514 #[test]
515 fn test_gradient_computation() {
516 let config = Default::default();
517 let executor = BatchCircuitExecutor::new(config).unwrap();
518 let mut optimizer = BatchParameterOptimizer::new(executor, Default::default());
519
520 let circuit_fn = |_params: &[f64]| -> QuantRS2Result<BatchCircuit> {
522 let mut circuit = BatchCircuit::new(1);
523 circuit.add_gate(Box::new(Hadamard { target: QubitId(0) }))?;
525 Ok(circuit)
526 };
527
528 let cost_fn = |_states: &BatchStateVector| -> f64 { 1.0 };
530
531 let batch = BatchStateVector::new(1, 1, Default::default()).unwrap();
532 let params = vec![0.5];
533
534 let gradients = optimizer
535 .compute_gradients_batch(circuit_fn, ¶ms, cost_fn, &batch, 0.01)
536 .unwrap();
537
538 assert_eq!(gradients.len(), 1);
539 }
540
541 #[test]
542 fn test_vqe_setup() {
543 let executor = BatchCircuitExecutor::new(Default::default()).unwrap();
544
545 let hamiltonian = array![
547 [Complex64::new(1.0, 0.0), Complex64::new(0.0, 0.0)],
548 [Complex64::new(0.0, 0.0), Complex64::new(-1.0, 0.0)]
549 ];
550
551 let vqe = BatchVQE::new(executor, hamiltonian, Default::default());
552
553 assert_eq!(vqe.hamiltonian.shape(), &[2, 2]);
555 }
556}