use super::*;
use crate::{DeviceError, DeviceResult, QuantumDevice};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::RwLock;
pub trait QuantumOptimizer: Send + Sync {
fn optimize(
&mut self,
initial_parameters: Vec<f64>,
objective_function: Box<dyn ObjectiveFunction + Send + Sync>,
) -> DeviceResult<OptimizationResult>;
fn config(&self) -> &OptimizerConfig;
fn reset(&mut self);
}
pub trait ObjectiveFunction: Send + Sync {
fn evaluate(&self, parameters: &[f64]) -> DeviceResult<f64>;
fn gradient(&self, parameters: &[f64]) -> DeviceResult<Option<Vec<f64>>>;
fn metadata(&self) -> HashMap<String, String>;
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OptimizerConfig {
pub optimizer_type: OptimizerType,
pub max_iterations: usize,
pub tolerance: f64,
pub learning_rate: f64,
pub momentum: Option<f64>,
pub adaptive_learning_rate: bool,
pub bounds: Option<(f64, f64)>,
pub noise_resilience: bool,
pub convergence_window: usize,
}
impl Default for OptimizerConfig {
fn default() -> Self {
Self {
optimizer_type: OptimizerType::Adam,
max_iterations: 1000,
tolerance: 1e-6,
learning_rate: 0.01,
momentum: Some(0.9),
adaptive_learning_rate: true,
bounds: None,
noise_resilience: true,
convergence_window: 10,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OptimizationResult {
pub optimal_parameters: Vec<f64>,
pub optimal_value: f64,
pub iterations: usize,
pub converged: bool,
pub function_evaluations: usize,
pub gradient_evaluations: usize,
pub optimization_history: Vec<OptimizationStep>,
pub final_gradient_norm: Option<f64>,
pub execution_time: std::time::Duration,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OptimizationStep {
pub iteration: usize,
pub parameters: Vec<f64>,
pub objective_value: f64,
pub gradient_norm: Option<f64>,
pub learning_rate: f64,
pub step_size: f64,
}
pub struct GradientBasedOptimizer {
config: OptimizerConfig,
device: Arc<RwLock<dyn QuantumDevice + Send + Sync>>,
state: OptimizerState,
}
#[derive(Debug, Clone)]
struct OptimizerState {
iteration: usize,
momentum_buffer: Vec<f64>,
velocity: Vec<f64>, squared_gradients: Vec<f64>, learning_rate: f64,
convergence_history: Vec<f64>,
}
impl GradientBasedOptimizer {
pub fn new(
device: Arc<RwLock<dyn QuantumDevice + Send + Sync>>,
config: OptimizerConfig,
) -> Self {
let state = OptimizerState {
iteration: 0,
momentum_buffer: Vec::new(),
velocity: Vec::new(),
squared_gradients: Vec::new(),
learning_rate: config.learning_rate,
convergence_history: Vec::new(),
};
Self {
config,
device,
state,
}
}
fn update_parameters_adam(
&mut self,
parameters: &[f64],
gradients: &[f64],
) -> DeviceResult<Vec<f64>> {
if self.state.velocity.is_empty() {
self.state.velocity = vec![0.0; parameters.len()];
self.state.squared_gradients = vec![0.0; parameters.len()];
}
let beta1 = 0.9;
let beta2 = 0.999;
let epsilon = 1e-8;
let mut updated_params = parameters.to_vec();
for i in 0..parameters.len() {
self.state.velocity[i] = beta1 * self.state.velocity[i] + (1.0 - beta1) * gradients[i];
self.state.squared_gradients[i] = beta2 * self.state.squared_gradients[i]
+ (1.0 - beta2) * gradients[i] * gradients[i];
let m_hat =
self.state.velocity[i] / (1.0 - beta1.powi(self.state.iteration as i32 + 1));
let v_hat = self.state.squared_gradients[i]
/ (1.0 - beta2.powi(self.state.iteration as i32 + 1));
updated_params[i] -= self.state.learning_rate * m_hat / (v_hat.sqrt() + epsilon);
if let Some((min_bound, max_bound)) = self.config.bounds {
updated_params[i] = updated_params[i].clamp(min_bound, max_bound);
}
}
Ok(updated_params)
}
fn update_parameters_sgd(
&mut self,
parameters: &[f64],
gradients: &[f64],
) -> DeviceResult<Vec<f64>> {
if self.state.momentum_buffer.is_empty() {
self.state.momentum_buffer = vec![0.0; parameters.len()];
}
let momentum = self.config.momentum.unwrap_or(0.0);
let mut updated_params = parameters.to_vec();
for i in 0..parameters.len() {
self.state.momentum_buffer[i] =
momentum * self.state.momentum_buffer[i] - self.state.learning_rate * gradients[i];
updated_params[i] += self.state.momentum_buffer[i];
if let Some((min_bound, max_bound)) = self.config.bounds {
updated_params[i] = updated_params[i].clamp(min_bound, max_bound);
}
}
Ok(updated_params)
}
fn update_learning_rate(&mut self, gradient_norm: f64) {
if self.config.adaptive_learning_rate {
if gradient_norm > 1.0 {
self.state.learning_rate *= 0.95;
} else if gradient_norm < 0.1 {
self.state.learning_rate *= 1.05;
}
self.state.learning_rate = self.state.learning_rate.clamp(1e-6, 1.0);
}
}
fn check_convergence(&mut self, objective_value: f64) -> bool {
self.state.convergence_history.push(objective_value);
if self.state.convergence_history.len() < self.config.convergence_window {
return false;
}
if self.state.convergence_history.len() > self.config.convergence_window {
self.state.convergence_history.remove(0);
}
let recent_values = &self.state.convergence_history;
let max_val = recent_values
.iter()
.fold(f64::NEG_INFINITY, |a, &b| a.max(b));
let min_val = recent_values.iter().fold(f64::INFINITY, |a, &b| a.min(b));
(max_val - min_val).abs() < self.config.tolerance
}
}
impl QuantumOptimizer for GradientBasedOptimizer {
fn optimize(
&mut self,
initial_parameters: Vec<f64>,
objective_function: Box<dyn ObjectiveFunction + Send + Sync>,
) -> DeviceResult<OptimizationResult> {
let start_time = std::time::Instant::now();
let mut parameters = initial_parameters;
let mut optimization_history = Vec::new();
let mut function_evaluations = 0;
let mut gradient_evaluations = 0;
let mut best_value = f64::INFINITY;
let mut best_parameters = parameters.clone();
for iteration in 0..self.config.max_iterations {
self.state.iteration = iteration;
let objective_value = objective_function.evaluate(¶meters)?;
function_evaluations += 1;
if objective_value < best_value {
best_value = objective_value;
best_parameters.clone_from(¶meters);
}
let gradients = objective_function.gradient(¶meters)?;
let (gradient_norm, final_gradient_norm) = if let Some(grad) = gradients {
gradient_evaluations += 1;
let norm = grad.iter().map(|g| g * g).sum::<f64>().sqrt();
parameters = match self.config.optimizer_type {
OptimizerType::Adam => self.update_parameters_adam(¶meters, &grad)?,
OptimizerType::GradientDescent => {
self.update_parameters_sgd(¶meters, &grad)?
}
_ => {
return Err(DeviceError::InvalidInput(format!(
"Optimizer type {:?} not supported in gradient-based optimizer",
self.config.optimizer_type
)))
}
};
self.update_learning_rate(norm);
(Some(norm), Some(norm))
} else {
(None, None)
};
let step_size = if let Some(norm) = gradient_norm {
self.state.learning_rate * norm
} else {
0.0
};
optimization_history.push(OptimizationStep {
iteration,
parameters: parameters.clone(),
objective_value,
gradient_norm,
learning_rate: self.state.learning_rate,
step_size,
});
if self.check_convergence(objective_value) {
return Ok(OptimizationResult {
optimal_parameters: best_parameters,
optimal_value: best_value,
iterations: iteration + 1,
converged: true,
function_evaluations,
gradient_evaluations,
optimization_history,
final_gradient_norm,
execution_time: start_time.elapsed(),
});
}
}
Ok(OptimizationResult {
optimal_parameters: best_parameters,
optimal_value: best_value,
iterations: self.config.max_iterations,
converged: false,
function_evaluations,
gradient_evaluations,
optimization_history: optimization_history.clone(),
final_gradient_norm: optimization_history
.last()
.and_then(|step| step.gradient_norm),
execution_time: start_time.elapsed(),
})
}
fn config(&self) -> &OptimizerConfig {
&self.config
}
fn reset(&mut self) {
self.state = OptimizerState {
iteration: 0,
momentum_buffer: Vec::new(),
velocity: Vec::new(),
squared_gradients: Vec::new(),
learning_rate: self.config.learning_rate,
convergence_history: Vec::new(),
};
}
}
pub struct GradientFreeOptimizer {
config: OptimizerConfig,
device: Arc<RwLock<dyn QuantumDevice + Send + Sync>>,
population: Vec<Individual>,
generation: usize,
}
#[derive(Debug, Clone)]
struct Individual {
parameters: Vec<f64>,
fitness: Option<f64>,
}
impl GradientFreeOptimizer {
pub fn new(
device: Arc<RwLock<dyn QuantumDevice + Send + Sync>>,
config: OptimizerConfig,
) -> Self {
Self {
config,
device,
population: Vec::new(),
generation: 0,
}
}
fn initialize_population(&mut self, parameter_count: usize, population_size: usize) {
self.population.clear();
for _ in 0..population_size {
let parameters = if let Some((min_bound, max_bound)) = self.config.bounds {
(0..parameter_count)
.map(|_| fastrand::f64().mul_add(max_bound - min_bound, min_bound))
.collect()
} else {
(0..parameter_count)
.map(|_| fastrand::f64() * 2.0 * std::f64::consts::PI)
.collect()
};
self.population.push(Individual {
parameters,
fitness: None,
});
}
}
fn evaluate_population(
&mut self,
objective_function: &dyn ObjectiveFunction,
) -> DeviceResult<()> {
for individual in &mut self.population {
if individual.fitness.is_none() {
individual.fitness = Some(objective_function.evaluate(&individual.parameters)?);
}
}
Ok(())
}
fn evolve_population(&mut self) -> DeviceResult<()> {
self.population.sort_by(|a, b| {
let fitness_a = a.fitness.unwrap_or(f64::INFINITY);
let fitness_b = b.fitness.unwrap_or(f64::INFINITY);
fitness_a
.partial_cmp(&fitness_b)
.unwrap_or(std::cmp::Ordering::Equal)
});
let elite_count = self.population.len() / 4; let new_population_size = self.population.len();
let mut new_population = self.population[..elite_count].to_vec();
while new_population.len() < new_population_size {
let parent1 = self.tournament_selection(3)?;
let parent2 = self.tournament_selection(3)?;
let mut offspring = self.crossover(&parent1, &parent2)?;
self.mutate(&mut offspring)?;
new_population.push(offspring);
}
self.population = new_population;
Ok(())
}
fn tournament_selection(&self, tournament_size: usize) -> DeviceResult<Individual> {
let mut best_individual = None;
let mut best_fitness = f64::INFINITY;
for _ in 0..tournament_size {
let idx = fastrand::usize(0..self.population.len());
let individual = &self.population[idx];
let fitness = individual.fitness.unwrap_or(f64::INFINITY);
if fitness < best_fitness {
best_fitness = fitness;
best_individual = Some(individual.clone());
}
}
best_individual
.ok_or_else(|| DeviceError::InvalidInput("Tournament selection failed".to_string()))
}
fn crossover(&self, parent1: &Individual, parent2: &Individual) -> DeviceResult<Individual> {
let parameter_count = parent1.parameters.len();
let mut offspring_params = Vec::with_capacity(parameter_count);
for i in 0..parameter_count {
let param = if fastrand::bool() {
parent1.parameters[i]
} else {
parent2.parameters[i]
};
offspring_params.push(param);
}
Ok(Individual {
parameters: offspring_params,
fitness: None,
})
}
fn mutate(&self, individual: &mut Individual) -> DeviceResult<()> {
let mutation_rate = 0.1;
let mutation_strength = 0.1;
for param in &mut individual.parameters {
if fastrand::f64() < mutation_rate {
let mutation = (fastrand::f64() - 0.5) * 2.0 * mutation_strength;
*param += mutation;
if let Some((min_bound, max_bound)) = self.config.bounds {
*param = param.clamp(min_bound, max_bound);
}
}
}
individual.fitness = None; Ok(())
}
fn get_best_individual(&self) -> Option<&Individual> {
self.population.iter().min_by(|a, b| {
let fitness_a = a.fitness.unwrap_or(f64::INFINITY);
let fitness_b = b.fitness.unwrap_or(f64::INFINITY);
fitness_a
.partial_cmp(&fitness_b)
.unwrap_or(std::cmp::Ordering::Equal)
})
}
}
impl QuantumOptimizer for GradientFreeOptimizer {
fn optimize(
&mut self,
initial_parameters: Vec<f64>,
objective_function: Box<dyn ObjectiveFunction + Send + Sync>,
) -> DeviceResult<OptimizationResult> {
let start_time = std::time::Instant::now();
let population_size = 20; let parameter_count = initial_parameters.len();
let mut optimization_history = Vec::new();
let mut function_evaluations = 0;
self.initialize_population(parameter_count, population_size - 1);
self.population.push(Individual {
parameters: initial_parameters,
fitness: None,
});
for generation in 0..self.config.max_iterations {
self.generation = generation;
self.evaluate_population(objective_function.as_ref())?;
function_evaluations += self.population.len();
let best_individual = self.get_best_individual().ok_or_else(|| {
DeviceError::InvalidInput("No valid individuals in population".to_string())
})?;
let best_fitness = best_individual.fitness.unwrap_or(f64::INFINITY);
optimization_history.push(OptimizationStep {
iteration: generation,
parameters: best_individual.parameters.clone(),
objective_value: best_fitness,
gradient_norm: None,
learning_rate: 0.0, step_size: 0.0,
});
if generation > 10 {
let recent_best: Vec<f64> = optimization_history
.iter()
.rev()
.take(10)
.map(|step| step.objective_value)
.collect();
let max_recent = recent_best.iter().fold(f64::NEG_INFINITY, |a, &b| a.max(b));
let min_recent = recent_best.iter().fold(f64::INFINITY, |a, &b| a.min(b));
if (max_recent - min_recent).abs() < self.config.tolerance {
return Ok(OptimizationResult {
optimal_parameters: best_individual.parameters.clone(),
optimal_value: best_fitness,
iterations: generation + 1,
converged: true,
function_evaluations,
gradient_evaluations: 0,
optimization_history,
final_gradient_norm: None,
execution_time: start_time.elapsed(),
});
}
}
if generation < self.config.max_iterations - 1 {
self.evolve_population()?;
}
}
let best_individual = self.get_best_individual().ok_or_else(|| {
DeviceError::InvalidInput("No valid individuals in final population".to_string())
})?;
Ok(OptimizationResult {
optimal_parameters: best_individual.parameters.clone(),
optimal_value: best_individual.fitness.unwrap_or(f64::INFINITY),
iterations: self.config.max_iterations,
converged: false,
function_evaluations,
gradient_evaluations: 0,
optimization_history,
final_gradient_norm: None,
execution_time: start_time.elapsed(),
})
}
fn config(&self) -> &OptimizerConfig {
&self.config
}
fn reset(&mut self) {
self.population.clear();
self.generation = 0;
}
}
pub struct VQEObjectiveFunction {
hamiltonian: super::variational_algorithms::Hamiltonian,
ansatz: Box<dyn super::variational_algorithms::VariationalAnsatz>,
device: Arc<RwLock<dyn QuantumDevice + Send + Sync>>,
shots: usize,
}
impl VQEObjectiveFunction {
pub fn new(
hamiltonian: super::variational_algorithms::Hamiltonian,
ansatz: Box<dyn super::variational_algorithms::VariationalAnsatz>,
device: Arc<RwLock<dyn QuantumDevice + Send + Sync>>,
shots: usize,
) -> Self {
Self {
hamiltonian,
ansatz,
device,
shots,
}
}
}
impl ObjectiveFunction for VQEObjectiveFunction {
fn evaluate(&self, parameters: &[f64]) -> DeviceResult<f64> {
let rt = tokio::runtime::Runtime::new().map_err(|e| {
DeviceError::ExecutionFailed(format!("Failed to create tokio runtime: {e}"))
})?;
rt.block_on(async {
let circuit = self.ansatz.build_circuit(parameters)?;
let device = self.device.read().await;
let result = Self::execute_circuit_helper(&*device, &circuit, self.shots).await?;
let mut energy = 0.0;
let total_shots = result.shots as f64;
for term in &self.hamiltonian.terms {
let mut term_expectation = 0.0;
for (bitstring, count) in &result.counts {
let probability = *count as f64 / total_shots;
let mut eigenvalue = term.coefficient;
for (qubit_idx, pauli_op) in &term.paulis {
if let Some(bit_char) = bitstring.chars().nth(*qubit_idx) {
let bit_value = if bit_char == '1' { -1.0 } else { 1.0 };
match pauli_op {
super::variational_algorithms::PauliOperator::Z => {
eigenvalue *= bit_value;
}
super::variational_algorithms::PauliOperator::I | _ => {
}
}
}
}
term_expectation += probability * eigenvalue;
}
energy += term_expectation;
}
Ok(energy)
})
}
fn gradient(&self, _parameters: &[f64]) -> DeviceResult<Option<Vec<f64>>> {
Ok(None)
}
fn metadata(&self) -> HashMap<String, String> {
let mut metadata = HashMap::new();
metadata.insert("objective_type".to_string(), "VQE".to_string());
metadata.insert(
"hamiltonian_terms".to_string(),
self.hamiltonian.terms.len().to_string(),
);
metadata.insert("shots".to_string(), self.shots.to_string());
metadata
}
}
impl VQEObjectiveFunction {
async fn execute_circuit_helper(
device: &(dyn QuantumDevice + Send + Sync),
circuit: &ParameterizedQuantumCircuit,
shots: usize,
) -> DeviceResult<CircuitResult> {
let mut counts = std::collections::HashMap::new();
counts.insert("0".repeat(circuit.num_qubits()), shots / 2);
counts.insert("1".repeat(circuit.num_qubits()), shots / 2);
Ok(CircuitResult {
counts,
shots,
metadata: std::collections::HashMap::new(),
})
}
}
pub fn create_gradient_optimizer(
device: Arc<RwLock<dyn QuantumDevice + Send + Sync>>,
optimizer_type: OptimizerType,
learning_rate: f64,
) -> Box<dyn QuantumOptimizer> {
let config = OptimizerConfig {
optimizer_type,
learning_rate,
..Default::default()
};
Box::new(GradientBasedOptimizer::new(device, config))
}
pub fn create_gradient_free_optimizer(
device: Arc<RwLock<dyn QuantumDevice + Send + Sync>>,
max_iterations: usize,
) -> Box<dyn QuantumOptimizer> {
let config = OptimizerConfig {
optimizer_type: OptimizerType::GradientDescent, max_iterations,
..Default::default()
};
Box::new(GradientFreeOptimizer::new(device, config))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_utils::create_mock_quantum_device;
struct QuadraticObjective {
target: Vec<f64>,
}
impl ObjectiveFunction for QuadraticObjective {
fn evaluate(&self, parameters: &[f64]) -> DeviceResult<f64> {
let mut sum = 0.0;
for (i, ¶m) in parameters.iter().enumerate() {
let target = self.target.get(i).unwrap_or(&0.0);
sum += (param - target).powi(2);
}
Ok(sum)
}
fn gradient(&self, parameters: &[f64]) -> DeviceResult<Option<Vec<f64>>> {
let mut grad = Vec::new();
for (i, ¶m) in parameters.iter().enumerate() {
let target = self.target.get(i).unwrap_or(&0.0);
grad.push(2.0 * (param - target));
}
Ok(Some(grad))
}
fn metadata(&self) -> HashMap<String, String> {
let mut metadata = HashMap::new();
metadata.insert("objective_type".to_string(), "quadratic".to_string());
metadata
}
}
#[test]
fn test_gradient_based_optimizer() {
let device = create_mock_quantum_device();
let config = OptimizerConfig {
learning_rate: 0.3, max_iterations: 500,
tolerance: 1e-6,
..Default::default()
};
let mut optimizer = GradientBasedOptimizer::new(device, config);
let objective = Box::new(QuadraticObjective {
target: vec![1.0, 2.0, 3.0],
});
let initial_params = vec![0.0, 0.0, 0.0];
let result = optimizer
.optimize(initial_params, objective)
.expect("Gradient-based optimization should succeed with quadratic objective");
assert!(result.optimal_value < 1.0); assert!(result.function_evaluations > 0);
assert!(result.gradient_evaluations > 0);
}
#[test]
fn test_gradient_free_optimizer() {
let device = create_mock_quantum_device();
let config = OptimizerConfig {
max_iterations: 50,
tolerance: 1e-3,
..Default::default()
};
let mut optimizer = GradientFreeOptimizer::new(device, config);
let objective = Box::new(QuadraticObjective {
target: vec![1.0, 2.0],
});
let initial_params = vec![0.0, 0.0];
let result = optimizer
.optimize(initial_params, objective)
.expect("Gradient-free optimization should succeed with quadratic objective");
assert!(result.optimal_value < 5.0); assert!(result.function_evaluations > 0);
assert_eq!(result.gradient_evaluations, 0); }
#[test]
fn test_optimization_result() {
let result = OptimizationResult {
optimal_parameters: vec![1.0, 2.0],
optimal_value: 0.5,
iterations: 100,
converged: true,
function_evaluations: 200,
gradient_evaluations: 100,
optimization_history: vec![],
final_gradient_norm: Some(1e-6),
execution_time: std::time::Duration::from_millis(500),
};
assert_eq!(result.optimal_parameters.len(), 2);
assert_eq!(result.optimal_value, 0.5);
assert!(result.converged);
}
#[test]
fn test_optimizer_config() {
let config = OptimizerConfig {
optimizer_type: OptimizerType::Adam,
learning_rate: 0.001,
max_iterations: 500,
..Default::default()
};
assert_eq!(config.optimizer_type, OptimizerType::Adam);
assert_eq!(config.learning_rate, 0.001);
assert_eq!(config.max_iterations, 500);
}
}