1use crate::builder::Circuit;
7use crate::scirs2_matrices::SparseMatrix;
8use quantrs2_core::{
9 error::{QuantRS2Error, QuantRS2Result},
10 gate::GateOp,
11 qubit::QubitId,
12};
13use scirs2_core::Complex64;
14use serde::{Deserialize, Serialize};
15use std::collections::HashMap;
16use std::sync::{Arc, Mutex};
17
18pub trait ObjectiveFunction: Send + Sync {
23 fn evaluate(&self, parameters: &[f64]) -> f64;
25
26 fn gradient(&self, parameters: &[f64]) -> Option<Vec<f64>> {
28 None
29 }
30
31 fn hessian(&self, parameters: &[f64]) -> Option<Vec<Vec<f64>>> {
33 None
34 }
35
36 fn bounds(&self) -> Vec<(f64, f64)>;
38
39 fn name(&self) -> &str;
41}
42
43#[derive(Debug, Clone, PartialEq)]
45pub enum OptimizationAlgorithm {
46 GradientDescent { learning_rate: f64, momentum: f64 },
48 Adam {
50 learning_rate: f64,
51 beta1: f64,
52 beta2: f64,
53 epsilon: f64,
54 },
55 LBFGSB {
57 max_iterations: usize,
58 tolerance: f64,
59 },
60 NelderMead {
62 max_iterations: usize,
63 tolerance: f64,
64 },
65 SimulatedAnnealing {
67 initial_temperature: f64,
68 cooling_rate: f64,
69 min_temperature: f64,
70 },
71 GeneticAlgorithm {
73 population_size: usize,
74 mutation_rate: f64,
75 crossover_rate: f64,
76 },
77 ParticleSwarm {
79 num_particles: usize,
80 inertia_weight: f64,
81 cognitive_weight: f64,
82 social_weight: f64,
83 },
84 BayesianOptimization {
86 acquisition_function: AcquisitionFunction,
87 kernel: KernelType,
88 num_initial_samples: usize,
89 },
90 QAOA {
92 num_layers: usize,
93 classical_optimizer: Box<Self>,
94 },
95}
96
97#[derive(Debug, Clone, PartialEq)]
99pub enum AcquisitionFunction {
100 ExpectedImprovement,
101 ProbabilityOfImprovement,
102 UpperConfidenceBound { kappa: f64 },
103 Thompson,
104}
105
106#[derive(Debug, Clone, PartialEq)]
108pub enum KernelType {
109 RBF { length_scale: f64 },
110 Matern { nu: f64, length_scale: f64 },
111 Linear { variance: f64 },
112 Periodic { period: f64, length_scale: f64 },
113}
114
115pub struct OptimizationConfig {
117 pub algorithm: OptimizationAlgorithm,
119 pub max_evaluations: usize,
121 pub tolerance: f64,
123 pub seed: Option<u64>,
125 pub parallel: bool,
127 pub num_threads: Option<usize>,
129 pub progress_callback: Option<Box<dyn Fn(usize, f64) + Send + Sync>>,
131 pub early_stopping: Option<EarlyStoppingCriteria>,
133}
134
135impl std::fmt::Debug for OptimizationConfig {
136 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
137 f.debug_struct("OptimizationConfig")
138 .field("algorithm", &self.algorithm)
139 .field("max_evaluations", &self.max_evaluations)
140 .field("tolerance", &self.tolerance)
141 .field("seed", &self.seed)
142 .field("parallel", &self.parallel)
143 .field("num_threads", &self.num_threads)
144 .field(
145 "progress_callback",
146 &self.progress_callback.as_ref().map(|_| "Some(callback)"),
147 )
148 .field("early_stopping", &self.early_stopping)
149 .finish()
150 }
151}
152
153impl Clone for OptimizationConfig {
154 fn clone(&self) -> Self {
155 Self {
156 algorithm: self.algorithm.clone(),
157 max_evaluations: self.max_evaluations,
158 tolerance: self.tolerance,
159 seed: self.seed,
160 parallel: self.parallel,
161 num_threads: self.num_threads,
162 progress_callback: None, early_stopping: self.early_stopping.clone(),
164 }
165 }
166}
167
168#[derive(Debug, Clone)]
170pub struct EarlyStoppingCriteria {
171 pub patience: usize,
173 pub min_delta: f64,
175 pub monitor_best: bool,
177}
178
179#[derive(Debug, Clone)]
181pub struct OptimizationResult {
182 pub optimal_parameters: Vec<f64>,
184 pub optimal_value: f64,
186 pub num_evaluations: usize,
188 pub converged: bool,
190 pub history: OptimizationHistory,
192 pub algorithm_info: HashMap<String, String>,
194 pub optimization_time: std::time::Duration,
196}
197
198#[derive(Debug, Clone)]
200pub struct OptimizationHistory {
201 pub parameters: Vec<Vec<f64>>,
203 pub objective_values: Vec<f64>,
205 pub gradient_norms: Vec<f64>,
207 pub step_sizes: Vec<f64>,
209 pub timestamps: Vec<std::time::Instant>,
211}
212
213pub struct QuantumCircuitOptimizer {
215 circuit_template: CircuitTemplate,
217 config: OptimizationConfig,
219 history: Arc<Mutex<OptimizationHistory>>,
221 best_parameters: Arc<Mutex<Option<Vec<f64>>>>,
223 best_value: Arc<Mutex<f64>>,
225}
226
227#[derive(Debug, Clone)]
229pub struct CircuitTemplate {
230 pub structure: Vec<ParameterizedGate>,
232 pub parameters: Vec<Parameter>,
234 pub num_qubits: usize,
236}
237
238#[derive(Debug, Clone)]
240pub struct ParameterizedGate {
241 pub gate_name: String,
243 pub qubits: Vec<usize>,
245 pub parameter_indices: Vec<usize>,
247 pub fixed_parameters: Vec<f64>,
249}
250
251#[derive(Debug, Clone)]
253pub struct Parameter {
254 pub name: String,
256 pub lower_bound: f64,
258 pub upper_bound: f64,
260 pub initial_value: f64,
262 pub discrete: bool,
264}
265
266impl QuantumCircuitOptimizer {
267 #[must_use]
269 pub fn new(template: CircuitTemplate, config: OptimizationConfig) -> Self {
270 Self {
271 circuit_template: template,
272 config,
273 history: Arc::new(Mutex::new(OptimizationHistory {
274 parameters: Vec::new(),
275 objective_values: Vec::new(),
276 gradient_norms: Vec::new(),
277 step_sizes: Vec::new(),
278 timestamps: Vec::new(),
279 })),
280 best_parameters: Arc::new(Mutex::new(None)),
281 best_value: Arc::new(Mutex::new(f64::INFINITY)),
282 }
283 }
284
285 pub fn optimize(
287 &mut self,
288 objective: Arc<dyn ObjectiveFunction>,
289 ) -> QuantRS2Result<OptimizationResult> {
290 let start_time = std::time::Instant::now();
291
292 let initial_params: Vec<f64> = self
294 .circuit_template
295 .parameters
296 .iter()
297 .map(|p| p.initial_value)
298 .collect();
299
300 let bounds = objective.bounds();
302 if bounds.len() != initial_params.len() {
303 return Err(QuantRS2Error::InvalidInput(
304 "Parameter count mismatch with bounds".to_string(),
305 ));
306 }
307
308 let result = match &self.config.algorithm {
310 OptimizationAlgorithm::GradientDescent {
311 learning_rate,
312 momentum,
313 } => self.optimize_gradient_descent(
314 objective,
315 &initial_params,
316 *learning_rate,
317 *momentum,
318 ),
319 OptimizationAlgorithm::Adam {
320 learning_rate,
321 beta1,
322 beta2,
323 epsilon,
324 } => self.optimize_adam(
325 objective,
326 &initial_params,
327 *learning_rate,
328 *beta1,
329 *beta2,
330 *epsilon,
331 ),
332 OptimizationAlgorithm::LBFGSB {
333 max_iterations,
334 tolerance,
335 } => self.optimize_lbfgs(objective, &initial_params, *max_iterations, *tolerance),
336 OptimizationAlgorithm::NelderMead {
337 max_iterations,
338 tolerance,
339 } => self.optimize_nelder_mead(objective, &initial_params, *max_iterations, *tolerance),
340 OptimizationAlgorithm::SimulatedAnnealing {
341 initial_temperature,
342 cooling_rate,
343 min_temperature,
344 } => self.optimize_simulated_annealing(
345 objective,
346 &initial_params,
347 *initial_temperature,
348 *cooling_rate,
349 *min_temperature,
350 ),
351 OptimizationAlgorithm::BayesianOptimization {
352 acquisition_function,
353 kernel,
354 num_initial_samples,
355 } => self.optimize_bayesian(
356 objective,
357 &initial_params,
358 acquisition_function,
359 kernel,
360 *num_initial_samples,
361 ),
362 _ => Err(QuantRS2Error::InvalidInput(
363 "Algorithm not yet implemented".to_string(),
364 )),
365 }?;
366
367 let history = self
368 .history
369 .lock()
370 .map_err(|e| QuantRS2Error::RuntimeError(format!("Failed to lock history: {}", e)))?
371 .clone();
372
373 Ok(OptimizationResult {
374 optimal_parameters: result.0,
375 optimal_value: result.1,
376 num_evaluations: result.2,
377 converged: result.3,
378 history,
379 algorithm_info: HashMap::new(),
380 optimization_time: start_time.elapsed(),
381 })
382 }
383
384 fn optimize_gradient_descent(
386 &self,
387 objective: Arc<dyn ObjectiveFunction>,
388 initial_params: &[f64],
389 learning_rate: f64,
390 momentum: f64,
391 ) -> QuantRS2Result<(Vec<f64>, f64, usize, bool)> {
392 let mut params = initial_params.to_vec();
393 let mut velocity = vec![0.0; params.len()];
394 let mut evaluations = 0;
395 let mut best_value = f64::INFINITY;
396 let mut last_step_size = 0.0;
400
401 for iteration in 0..self.config.max_evaluations {
402 let value = objective.evaluate(¶ms);
404 evaluations += 1;
405
406 if value < best_value {
408 best_value = value;
409 if let Ok(mut guard) = self.best_parameters.lock() {
410 *guard = Some(params.clone());
411 }
412 if let Ok(mut guard) = self.best_value.lock() {
413 *guard = best_value;
414 }
415 }
416
417 let gradient = if let Some(grad) = objective.gradient(¶ms) {
421 grad
422 } else {
423 self.numerical_gradient(&*objective, ¶ms)?
424 };
425 let gradient_norm = gradient.iter().map(|g| g * g).sum::<f64>().sqrt();
426
427 self.record_iteration(¶ms, value, iteration, gradient_norm, last_step_size);
429
430 if iteration > 0 {
432 let prev_value = self
433 .history
434 .lock()
435 .ok()
436 .and_then(|h| h.objective_values.get(iteration - 1).copied())
437 .unwrap_or(value);
438 if (prev_value - value).abs() < self.config.tolerance {
439 return Ok((params, best_value, evaluations, true));
440 }
441 }
442
443 for i in 0..params.len() {
445 velocity[i] = momentum.mul_add(velocity[i], -(learning_rate * gradient[i]));
446 params[i] += velocity[i];
447
448 let bounds = objective.bounds();
450 params[i] = params[i].max(bounds[i].0).min(bounds[i].1);
451 }
452 last_step_size = velocity.iter().map(|v| v * v).sum::<f64>().sqrt();
453
454 if let Some(callback) = &self.config.progress_callback {
456 callback(iteration, value);
457 }
458 }
459
460 Ok((params, best_value, evaluations, false))
461 }
462
463 fn optimize_adam(
465 &self,
466 objective: Arc<dyn ObjectiveFunction>,
467 initial_params: &[f64],
468 learning_rate: f64,
469 beta1: f64,
470 beta2: f64,
471 epsilon: f64,
472 ) -> QuantRS2Result<(Vec<f64>, f64, usize, bool)> {
473 let mut params = initial_params.to_vec();
474 let mut m = vec![0.0; params.len()]; let mut v = vec![0.0; params.len()]; let mut evaluations = 0;
477 let mut best_value = f64::INFINITY;
478 let mut last_step_size = 0.0;
479
480 for iteration in 0..self.config.max_evaluations {
481 let t = iteration + 1;
482
483 let value = objective.evaluate(¶ms);
485 evaluations += 1;
486
487 if value < best_value {
489 best_value = value;
490 if let Ok(mut guard) = self.best_parameters.lock() {
491 *guard = Some(params.clone());
492 }
493 if let Ok(mut guard) = self.best_value.lock() {
494 *guard = best_value;
495 }
496 }
497
498 let gradient = if let Some(grad) = objective.gradient(¶ms) {
501 grad
502 } else {
503 self.numerical_gradient(&*objective, ¶ms)?
504 };
505 let gradient_norm = gradient.iter().map(|g| g * g).sum::<f64>().sqrt();
506
507 self.record_iteration(¶ms, value, iteration, gradient_norm, last_step_size);
509
510 if iteration > 0 {
512 let prev_value = self
513 .history
514 .lock()
515 .ok()
516 .and_then(|h| h.objective_values.get(iteration - 1).copied())
517 .unwrap_or(value);
518 if (prev_value - value).abs() < self.config.tolerance {
519 return Ok((params, best_value, evaluations, true));
520 }
521 }
522
523 let mut step_sq_norm = 0.0;
525 for i in 0..params.len() {
526 m[i] = beta1.mul_add(m[i], (1.0 - beta1) * gradient[i]);
527 v[i] = beta2.mul_add(v[i], (1.0 - beta2) * gradient[i] * gradient[i]);
528
529 let m_hat = m[i] / (1.0 - beta1.powi(t as i32));
531 let v_hat = v[i] / (1.0 - beta2.powi(t as i32));
532
533 let step = learning_rate * m_hat / (v_hat.sqrt() + epsilon);
535 params[i] -= step;
536 step_sq_norm += step * step;
537
538 let bounds = objective.bounds();
540 params[i] = params[i].max(bounds[i].0).min(bounds[i].1);
541 }
542 last_step_size = step_sq_norm.sqrt();
543
544 if let Some(callback) = &self.config.progress_callback {
546 callback(iteration, value);
547 }
548 }
549
550 Ok((params, best_value, evaluations, false))
551 }
552
553 fn optimize_lbfgs(
570 &self,
571 objective: Arc<dyn ObjectiveFunction>,
572 initial_params: &[f64],
573 max_iterations: usize,
574 tolerance: f64,
575 ) -> QuantRS2Result<(Vec<f64>, f64, usize, bool)> {
576 let bounds = objective.bounds();
577 let n = initial_params.len();
578 let memory = 10.min(max_iterations.max(1));
580
581 let clamp = |v: &mut [f64]| {
582 for (vi, &(lo, hi)) in v.iter_mut().zip(bounds.iter()) {
583 *vi = vi.max(lo).min(hi);
584 }
585 };
586
587 let gradient_at = |x: &[f64]| -> QuantRS2Result<Vec<f64>> {
588 if let Some(g) = objective.gradient(x) {
589 Ok(g)
590 } else {
591 self.numerical_gradient(&*objective, x)
592 }
593 };
594
595 let mut x = initial_params.to_vec();
596 clamp(&mut x);
597 let mut evaluations = 0usize;
598 let mut value = objective.evaluate(&x);
599 evaluations += 1;
600 let mut grad = gradient_at(&x)?;
601
602 let mut best_params = x.clone();
603 let mut best_value = value;
604
605 let mut s_history: Vec<Vec<f64>> = Vec::with_capacity(memory);
606 let mut y_history: Vec<Vec<f64>> = Vec::with_capacity(memory);
607 let mut rho_history: Vec<f64> = Vec::with_capacity(memory);
608
609 for iteration in 0..max_iterations {
610 let grad_norm = dot(&grad, &grad).sqrt();
611
612 if grad_norm < tolerance {
613 self.record_iteration(&x, value, iteration, grad_norm, 0.0);
614 return Ok((best_params, best_value, evaluations, true));
615 }
616
617 let k = s_history.len();
620 let mut q = grad.clone();
621 let mut alpha_i = vec![0.0; k];
622 for i in (0..k).rev() {
623 let a = rho_history[i] * dot(&s_history[i], &q);
624 alpha_i[i] = a;
625 for j in 0..n {
626 q[j] -= a * y_history[i][j];
627 }
628 }
629 let gamma = if k > 0 {
630 let s = &s_history[k - 1];
631 let y = &y_history[k - 1];
632 dot(s, y) / dot(y, y).max(1e-12)
633 } else {
634 1.0
635 };
636 for qj in &mut q {
637 *qj *= gamma;
638 }
639 for i in 0..k {
640 let beta = rho_history[i] * dot(&y_history[i], &q);
641 for j in 0..n {
642 q[j] += s_history[i][j] * (alpha_i[i] - beta);
643 }
644 }
645 let direction: Vec<f64> = q.iter().map(|v| -v).collect();
646
647 let directional_derivative = dot(&grad, &direction);
649 let c1 = 1e-4;
650 let mut step_length = 1.0;
651 let mut new_x = x.clone();
652 let mut new_value = value;
653 let mut accepted = false;
654 for _ in 0..20 {
655 for j in 0..n {
656 new_x[j] = x[j] + step_length * direction[j];
657 }
658 clamp(&mut new_x);
659 new_value = objective.evaluate(&new_x);
660 evaluations += 1;
661 if new_value <= step_length.mul_add(c1 * directional_derivative, value) {
662 accepted = true;
663 break;
664 }
665 step_length *= 0.5;
666 }
667
668 if !accepted {
669 self.record_iteration(&x, value, iteration, grad_norm, 0.0);
673 return Ok((best_params, best_value, evaluations, true));
674 }
675
676 let step_vec: Vec<f64> = (0..n).map(|j| new_x[j] - x[j]).collect();
677 let step_size = dot(&step_vec, &step_vec).sqrt();
678 self.record_iteration(&x, value, iteration, grad_norm, step_size);
679
680 let new_grad = gradient_at(&new_x)?;
681 let y_vec: Vec<f64> = (0..n).map(|j| new_grad[j] - grad[j]).collect();
682 let sy = dot(&step_vec, &y_vec);
683 if sy > 1e-10 {
687 if s_history.len() == memory {
688 s_history.remove(0);
689 y_history.remove(0);
690 rho_history.remove(0);
691 }
692 s_history.push(step_vec);
693 y_history.push(y_vec);
694 rho_history.push(1.0 / sy);
695 }
696
697 x = new_x;
698 value = new_value;
699 grad = new_grad;
700
701 if value < best_value {
702 best_value = value;
703 best_params.clone_from(&x);
704 if let Ok(mut guard) = self.best_parameters.lock() {
705 *guard = Some(best_params.clone());
706 }
707 if let Ok(mut guard) = self.best_value.lock() {
708 *guard = best_value;
709 }
710 }
711
712 if let Some(callback) = &self.config.progress_callback {
713 callback(iteration, value);
714 }
715 }
716
717 Ok((best_params, best_value, evaluations, false))
718 }
719
720 fn optimize_nelder_mead(
722 &self,
723 objective: Arc<dyn ObjectiveFunction>,
724 initial_params: &[f64],
725 max_iterations: usize,
726 tolerance: f64,
727 ) -> QuantRS2Result<(Vec<f64>, f64, usize, bool)> {
728 let n = initial_params.len();
729 let mut simplex = Vec::new();
730 let mut evaluations = 0;
731
732 simplex.push(initial_params.to_vec());
734 for i in 0..n {
735 let mut vertex = initial_params.to_vec();
736 vertex[i] += if vertex[i] == 0.0 {
737 0.00025
738 } else {
739 vertex[i] * 0.05
740 };
741 simplex.push(vertex);
742 }
743
744 let mut values: Vec<f64> = simplex
746 .iter()
747 .map(|params| {
748 evaluations += 1;
749 objective.evaluate(params)
750 })
751 .collect();
752
753 let mut previous_best_point = simplex[0].clone();
757
758 for iteration in 0..max_iterations {
759 let mut indices: Vec<usize> = (0..simplex.len()).collect();
761 indices.sort_by(|&i, &j| {
762 values[i]
763 .partial_cmp(&values[j])
764 .unwrap_or(std::cmp::Ordering::Equal)
765 });
766
767 let best_value = values[indices[0]];
768 let worst_idx = indices[n];
769 let second_worst_idx = indices[n - 1];
770
771 let best_step_size = dot_diff(&simplex[indices[0]], &previous_best_point).sqrt();
772 previous_best_point.clone_from(&simplex[indices[0]]);
773
774 self.record_iteration(
776 &simplex[indices[0]],
777 best_value,
778 iteration,
779 0.0,
780 best_step_size,
781 );
782
783 let range = values[worst_idx] - values[indices[0]];
785 if range < tolerance {
786 return Ok((simplex[indices[0]].clone(), best_value, evaluations, true));
787 }
788
789 let mut centroid = vec![0.0; n];
791 for i in 0..n {
792 for j in 0..n {
793 centroid[j] += simplex[indices[i]][j];
794 }
795 }
796 for j in 0..n {
797 centroid[j] /= n as f64;
798 }
799
800 let alpha = 1.0;
802 let mut reflected = vec![0.0; n];
803 for j in 0..n {
804 reflected[j] = centroid[j] + alpha * (centroid[j] - simplex[worst_idx][j]);
805 }
806
807 let bounds = objective.bounds();
809 for j in 0..n {
810 reflected[j] = reflected[j].max(bounds[j].0).min(bounds[j].1);
811 }
812
813 let reflected_value = objective.evaluate(&reflected);
814 evaluations += 1;
815
816 if values[indices[0]] <= reflected_value && reflected_value < values[second_worst_idx] {
817 simplex[worst_idx] = reflected;
819 values[worst_idx] = reflected_value;
820 } else if reflected_value < values[indices[0]] {
821 let gamma = 2.0;
823 let mut expanded = vec![0.0; n];
824 for j in 0..n {
825 expanded[j] = centroid[j] + gamma * (reflected[j] - centroid[j]);
826 expanded[j] = expanded[j].max(bounds[j].0).min(bounds[j].1);
827 }
828
829 let expanded_value = objective.evaluate(&expanded);
830 evaluations += 1;
831
832 if expanded_value < reflected_value {
833 simplex[worst_idx] = expanded;
834 values[worst_idx] = expanded_value;
835 } else {
836 simplex[worst_idx] = reflected;
837 values[worst_idx] = reflected_value;
838 }
839 } else {
840 let rho = 0.5;
842 let mut contracted = vec![0.0; n];
843 for j in 0..n {
844 contracted[j] = centroid[j] + rho * (simplex[worst_idx][j] - centroid[j]);
845 contracted[j] = contracted[j].max(bounds[j].0).min(bounds[j].1);
846 }
847
848 let contracted_value = objective.evaluate(&contracted);
849 evaluations += 1;
850
851 if contracted_value < values[worst_idx] {
852 simplex[worst_idx] = contracted;
853 values[worst_idx] = contracted_value;
854 } else {
855 let sigma = 0.5;
857 for i in 1..=n {
858 for j in 0..n {
859 simplex[i][j] = simplex[indices[0]][j]
860 + sigma * (simplex[i][j] - simplex[indices[0]][j]);
861 simplex[i][j] = simplex[i][j].max(bounds[j].0).min(bounds[j].1);
862 }
863 values[i] = objective.evaluate(&simplex[i]);
864 evaluations += 1;
865 }
866 }
867 }
868
869 if let Some(callback) = &self.config.progress_callback {
871 callback(iteration, best_value);
872 }
873 }
874
875 let mut best_idx = 0;
877 let mut best_value = values[0];
878 for i in 1..values.len() {
879 if values[i] < best_value {
880 best_value = values[i];
881 best_idx = i;
882 }
883 }
884
885 Ok((simplex[best_idx].clone(), best_value, evaluations, false))
886 }
887
888 fn optimize_simulated_annealing(
890 &self,
891 objective: Arc<dyn ObjectiveFunction>,
892 initial_params: &[f64],
893 initial_temperature: f64,
894 cooling_rate: f64,
895 min_temperature: f64,
896 ) -> QuantRS2Result<(Vec<f64>, f64, usize, bool)> {
897 use scirs2_core::random::prelude::*;
898 let mut rng = thread_rng();
899
900 let mut current_params = initial_params.to_vec();
901 let mut current_value = objective.evaluate(¤t_params);
902 let mut best_params = current_params.clone();
903 let mut best_value = current_value;
904 let mut temperature = initial_temperature;
905 let mut evaluations = 1;
906
907 let bounds = objective.bounds();
908
909 for iteration in 0..self.config.max_evaluations {
910 if temperature < min_temperature {
911 break;
912 }
913
914 let mut neighbor_params = current_params.clone();
916 let mut proposed_step_sq_norm = 0.0;
917 for i in 0..neighbor_params.len() {
918 let range = bounds[i].1 - bounds[i].0;
919 let step = rng.random_range(-0.1..0.1) * range * temperature / initial_temperature;
920 neighbor_params[i] = (neighbor_params[i] + step)
921 .max(bounds[i].0)
922 .min(bounds[i].1);
923 let applied_step = neighbor_params[i] - current_params[i];
924 proposed_step_sq_norm += applied_step * applied_step;
925 }
926 let metropolis_step_size = proposed_step_sq_norm.sqrt();
927
928 let neighbor_value = objective.evaluate(&neighbor_params);
929 evaluations += 1;
930
931 let delta = neighbor_value - current_value;
933 if delta < 0.0 || rng.random::<f64>() < (-delta / temperature).exp() {
934 current_params = neighbor_params;
935 current_value = neighbor_value;
936
937 if current_value < best_value {
938 best_params.clone_from(¤t_params);
939 best_value = current_value;
940 }
941 }
942
943 self.record_iteration(
948 ¤t_params,
949 current_value,
950 iteration,
951 0.0,
952 metropolis_step_size,
953 );
954
955 temperature *= cooling_rate;
957
958 if let Some(callback) = &self.config.progress_callback {
960 callback(iteration, best_value);
961 }
962 }
963
964 Ok((
965 best_params,
966 best_value,
967 evaluations,
968 temperature < min_temperature,
969 ))
970 }
971
972 fn optimize_bayesian(
994 &self,
995 objective: Arc<dyn ObjectiveFunction>,
996 initial_params: &[f64],
997 acquisition_function: &AcquisitionFunction,
998 kernel: &KernelType,
999 num_initial_samples: usize,
1000 ) -> QuantRS2Result<(Vec<f64>, f64, usize, bool)> {
1001 use scirs2_core::random::prelude::*;
1002 let mut rng = thread_rng();
1003
1004 let bounds = objective.bounds();
1005 let n_dims = initial_params.len();
1006 let max_evaluations = self.config.max_evaluations.max(1);
1007
1008 let mut points: Vec<Vec<f64>> = Vec::new();
1009 let mut values: Vec<f64> = Vec::new();
1010 let mut evaluations = 0usize;
1011
1012 let initial_value = objective.evaluate(initial_params);
1013 points.push(initial_params.to_vec());
1014 values.push(initial_value);
1015 evaluations += 1;
1016
1017 let num_random_samples = num_initial_samples
1018 .saturating_sub(1)
1019 .min(max_evaluations.saturating_sub(evaluations));
1020 for _ in 0..num_random_samples {
1021 let candidate: Vec<f64> = bounds
1022 .iter()
1023 .map(|&(lo, hi)| lo + rng.random::<f64>() * (hi - lo))
1024 .collect();
1025 let value = objective.evaluate(&candidate);
1026 points.push(candidate);
1027 values.push(value);
1028 evaluations += 1;
1029 }
1030
1031 let mut best_idx = 0;
1032 for i in 1..values.len() {
1033 if values[i] < values[best_idx] {
1034 best_idx = i;
1035 }
1036 }
1037 let mut best_params = points[best_idx].clone();
1038 let mut best_value = values[best_idx];
1039 if let Ok(mut guard) = self.best_parameters.lock() {
1040 *guard = Some(best_params.clone());
1041 }
1042 if let Ok(mut guard) = self.best_value.lock() {
1043 *guard = best_value;
1044 }
1045
1046 const JITTER: f64 = 1e-6;
1047 let num_candidates = (4 * n_dims).max(32);
1048 let mut iteration = 0usize;
1049 let mut converged = false;
1050
1051 while evaluations < max_evaluations {
1052 let covariance = build_covariance(kernel, &points, JITTER);
1053 let Ok(chol) = cholesky(&covariance) else {
1054 break;
1059 };
1060 let y_mean = values.iter().sum::<f64>() / values.len() as f64;
1061 let y_centered: Vec<f64> = values.iter().map(|v| v - y_mean).collect();
1062 let z = forward_substitution(&chol, &y_centered);
1063 let alpha = backward_substitution_transpose(&chol, &z);
1064
1065 let mut best_candidate: Option<Vec<f64>> = None;
1066 let mut best_acquisition = f64::NEG_INFINITY;
1067 for c in 0..num_candidates {
1068 let candidate: Vec<f64> = if c < num_candidates / 2 {
1069 bounds
1070 .iter()
1071 .map(|&(lo, hi)| lo + rng.random::<f64>() * (hi - lo))
1072 .collect()
1073 } else {
1074 best_params
1075 .iter()
1076 .zip(bounds.iter())
1077 .map(|(&v, &(lo, hi))| {
1078 let scale = (hi - lo) * 0.1;
1079 (v + (rng.random::<f64>() - 0.5) * 2.0 * scale).clamp(lo, hi)
1080 })
1081 .collect()
1082 };
1083
1084 let (mean_c, std_c) =
1085 gp_posterior(kernel, &points, &alpha, &chol, &candidate, y_mean, JITTER);
1086
1087 let acquisition_score = match acquisition_function {
1088 AcquisitionFunction::Thompson => {
1089 let u1 = rng.random::<f64>().max(1e-12);
1092 let u2 = rng.random::<f64>();
1093 let z = (-2.0 * u1.ln()).sqrt() * (std::f64::consts::TAU * u2).cos();
1094 let sampled_value = mean_c + std_c * z;
1095 -sampled_value }
1097 other => acquisition_value(other, mean_c, std_c, best_value),
1098 };
1099
1100 if acquisition_score > best_acquisition {
1101 best_acquisition = acquisition_score;
1102 best_candidate = Some(candidate);
1103 }
1104 }
1105
1106 let Some(next_point) = best_candidate else {
1107 break;
1108 };
1109 let next_value = objective.evaluate(&next_point);
1110 evaluations += 1;
1111
1112 let step_size = dot_diff(&next_point, &best_params).sqrt();
1113
1114 if next_value < best_value {
1115 best_value = next_value;
1116 best_params = next_point.clone();
1117 if let Ok(mut guard) = self.best_parameters.lock() {
1118 *guard = Some(best_params.clone());
1119 }
1120 if let Ok(mut guard) = self.best_value.lock() {
1121 *guard = best_value;
1122 }
1123 }
1124
1125 points.push(next_point);
1126 values.push(next_value);
1127
1128 self.record_iteration(&best_params, best_value, iteration, 0.0, step_size);
1132
1133 if let Some(callback) = &self.config.progress_callback {
1134 callback(iteration, best_value);
1135 }
1136
1137 if best_acquisition.abs() < self.config.tolerance && step_size < self.config.tolerance {
1141 converged = true;
1142 iteration += 1;
1143 break;
1144 }
1145 iteration += 1;
1146 }
1147
1148 Ok((best_params, best_value, evaluations, converged))
1149 }
1150
1151 fn numerical_gradient(
1153 &self,
1154 objective: &dyn ObjectiveFunction,
1155 params: &[f64],
1156 ) -> QuantRS2Result<Vec<f64>> {
1157 let epsilon = 1e-8;
1158 let mut gradient = vec![0.0; params.len()];
1159
1160 for i in 0..params.len() {
1161 let mut params_plus = params.to_vec();
1162 let mut params_minus = params.to_vec();
1163
1164 params_plus[i] += epsilon;
1165 params_minus[i] -= epsilon;
1166
1167 let f_plus = objective.evaluate(¶ms_plus);
1168 let f_minus = objective.evaluate(¶ms_minus);
1169
1170 gradient[i] = (f_plus - f_minus) / (2.0 * epsilon);
1171 }
1172
1173 Ok(gradient)
1174 }
1175
1176 fn record_iteration(
1186 &self,
1187 params: &[f64],
1188 value: f64,
1189 _iteration: usize,
1190 gradient_norm: f64,
1191 step_size: f64,
1192 ) {
1193 if let Ok(mut history) = self.history.lock() {
1194 history.parameters.push(params.to_vec());
1195 history.objective_values.push(value);
1196 history.gradient_norms.push(gradient_norm);
1197 history.step_sizes.push(step_size);
1198 history.timestamps.push(std::time::Instant::now());
1199 }
1200 }
1201
1202 #[must_use]
1204 pub fn get_best_parameters(&self) -> Option<Vec<f64>> {
1205 self.best_parameters.lock().ok().and_then(|g| g.clone())
1206 }
1207
1208 #[must_use]
1210 pub fn get_best_value(&self) -> f64 {
1211 self.best_value.lock().ok().map_or(f64::INFINITY, |g| *g)
1212 }
1213
1214 pub fn build_circuit(&self, parameters: &[f64]) -> QuantRS2Result<Circuit<32>> {
1223 if parameters.len() != self.circuit_template.parameters.len() {
1224 return Err(QuantRS2Error::InvalidInput(
1225 "Parameter count mismatch".to_string(),
1226 ));
1227 }
1228
1229 let mut circuit = Circuit::<32>::new();
1230 for gate_template in &self.circuit_template.structure {
1231 apply_template_gate(&mut circuit, gate_template, parameters)?;
1232 }
1233
1234 Ok(circuit)
1235 }
1236}
1237
1238fn dot(a: &[f64], b: &[f64]) -> f64 {
1240 a.iter().zip(b).map(|(x, y)| x * y).sum()
1241}
1242
1243fn dot_diff(a: &[f64], b: &[f64]) -> f64 {
1245 a.iter().zip(b).map(|(x, y)| (x - y) * (x - y)).sum()
1246}
1247
1248fn kernel_value(kernel: &KernelType, a: &[f64], b: &[f64]) -> f64 {
1250 match kernel {
1251 KernelType::RBF { length_scale } => {
1252 let denom = (2.0 * length_scale * length_scale).max(1e-12);
1253 (-dot_diff(a, b) / denom).exp()
1254 }
1255 KernelType::Matern { nu, length_scale } => {
1256 matern_kernel(*nu, *length_scale, dot_diff(a, b).sqrt())
1257 }
1258 KernelType::Linear { variance } => variance * dot(a, b),
1259 KernelType::Periodic {
1260 period,
1261 length_scale,
1262 } => {
1263 let dist = dot_diff(a, b).sqrt();
1264 let s = (std::f64::consts::PI * dist / period.max(1e-12)).sin();
1265 (-2.0 * s * s / (length_scale * length_scale).max(1e-12)).exp()
1266 }
1267 }
1268}
1269
1270fn matern_kernel(nu: f64, length_scale: f64, dist: f64) -> f64 {
1278 let length_scale = length_scale.max(1e-12);
1279 let r = dist / length_scale;
1280 if nu <= 1.0 {
1281 (-r).exp()
1282 } else if nu <= 2.0 {
1283 let root3 = 3f64.sqrt();
1284 (1.0 + root3 * r) * (-root3 * r).exp()
1285 } else {
1286 let root5 = 5f64.sqrt();
1287 (1.0 + root5 * r + 5.0 * r * r / 3.0) * (-root5 * r).exp()
1288 }
1289}
1290
1291fn build_covariance(kernel: &KernelType, points: &[Vec<f64>], jitter: f64) -> Vec<Vec<f64>> {
1297 let n = points.len();
1298 let mut cov = vec![vec![0.0; n]; n];
1299 for i in 0..n {
1300 for j in i..n {
1301 let value =
1302 kernel_value(kernel, &points[i], &points[j]) + if i == j { jitter } else { 0.0 };
1303 cov[i][j] = value;
1304 cov[j][i] = value;
1305 }
1306 }
1307 cov
1308}
1309
1310fn cholesky(matrix: &[Vec<f64>]) -> QuantRS2Result<Vec<Vec<f64>>> {
1313 let n = matrix.len();
1314 let mut l = vec![vec![0.0; n]; n];
1315 for i in 0..n {
1316 for j in 0..=i {
1317 let mut sum = matrix[i][j];
1318 for k in 0..j {
1319 sum -= l[i][k] * l[j][k];
1320 }
1321 if i == j {
1322 if sum <= 0.0 {
1323 return Err(QuantRS2Error::ComputationError(
1324 "GP covariance matrix is not positive definite".to_string(),
1325 ));
1326 }
1327 l[i][j] = sum.sqrt();
1328 } else {
1329 l[i][j] = sum / l[j][j];
1330 }
1331 }
1332 }
1333 Ok(l)
1334}
1335
1336fn forward_substitution(l: &[Vec<f64>], b: &[f64]) -> Vec<f64> {
1338 let n = l.len();
1339 let mut y = vec![0.0; n];
1340 for i in 0..n {
1341 let mut sum = b[i];
1342 for k in 0..i {
1343 sum -= l[i][k] * y[k];
1344 }
1345 y[i] = sum / l[i][i];
1346 }
1347 y
1348}
1349
1350fn backward_substitution_transpose(l: &[Vec<f64>], y: &[f64]) -> Vec<f64> {
1353 let n = l.len();
1354 let mut x = vec![0.0; n];
1355 for i in (0..n).rev() {
1356 let mut sum = y[i];
1357 for k in (i + 1)..n {
1358 sum -= l[k][i] * x[k];
1359 }
1360 x[i] = sum / l[i][i];
1361 }
1362 x
1363}
1364
1365fn gp_posterior(
1369 kernel: &KernelType,
1370 points: &[Vec<f64>],
1371 alpha: &[f64],
1372 l: &[Vec<f64>],
1373 x: &[f64],
1374 y_mean: f64,
1375 jitter: f64,
1376) -> (f64, f64) {
1377 let k_star: Vec<f64> = points.iter().map(|p| kernel_value(kernel, p, x)).collect();
1378 let mean = y_mean + dot(&k_star, alpha);
1379 let v = forward_substitution(l, &k_star);
1380 let k_xx = kernel_value(kernel, x, x) + jitter;
1381 let variance = (k_xx - dot(&v, &v)).max(0.0);
1382 (mean, variance.sqrt())
1383}
1384
1385fn normal_cdf(z: f64) -> f64 {
1388 0.5 * (1.0 + erf(z / std::f64::consts::SQRT_2))
1389}
1390
1391fn normal_pdf(z: f64) -> f64 {
1393 (-(z * z) / 2.0).exp() / (2.0 * std::f64::consts::PI).sqrt()
1394}
1395
1396fn erf(x: f64) -> f64 {
1398 let sign = if x < 0.0 { -1.0 } else { 1.0 };
1399 let x = x.abs();
1400 let a1 = 0.254_829_592;
1401 let a2 = -0.284_496_736;
1402 let a3 = 1.421_413_741;
1403 let a4 = -1.453_152_027;
1404 let a5 = 1.061_405_429;
1405 let p: f64 = 0.327_591_1;
1406 let t = 1.0 / p.mul_add(x, 1.0);
1407 let y = 1.0 - ((((a5 * t + a4) * t + a3) * t + a2) * t + a1) * t * (-x * x).exp();
1408 sign * y
1409}
1410
1411fn acquisition_value(
1417 acquisition: &AcquisitionFunction,
1418 mean: f64,
1419 std: f64,
1420 best_so_far: f64,
1421) -> f64 {
1422 let std = std.max(1e-9);
1423 match acquisition {
1424 AcquisitionFunction::ExpectedImprovement => {
1425 let improvement = best_so_far - mean;
1426 let z = improvement / std;
1427 improvement * normal_cdf(z) + std * normal_pdf(z)
1428 }
1429 AcquisitionFunction::ProbabilityOfImprovement => {
1430 let z = (best_so_far - mean) / std;
1431 normal_cdf(z)
1432 }
1433 AcquisitionFunction::UpperConfidenceBound { kappa } => {
1434 kappa * std - mean
1436 }
1437 AcquisitionFunction::Thompson => {
1438 unreachable!("Thompson sampling is handled by the caller, not acquisition_value")
1439 }
1440 }
1441}
1442
1443fn template_gate_angle(
1448 gate: &ParameterizedGate,
1449 parameters: &[f64],
1450 slot: usize,
1451) -> QuantRS2Result<f64> {
1452 if let Some(&idx) = gate.parameter_indices.get(slot) {
1453 parameters.get(idx).copied().ok_or_else(|| {
1454 QuantRS2Error::InvalidInput(format!(
1455 "Gate '{}' references parameter index {idx} which is out of range",
1456 gate.gate_name
1457 ))
1458 })
1459 } else if let Some(&fixed) = gate
1460 .fixed_parameters
1461 .get(slot - gate.parameter_indices.len())
1462 {
1463 Ok(fixed)
1464 } else {
1465 Err(QuantRS2Error::InvalidInput(format!(
1466 "Gate '{}' is missing angle #{slot}",
1467 gate.gate_name
1468 )))
1469 }
1470}
1471
1472fn apply_template_gate<const N: usize>(
1477 circuit: &mut Circuit<N>,
1478 gate: &ParameterizedGate,
1479 parameters: &[f64],
1480) -> QuantRS2Result<()> {
1481 let q = |i: usize| -> QuantRS2Result<QubitId> {
1482 gate.qubits
1483 .get(i)
1484 .map(|&qb| QubitId(qb as u32))
1485 .ok_or_else(|| {
1486 QuantRS2Error::InvalidInput(format!(
1487 "Gate '{}' expects at least {} qubit(s)",
1488 gate.gate_name,
1489 i + 1
1490 ))
1491 })
1492 };
1493
1494 match gate.gate_name.as_str() {
1495 "H" | "h" => {
1496 circuit.h(q(0)?)?;
1497 }
1498 "X" | "x" => {
1499 circuit.x(q(0)?)?;
1500 }
1501 "Y" | "y" => {
1502 circuit.y(q(0)?)?;
1503 }
1504 "Z" | "z" => {
1505 circuit.z(q(0)?)?;
1506 }
1507 "S" | "s" => {
1508 circuit.s(q(0)?)?;
1509 }
1510 "T" | "t" => {
1511 circuit.t(q(0)?)?;
1512 }
1513 "RX" | "rx" => {
1514 circuit.rx(q(0)?, template_gate_angle(gate, parameters, 0)?)?;
1515 }
1516 "RY" | "ry" => {
1517 circuit.ry(q(0)?, template_gate_angle(gate, parameters, 0)?)?;
1518 }
1519 "RZ" | "rz" => {
1520 circuit.rz(q(0)?, template_gate_angle(gate, parameters, 0)?)?;
1521 }
1522 "P" | "p" | "PHASE" => {
1523 circuit.p(q(0)?, template_gate_angle(gate, parameters, 0)?)?;
1524 }
1525 "CNOT" | "cnot" | "CX" | "cx" => {
1526 circuit.cnot(q(0)?, q(1)?)?;
1527 }
1528 "CZ" | "cz" => {
1529 circuit.cz(q(0)?, q(1)?)?;
1530 }
1531 other => {
1532 return Err(QuantRS2Error::UnsupportedOperation(format!(
1533 "Template gate '{other}' is not supported by the circuit builder"
1534 )));
1535 }
1536 }
1537 Ok(())
1538}
1539
1540pub struct VQEObjective {
1542 hamiltonian: SparseMatrix,
1544 circuit_template: CircuitTemplate,
1546 bounds: Vec<(f64, f64)>,
1548}
1549
1550impl VQEObjective {
1551 #[must_use]
1553 pub fn new(hamiltonian: SparseMatrix, circuit_template: CircuitTemplate) -> Self {
1554 let bounds = circuit_template
1555 .parameters
1556 .iter()
1557 .map(|p| (p.lower_bound, p.upper_bound))
1558 .collect();
1559
1560 Self {
1561 hamiltonian,
1562 circuit_template,
1563 bounds,
1564 }
1565 }
1566}
1567
1568impl VQEObjective {
1569 fn try_evaluate(&self, parameters: &[f64]) -> QuantRS2Result<f64> {
1573 let num_qubits = self.circuit_template.num_qubits;
1574 let state = statevector::simulate_template(
1575 num_qubits,
1576 &self.circuit_template.structure,
1577 parameters,
1578 )?;
1579
1580 let dim = 1usize << num_qubits;
1581 if self.hamiltonian.shape.0 != dim || self.hamiltonian.shape.1 != dim {
1582 return Err(QuantRS2Error::InvalidInput(format!(
1583 "Hamiltonian is {}x{} but a {}-qubit circuit needs {dim}x{dim}",
1584 self.hamiltonian.shape.0, self.hamiltonian.shape.1, num_qubits
1585 )));
1586 }
1587
1588 let expectation = statevector::sparse_expectation(self.hamiltonian.triplets(), &state);
1589 if expectation.im.abs() > 1e-6 {
1590 return Err(QuantRS2Error::ComputationError(format!(
1591 "VQE energy has non-negligible imaginary part ({:.3e}); Hamiltonian is not Hermitian",
1592 expectation.im
1593 )));
1594 }
1595 Ok(expectation.re)
1596 }
1597}
1598
1599impl ObjectiveFunction for VQEObjective {
1600 fn evaluate(&self, parameters: &[f64]) -> f64 {
1608 self.try_evaluate(parameters).unwrap_or(f64::INFINITY)
1609 }
1610
1611 fn bounds(&self) -> Vec<(f64, f64)> {
1612 self.bounds.clone()
1613 }
1614
1615 fn name(&self) -> &'static str {
1616 "VQE"
1617 }
1618}
1619
1620pub struct QAOAObjective {
1622 problem_hamiltonian: SparseMatrix,
1624 mixer_hamiltonian: SparseMatrix,
1626 num_layers: usize,
1628 bounds: Vec<(f64, f64)>,
1630}
1631
1632impl QAOAObjective {
1633 #[must_use]
1635 pub fn new(
1636 problem_hamiltonian: SparseMatrix,
1637 mixer_hamiltonian: SparseMatrix,
1638 num_layers: usize,
1639 ) -> Self {
1640 let bounds = vec![(0.0, 2.0 * std::f64::consts::PI); 2 * num_layers];
1642
1643 Self {
1644 problem_hamiltonian,
1645 mixer_hamiltonian,
1646 num_layers,
1647 bounds,
1648 }
1649 }
1650}
1651
1652impl QAOAObjective {
1653 fn try_evaluate(&self, parameters: &[f64]) -> QuantRS2Result<f64> {
1660 if parameters.len() != 2 * self.num_layers {
1661 return Err(QuantRS2Error::InvalidInput(format!(
1662 "QAOA with {} layers needs {} parameters, got {}",
1663 self.num_layers,
1664 2 * self.num_layers,
1665 parameters.len()
1666 )));
1667 }
1668
1669 let dim = self.problem_hamiltonian.shape.0;
1670 if !dim.is_power_of_two() || self.problem_hamiltonian.shape.1 != dim {
1671 return Err(QuantRS2Error::InvalidInput(
1672 "QAOA problem Hamiltonian must be a square 2^n matrix".to_string(),
1673 ));
1674 }
1675 if self.mixer_hamiltonian.shape != self.problem_hamiltonian.shape {
1676 return Err(QuantRS2Error::InvalidInput(
1677 "QAOA mixer and problem Hamiltonians must have the same shape".to_string(),
1678 ));
1679 }
1680 let num_qubits = dim.trailing_zeros() as usize;
1681
1682 let problem_dense =
1685 statevector::dense_from_triplets(self.problem_hamiltonian.triplets(), dim);
1686 let mixer_dense = statevector::dense_from_triplets(self.mixer_hamiltonian.triplets(), dim);
1687
1688 let amp = Complex64::new(1.0 / (dim as f64).sqrt(), 0.0);
1690 let mut state = vec![amp; dim];
1691
1692 for layer in 0..self.num_layers {
1693 let gamma = parameters[2 * layer];
1694 let beta = parameters[2 * layer + 1];
1695 let u_problem =
1697 statevector::expm_scaled(&problem_dense, dim, Complex64::new(0.0, -gamma))?;
1698 state = statevector::matvec(&u_problem, dim, &state);
1699 let u_mixer = statevector::expm_scaled(&mixer_dense, dim, Complex64::new(0.0, -beta))?;
1701 state = statevector::matvec(&u_mixer, dim, &state);
1702 }
1703
1704 let _ = num_qubits; let expectation =
1706 statevector::sparse_expectation(self.problem_hamiltonian.triplets(), &state);
1707 if expectation.im.abs() > 1e-6 {
1708 return Err(QuantRS2Error::ComputationError(format!(
1709 "QAOA energy has non-negligible imaginary part ({:.3e}); problem Hamiltonian is not Hermitian",
1710 expectation.im
1711 )));
1712 }
1713 Ok(expectation.re)
1714 }
1715}
1716
1717impl ObjectiveFunction for QAOAObjective {
1718 fn evaluate(&self, parameters: &[f64]) -> f64 {
1723 self.try_evaluate(parameters).unwrap_or(f64::INFINITY)
1724 }
1725
1726 fn bounds(&self) -> Vec<(f64, f64)> {
1727 self.bounds.clone()
1728 }
1729
1730 fn name(&self) -> &'static str {
1731 "QAOA"
1732 }
1733}
1734
1735impl Default for OptimizationConfig {
1736 fn default() -> Self {
1737 Self {
1738 algorithm: OptimizationAlgorithm::Adam {
1739 learning_rate: 0.01,
1740 beta1: 0.9,
1741 beta2: 0.999,
1742 epsilon: 1e-8,
1743 },
1744 max_evaluations: 1000,
1745 tolerance: 1e-6,
1746 seed: None,
1747 parallel: false,
1748 num_threads: None,
1749 progress_callback: None,
1750 early_stopping: None,
1751 }
1752 }
1753}
1754
1755mod statevector {
1764 use super::{ParameterizedGate, QuantRS2Error, QuantRS2Result};
1765 use quantrs2_core::gate::{
1766 single::{Phase, RotationX, RotationY, RotationZ, T},
1767 GateOp,
1768 };
1769 use quantrs2_core::qubit::QubitId;
1770 use scirs2_core::Complex64;
1771
1772 pub fn simulate_template(
1774 num_qubits: usize,
1775 structure: &[ParameterizedGate],
1776 parameters: &[f64],
1777 ) -> QuantRS2Result<Vec<Complex64>> {
1778 let dim = 1usize << num_qubits;
1779 let mut state = vec![Complex64::new(0.0, 0.0); dim];
1780 state[0] = Complex64::new(1.0, 0.0);
1781
1782 for gate in structure {
1783 let boxed = super::statevector::boxed_template_gate(gate, parameters)?;
1784 apply_gate(&mut state, num_qubits, boxed.as_ref())?;
1785 }
1786 Ok(state)
1787 }
1788
1789 fn boxed_template_gate(
1797 gate: &ParameterizedGate,
1798 parameters: &[f64],
1799 ) -> QuantRS2Result<Box<dyn GateOp>> {
1800 use quantrs2_core::gate::multi::{CNOT, CZ};
1801 use quantrs2_core::gate::single::{Hadamard, PauliX, PauliY, PauliZ};
1802
1803 let target = |i: usize| -> QuantRS2Result<QubitId> {
1804 gate.qubits
1805 .get(i)
1806 .map(|&q| QubitId(q as u32))
1807 .ok_or_else(|| {
1808 QuantRS2Error::InvalidInput(format!(
1809 "Gate '{}' expects at least {} qubit(s)",
1810 gate.gate_name,
1811 i + 1
1812 ))
1813 })
1814 };
1815 let angle = |slot: usize| super::template_gate_angle(gate, parameters, slot);
1816
1817 let boxed: Box<dyn GateOp> = match gate.gate_name.as_str() {
1818 "H" | "h" => Box::new(Hadamard { target: target(0)? }),
1819 "X" | "x" => Box::new(PauliX { target: target(0)? }),
1820 "Y" | "y" => Box::new(PauliY { target: target(0)? }),
1821 "Z" | "z" => Box::new(PauliZ { target: target(0)? }),
1822 "S" | "s" => Box::new(Phase { target: target(0)? }),
1823 "T" | "t" => Box::new(T { target: target(0)? }),
1824 "RX" | "rx" => Box::new(RotationX {
1825 target: target(0)?,
1826 theta: angle(0)?,
1827 }),
1828 "RY" | "ry" => Box::new(RotationY {
1829 target: target(0)?,
1830 theta: angle(0)?,
1831 }),
1832 "RZ" | "rz" => Box::new(RotationZ {
1833 target: target(0)?,
1834 theta: angle(0)?,
1835 }),
1836 "CNOT" | "cnot" | "CX" | "cx" => Box::new(CNOT {
1837 control: target(0)?,
1838 target: target(1)?,
1839 }),
1840 "CZ" | "cz" => Box::new(CZ {
1841 control: target(0)?,
1842 target: target(1)?,
1843 }),
1844 other => {
1845 return Err(QuantRS2Error::UnsupportedOperation(format!(
1846 "Template gate '{other}' is not supported by the state-vector engine"
1847 )))
1848 }
1849 };
1850 Ok(boxed)
1851 }
1852
1853 pub fn apply_gate(
1856 state: &mut [Complex64],
1857 num_qubits: usize,
1858 gate: &dyn GateOp,
1859 ) -> QuantRS2Result<()> {
1860 let targets: Vec<usize> = gate.qubits().iter().map(|q| q.id() as usize).collect();
1861 let k = targets.len();
1862 if k == 0 {
1863 return Ok(());
1864 }
1865 for &t in &targets {
1866 if t >= num_qubits {
1867 return Err(QuantRS2Error::InvalidInput(format!(
1868 "Gate '{}' acts on qubit {t} but only {num_qubits} qubits are available",
1869 gate.name()
1870 )));
1871 }
1872 }
1873 let matrix = gate.matrix()?;
1874 let side = 1usize << k;
1875 if matrix.len() != side * side {
1876 return Err(QuantRS2Error::InvalidInput(format!(
1877 "Gate '{}' returned {} matrix elements, expected {}",
1878 gate.name(),
1879 matrix.len(),
1880 side * side
1881 )));
1882 }
1883
1884 let bit_masks: Vec<usize> = (0..k).map(|p| 1usize << targets[k - 1 - p]).collect();
1886 let mut fixed_mask = 0usize;
1887 for &m in &bit_masks {
1888 fixed_mask |= m;
1889 }
1890 let dim = state.len();
1891 let mut visited = vec![false; dim];
1892 let mut amplitudes = vec![Complex64::new(0.0, 0.0); side];
1893 let mut indices = vec![0usize; side];
1894
1895 for base in 0..dim {
1896 if visited[base] || (base & fixed_mask) != 0 {
1897 continue;
1898 }
1899 for (local, slot) in indices.iter_mut().enumerate() {
1900 let mut idx = base;
1901 for (bit, &mask) in bit_masks.iter().enumerate() {
1902 if (local >> bit) & 1 == 1 {
1903 idx |= mask;
1904 }
1905 }
1906 *slot = idx;
1907 amplitudes[local] = state[idx];
1908 visited[idx] = true;
1909 }
1910 for r in 0..side {
1911 let mut acc = Complex64::new(0.0, 0.0);
1912 let row = r * side;
1913 for (c, amp) in amplitudes.iter().enumerate() {
1914 acc += matrix[row + c] * amp;
1915 }
1916 state[indices[r]] = acc;
1917 }
1918 }
1919 Ok(())
1920 }
1921
1922 pub fn sparse_expectation(
1924 triplets: &[(usize, usize, Complex64)],
1925 state: &[Complex64],
1926 ) -> Complex64 {
1927 let mut acc = Complex64::new(0.0, 0.0);
1928 for &(row, col, value) in triplets {
1929 if row < state.len() && col < state.len() {
1930 acc += state[row].conj() * value * state[col];
1931 }
1932 }
1933 acc
1934 }
1935
1936 pub fn dense_from_triplets(
1938 triplets: &[(usize, usize, Complex64)],
1939 dim: usize,
1940 ) -> Vec<Complex64> {
1941 let mut dense = vec![Complex64::new(0.0, 0.0); dim * dim];
1942 for &(row, col, value) in triplets {
1943 if row < dim && col < dim {
1944 dense[row * dim + col] += value;
1945 }
1946 }
1947 dense
1948 }
1949
1950 pub fn matvec(matrix: &[Complex64], dim: usize, x: &[Complex64]) -> Vec<Complex64> {
1952 let mut y = vec![Complex64::new(0.0, 0.0); dim];
1953 for (row, y_row) in y.iter_mut().enumerate() {
1954 let base = row * dim;
1955 let mut acc = Complex64::new(0.0, 0.0);
1956 for (col, x_col) in x.iter().enumerate() {
1957 acc += matrix[base + col] * x_col;
1958 }
1959 *y_row = acc;
1960 }
1961 y
1962 }
1963
1964 pub fn expm_scaled(
1971 h: &[Complex64],
1972 dim: usize,
1973 scale: Complex64,
1974 ) -> QuantRS2Result<Vec<Complex64>> {
1975 let a: Vec<Complex64> = h.iter().map(|&v| v * scale).collect();
1977
1978 let norm = matrix_inf_norm(&a, dim);
1980 let s = if norm <= 0.5 {
1981 0u32
1982 } else {
1983 (norm.log2().ceil().max(0.0) as u32) + 1
1984 };
1985 let scaling = Complex64::new(f64::from(1u32 << s).recip(), 0.0);
1986 let a_scaled: Vec<Complex64> = a.iter().map(|&v| v * scaling).collect();
1987
1988 let mut result = identity(dim);
1990 let mut term = identity(dim);
1991 for k in 1..=18u32 {
1992 term = matmul(&term, &a_scaled, dim);
1993 let inv_factorial = Complex64::new(1.0_f64 / factorial(k), 0.0);
1994 for (r, t) in result.iter_mut().zip(term.iter()) {
1995 *r += *t * inv_factorial;
1996 }
1997 }
1998
1999 for _ in 0..s {
2001 result = matmul(&result, &result, dim);
2002 }
2003 Ok(result)
2004 }
2005
2006 fn factorial(n: u32) -> f64 {
2007 (1..=n).fold(1.0_f64, |acc, x| acc * f64::from(x))
2008 }
2009
2010 fn identity(dim: usize) -> Vec<Complex64> {
2011 let mut m = vec![Complex64::new(0.0, 0.0); dim * dim];
2012 for i in 0..dim {
2013 m[i * dim + i] = Complex64::new(1.0, 0.0);
2014 }
2015 m
2016 }
2017
2018 fn matmul(a: &[Complex64], b: &[Complex64], dim: usize) -> Vec<Complex64> {
2019 let mut c = vec![Complex64::new(0.0, 0.0); dim * dim];
2020 for i in 0..dim {
2021 for k in 0..dim {
2022 let a_ik = a[i * dim + k];
2023 if a_ik.norm_sqr() == 0.0 {
2024 continue;
2025 }
2026 let brow = k * dim;
2027 let crow = i * dim;
2028 for j in 0..dim {
2029 c[crow + j] += a_ik * b[brow + j];
2030 }
2031 }
2032 }
2033 c
2034 }
2035
2036 fn matrix_inf_norm(m: &[Complex64], dim: usize) -> f64 {
2037 let mut max_row = 0.0_f64;
2038 for i in 0..dim {
2039 let base = i * dim;
2040 let row_sum: f64 = (0..dim).map(|j| m[base + j].norm()).sum();
2041 if row_sum > max_row {
2042 max_row = row_sum;
2043 }
2044 }
2045 max_row
2046 }
2047}
2048
2049#[cfg(test)]
2050mod tests {
2051 use super::*;
2052
2053 #[test]
2054 fn test_optimization_config_creation() {
2055 let config = OptimizationConfig::default();
2056 assert_eq!(config.max_evaluations, 1000);
2057 assert_eq!(config.tolerance, 1e-6);
2058 }
2059
2060 #[test]
2061 fn test_vqe_objective() {
2062 let hamiltonian = SparseMatrix::identity(4);
2064 let template = CircuitTemplate {
2065 structure: vec![ParameterizedGate {
2066 gate_name: "RY".to_string(),
2067 qubits: vec![0],
2068 parameter_indices: vec![0],
2069 fixed_parameters: Vec::new(),
2070 }],
2071 parameters: vec![Parameter {
2072 name: "theta".to_string(),
2073 lower_bound: 0.0,
2074 upper_bound: 2.0 * std::f64::consts::PI,
2075 initial_value: 0.5,
2076 discrete: false,
2077 }],
2078 num_qubits: 2,
2079 };
2080
2081 let objective = VQEObjective::new(hamiltonian, template);
2082 let value = objective.evaluate(&[0.5]);
2083 assert!((value - 1.0).abs() < 1e-9, "⟨I⟩ must equal 1, got {value}");
2084 }
2085
2086 #[test]
2089 fn test_vqe_objective_matches_cos() {
2090 use std::f64::consts::PI;
2091 let mut hamiltonian = SparseMatrix::zeros(2, 2);
2093 hamiltonian.insert(0, 0, Complex64::new(1.0, 0.0));
2094 hamiltonian.insert(1, 1, Complex64::new(-1.0, 0.0));
2095
2096 let template = CircuitTemplate {
2097 structure: vec![ParameterizedGate {
2098 gate_name: "RY".to_string(),
2099 qubits: vec![0],
2100 parameter_indices: vec![0],
2101 fixed_parameters: Vec::new(),
2102 }],
2103 parameters: vec![Parameter {
2104 name: "theta".to_string(),
2105 lower_bound: 0.0,
2106 upper_bound: 2.0 * PI,
2107 initial_value: 0.0,
2108 discrete: false,
2109 }],
2110 num_qubits: 1,
2111 };
2112 let objective = VQEObjective::new(hamiltonian, template);
2113
2114 for &theta in &[0.0, PI / 4.0, PI / 2.0, PI, 3.0 * PI / 2.0] {
2115 let value = objective.evaluate(&[theta]);
2116 assert!(
2117 (value - theta.cos()).abs() < 1e-9,
2118 "θ={theta}: got {value}, expected {}",
2119 theta.cos()
2120 );
2121 }
2122 }
2123
2124 #[test]
2125 fn test_qaoa_objective() {
2126 let problem_h = SparseMatrix::identity(4);
2128 let mixer_h = SparseMatrix::identity(4);
2129
2130 let objective = QAOAObjective::new(problem_h, mixer_h, 2);
2131 assert_eq!(objective.bounds().len(), 4); let value = objective.evaluate(&[0.5, 1.0, 1.5, 2.0]);
2134 assert!(
2135 (value - 1.0).abs() < 1e-9,
2136 "⟨I⟩ for QAOA state must equal 1, got {value}"
2137 );
2138 }
2139
2140 #[test]
2145 fn test_qaoa_objective_zero_angles_is_diagonal_mean() {
2146 let mut problem_h = SparseMatrix::zeros(4, 4);
2147 problem_h.insert(0, 0, Complex64::new(0.0, 0.0));
2148 problem_h.insert(1, 1, Complex64::new(1.0, 0.0));
2149 problem_h.insert(2, 2, Complex64::new(1.0, 0.0));
2150 problem_h.insert(3, 3, Complex64::new(2.0, 0.0));
2151 let mixer_h = SparseMatrix::identity(4);
2152
2153 let objective = QAOAObjective::new(problem_h, mixer_h, 1);
2154 let value = objective.evaluate(&[0.0, 0.0]);
2156 assert!(
2157 (value - 1.0).abs() < 1e-9,
2158 "QAOA zero-angle energy should be diagonal mean 1, got {value}"
2159 );
2160 }
2161
2162 #[test]
2165 fn test_expm_matches_rx_rotation() {
2166 use std::f64::consts::PI;
2167 let x = vec![
2169 Complex64::new(0.0, 0.0),
2170 Complex64::new(1.0, 0.0),
2171 Complex64::new(1.0, 0.0),
2172 Complex64::new(0.0, 0.0),
2173 ];
2174 let theta = PI / 3.0;
2175 let u = statevector::expm_scaled(&x, 2, Complex64::new(0.0, -theta)).expect("expm");
2177 let c = theta.cos();
2178 let s = theta.sin();
2179 let expected = [
2180 Complex64::new(c, 0.0),
2181 Complex64::new(0.0, -s),
2182 Complex64::new(0.0, -s),
2183 Complex64::new(c, 0.0),
2184 ];
2185 for (got, want) in u.iter().zip(expected.iter()) {
2186 assert!((got - want).norm() < 1e-10, "expm element {got} vs {want}");
2187 }
2188 }
2189
2190 #[test]
2191 fn test_build_circuit_emits_gates() {
2192 let template = CircuitTemplate {
2193 structure: vec![
2194 ParameterizedGate {
2195 gate_name: "RY".to_string(),
2196 qubits: vec![0],
2197 parameter_indices: vec![0],
2198 fixed_parameters: Vec::new(),
2199 },
2200 ParameterizedGate {
2201 gate_name: "CNOT".to_string(),
2202 qubits: vec![0, 1],
2203 parameter_indices: Vec::new(),
2204 fixed_parameters: Vec::new(),
2205 },
2206 ],
2207 parameters: vec![Parameter {
2208 name: "theta".to_string(),
2209 lower_bound: 0.0,
2210 upper_bound: 2.0 * std::f64::consts::PI,
2211 initial_value: 0.3,
2212 discrete: false,
2213 }],
2214 num_qubits: 2,
2215 };
2216 let config = OptimizationConfig::default();
2217 let optimizer = QuantumCircuitOptimizer::new(template, config);
2218 let circuit = optimizer.build_circuit(&[0.3]).expect("build");
2219 assert_eq!(circuit.gates().len(), 2);
2221 }
2222
2223 #[test]
2224 fn test_circuit_template() {
2225 let template = CircuitTemplate {
2226 structure: vec![ParameterizedGate {
2227 gate_name: "RY".to_string(),
2228 qubits: vec![0],
2229 parameter_indices: vec![0],
2230 fixed_parameters: Vec::new(),
2231 }],
2232 parameters: vec![Parameter {
2233 name: "theta".to_string(),
2234 lower_bound: 0.0,
2235 upper_bound: 2.0 * std::f64::consts::PI,
2236 initial_value: 0.0,
2237 discrete: false,
2238 }],
2239 num_qubits: 1,
2240 };
2241
2242 assert_eq!(template.parameters.len(), 1);
2243 assert_eq!(template.structure.len(), 1);
2244 }
2245
2246 struct TestObjective;
2247
2248 impl ObjectiveFunction for TestObjective {
2249 fn evaluate(&self, parameters: &[f64]) -> f64 {
2250 parameters.iter().map(|x| (x - 1.0).powi(2)).sum()
2251 }
2252
2253 fn bounds(&self) -> Vec<(f64, f64)> {
2254 vec![(-5.0, 5.0); 2]
2255 }
2256
2257 fn name(&self) -> &'static str {
2258 "test"
2259 }
2260 }
2261
2262 #[test]
2263 fn test_optimizer_creation() {
2264 let template = CircuitTemplate {
2265 structure: Vec::new(),
2266 parameters: vec![
2267 Parameter {
2268 name: "x1".to_string(),
2269 lower_bound: -5.0,
2270 upper_bound: 5.0,
2271 initial_value: 0.0,
2272 discrete: false,
2273 },
2274 Parameter {
2275 name: "x2".to_string(),
2276 lower_bound: -5.0,
2277 upper_bound: 5.0,
2278 initial_value: 0.0,
2279 discrete: false,
2280 },
2281 ],
2282 num_qubits: 1,
2283 };
2284
2285 let config = OptimizationConfig::default();
2286 let optimizer = QuantumCircuitOptimizer::new(template, config);
2287
2288 assert_eq!(optimizer.circuit_template.parameters.len(), 2);
2289 }
2290}