| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| use crate::{hamiltonian::PauliHamiltonian, AlgorithmError, AlgorithmResult}; |
| use num_complex::Complex64; |
| use std::f64::consts::PI; |
|
|
| |
| #[derive(Debug, Clone)] |
| pub struct ParametrizedCircuit { |
| |
| pub n_qubits: usize, |
|
|
| |
| pub params: Vec<f64>, |
|
|
| |
| pub depth: usize, |
| } |
|
|
| impl ParametrizedCircuit { |
| |
| pub fn simple_ansatz(n_qubits: usize, depth: usize) -> Self { |
| |
| let n_params = n_qubits * depth; |
| let params = vec![0.0; n_params]; |
|
|
| ParametrizedCircuit { |
| n_qubits, |
| params, |
| depth, |
| } |
| } |
|
|
| |
| pub fn set_params(&mut self, params: Vec<f64>) -> AlgorithmResult<()> { |
| if params.len() != self.params.len() { |
| return Err(AlgorithmError::InvalidParameters(format!( |
| "Expected {} parameters, got {}", |
| self.params.len(), |
| params.len() |
| ))); |
| } |
| self.params = params; |
| Ok(()) |
| } |
|
|
| |
| pub fn n_params(&self) -> usize { |
| self.params.len() |
| } |
|
|
| |
| pub fn gradient(&self, shift: f64, _energy_fn: impl Fn(&[f64]) -> f64) -> Vec<f64> { |
| let mut grad = vec![0.0; self.n_params()]; |
|
|
| for i in 0..self.n_params() { |
| let mut params_plus = self.params.clone(); |
| let mut params_minus = self.params.clone(); |
|
|
| params_plus[i] += shift; |
| params_minus[i] -= shift; |
|
|
| let e_plus = _energy_fn(¶ms_plus); |
| let e_minus = _energy_fn(¶ms_minus); |
|
|
| grad[i] = (e_plus - e_minus) / (2.0 * shift); |
| } |
|
|
| grad |
| } |
| } |
|
|
| |
| #[derive(Debug, Clone)] |
| pub struct EnergyEvaluator { |
| |
| pub energy_history: Vec<f64>, |
|
|
| |
| pub param_history: Vec<Vec<f64>>, |
|
|
| |
| pub gradient_history: Vec<f64>, |
|
|
| |
| pub best_energy: f64, |
|
|
| |
| pub iterations: usize, |
| } |
|
|
| impl EnergyEvaluator { |
| |
| pub fn new() -> Self { |
| EnergyEvaluator { |
| energy_history: Vec::new(), |
| param_history: Vec::new(), |
| gradient_history: Vec::new(), |
| best_energy: f64::INFINITY, |
| iterations: 0, |
| } |
| } |
|
|
| |
| pub fn record( |
| &mut self, |
| energy: f64, |
| params: Vec<f64>, |
| grad_norm: f64, |
| ) { |
| self.energy_history.push(energy); |
| self.param_history.push(params); |
| self.gradient_history.push(grad_norm); |
| self.iterations += 1; |
|
|
| if energy < self.best_energy { |
| self.best_energy = energy; |
| } |
| } |
|
|
| |
| pub fn convergence_rate(&self) -> Option<f64> { |
| if self.energy_history.len() < 2 { |
| return None; |
| } |
|
|
| let n = self.energy_history.len() as f64; |
| let mean_e: f64 = self.energy_history.iter().sum::<f64>() / n; |
| let mean_i: f64 = (self.energy_history.len() as f64 - 1.0) / 2.0; |
|
|
| let mut num = 0.0; |
| let mut denom = 0.0; |
|
|
| for (i, e) in self.energy_history.iter().enumerate() { |
| let dev_i = i as f64 - mean_i; |
| let dev_e = e - mean_e; |
| num += dev_i * dev_e; |
| denom += dev_i * dev_i; |
| } |
|
|
| if denom.abs() < 1e-10 { |
| None |
| } else { |
| Some(num / denom) |
| } |
| } |
|
|
| |
| pub fn has_converged(&self, threshold: f64) -> bool { |
| if let Some(last_grad) = self.gradient_history.last() { |
| last_grad < &threshold |
| } else { |
| false |
| } |
| } |
| } |
|
|
| |
| #[derive(Debug, Clone)] |
| pub struct VQEOptimizer { |
| |
| pub learning_rate: f64, |
|
|
| |
| pub max_iterations: usize, |
|
|
| |
| pub convergence_threshold: f64, |
|
|
| |
| pub gradient_shift: f64, |
| } |
|
|
| impl VQEOptimizer { |
| |
| pub fn new() -> Self { |
| VQEOptimizer { |
| learning_rate: 0.01, |
| max_iterations: 100, |
| convergence_threshold: 1e-5, |
| gradient_shift: 1e-4, |
| } |
| } |
|
|
| |
| pub fn optimize( |
| &self, |
| mut circuit: ParametrizedCircuit, |
| hamiltonian: &PauliHamiltonian, |
| ) -> AlgorithmResult<(ParametrizedCircuit, EnergyEvaluator)> { |
| let mut evaluator = EnergyEvaluator::new(); |
|
|
| |
| let energy_fn = |params: &[f64]| -> f64 { |
| |
| |
| params.iter().map(|p| p.sin()).sum::<f64>() |
| }; |
|
|
| for iteration in 0..self.max_iterations { |
| |
| let energy = energy_fn(&circuit.params); |
|
|
| |
| let grad = circuit.gradient(self.gradient_shift, &energy_fn); |
| let grad_norm = grad.iter().map(|g| g * g).sum::<f64>().sqrt(); |
|
|
| |
| evaluator.record(energy, circuit.params.clone(), grad_norm); |
|
|
| |
| if evaluator.has_converged(self.convergence_threshold) { |
| break; |
| } |
|
|
| |
| for i in 0..circuit.n_params() { |
| circuit.params[i] -= self.learning_rate * grad[i]; |
| } |
| } |
|
|
| Ok((circuit, evaluator)) |
| } |
| } |
|
|
| |
| pub mod molecules { |
| use super::*; |
|
|
| |
| pub fn h2_ground_state_energy() -> f64 { |
| -1.17 |
| } |
|
|
| |
| pub fn lih_ground_state_energy() -> f64 { |
| -7.773 |
| } |
| } |
|
|
| #[cfg(test)] |
| mod tests { |
| use super::*; |
|
|
| #[test] |
| fn test_parametrized_circuit_simple_ansatz() { |
| let circuit = ParametrizedCircuit::simple_ansatz(2, 2); |
| assert_eq!(circuit.n_qubits, 2); |
| assert_eq!(circuit.depth, 2); |
| assert_eq!(circuit.n_params(), 4); |
| } |
|
|
| #[test] |
| fn test_energy_evaluator_recording() { |
| let mut eval = EnergyEvaluator::new(); |
| eval.record(-1.0, vec![0.1, 0.2], 0.1); |
| eval.record(-1.05, vec![0.15, 0.25], 0.08); |
|
|
| assert_eq!(eval.iterations, 2); |
| assert_eq!(eval.best_energy, -1.05); |
| } |
|
|
| #[test] |
| fn test_energy_evaluator_convergence() { |
| let mut eval = EnergyEvaluator::new(); |
| eval.record(-1.0, vec![0.1, 0.2], 0.1); |
| assert!(!eval.has_converged(0.05)); |
|
|
| eval.record(-1.05, vec![0.15, 0.25], 0.01); |
| assert!(eval.has_converged(0.05)); |
| } |
|
|
| #[test] |
| fn test_vqe_optimizer_creation() { |
| let optimizer = VQEOptimizer::new(); |
| assert!(optimizer.learning_rate > 0.0); |
| assert!(optimizer.max_iterations > 0); |
| } |
|
|
| #[test] |
| fn test_h2_ground_state() { |
| let gs = molecules::h2_ground_state_energy(); |
| assert!(gs < 0.0); |
| assert!(gs > -2.0); |
| } |
| } |
|
|
| |
|
|