use scirs2_core::ndarray::Array1;
use std::collections::HashMap;
use super::circuit::ParameterizedQuantumCircuit;
use super::config::{HardwareArchitecture, QMLAlgorithmType, QMLConfig};
use super::trainer::QuantumMLTrainer;
use crate::circuit_interfaces::InterfaceCircuit;
use crate::error::Result;
pub fn benchmark_quantum_ml_algorithms() -> Result<HashMap<String, f64>> {
let mut results = HashMap::new();
let algorithms = vec![
QMLAlgorithmType::VQE,
QMLAlgorithmType::QAOA,
QMLAlgorithmType::QCNN,
QMLAlgorithmType::QSVM,
];
let hardware_archs = vec![
HardwareArchitecture::NISQ,
HardwareArchitecture::Superconducting,
HardwareArchitecture::TrappedIon,
];
for &algorithm in &algorithms {
for &hardware in &hardware_archs {
let benchmark_time = benchmark_algorithm_hardware_combination(algorithm, hardware)?;
results.insert(format!("{algorithm:?}_{hardware:?}"), benchmark_time);
}
}
Ok(results)
}
fn benchmark_algorithm_hardware_combination(
algorithm: QMLAlgorithmType,
hardware: HardwareArchitecture,
) -> Result<f64> {
let start = std::time::Instant::now();
let config = QMLConfig {
algorithm_type: algorithm,
hardware_architecture: hardware,
num_qubits: 4,
circuit_depth: 2,
num_parameters: 8,
max_epochs: 5,
batch_size: 4,
..Default::default()
};
let circuit = create_test_circuit(config.num_qubits)?;
let parameters = Array1::from_vec(vec![0.1; config.num_parameters]);
let parameter_names = (0..config.num_parameters)
.map(|i| format!("param_{i}"))
.collect();
let pqc = ParameterizedQuantumCircuit::new(circuit, parameters, parameter_names, hardware);
let mut trainer = QuantumMLTrainer::new(config, pqc, None)?;
let loss_fn = |params: &Array1<f64>| -> Result<f64> {
Ok(params.iter().map(|&x| x * x).sum::<f64>())
};
let _result = trainer.train(loss_fn)?;
Ok(start.elapsed().as_secs_f64() * 1000.0)
}
fn create_test_circuit(num_qubits: usize) -> Result<InterfaceCircuit> {
let circuit = InterfaceCircuit::new(num_qubits, 0);
Ok(circuit)
}
pub fn benchmark_gradient_methods() -> Result<HashMap<String, f64>> {
let mut results = HashMap::new();
let methods = vec![
"parameter_shift",
"finite_differences",
"automatic_differentiation",
"natural_gradients",
];
for method in methods {
let benchmark_time = benchmark_gradient_method(method)?;
results.insert(method.to_string(), benchmark_time);
}
Ok(results)
}
fn benchmark_gradient_method(method: &str) -> Result<f64> {
let start = std::time::Instant::now();
let test_function = |params: &Array1<f64>| -> Result<f64> {
Ok(params
.iter()
.enumerate()
.map(|(i, &x)| (i as f64 + 1.0) * x * x)
.sum::<f64>())
};
let test_params = Array1::from_vec(vec![0.1, 0.2, 0.3, 0.4]);
let test_function_dual = |params: &[Dual64]| -> Dual64 {
params
.iter()
.enumerate()
.fold(Dual64::constant(0.0), |acc, (i, &x)| {
acc + (x * x) * (i as f64 + 1.0)
})
};
match method {
"parameter_shift" => {
compute_parameter_shift_gradient(&test_function, &test_params)?;
}
"finite_differences" => {
compute_finite_difference_gradient(&test_function, &test_params)?;
}
"automatic_differentiation" => {
compute_autodiff_gradient(&test_function_dual, &test_params)?;
}
"natural_gradients" => {
compute_natural_gradient(&test_function, &test_params)?;
}
_ => {
return Err(crate::error::SimulatorError::InvalidInput(format!(
"Unknown gradient method: {method}"
)))
}
}
Ok(start.elapsed().as_secs_f64() * 1000.0)
}
fn compute_parameter_shift_gradient<F>(
function: &F,
parameters: &Array1<f64>,
) -> Result<Array1<f64>>
where
F: Fn(&Array1<f64>) -> Result<f64>,
{
let num_params = parameters.len();
let mut gradient = Array1::zeros(num_params);
let shift = std::f64::consts::PI / 2.0;
for i in 0..num_params {
let mut params_plus = parameters.clone();
let mut params_minus = parameters.clone();
params_plus[i] += shift;
params_minus[i] -= shift;
let loss_plus = function(¶ms_plus)?;
let loss_minus = function(¶ms_minus)?;
gradient[i] = (loss_plus - loss_minus) / 2.0;
}
Ok(gradient)
}
fn compute_finite_difference_gradient<F>(
function: &F,
parameters: &Array1<f64>,
) -> Result<Array1<f64>>
where
F: Fn(&Array1<f64>) -> Result<f64>,
{
let num_params = parameters.len();
let mut gradient = Array1::zeros(num_params);
let eps = 1e-8;
for i in 0..num_params {
let mut params_plus = parameters.clone();
params_plus[i] += eps;
let loss_plus = function(¶ms_plus)?;
let loss_current = function(parameters)?;
gradient[i] = (loss_plus - loss_current) / eps;
}
Ok(gradient)
}
#[derive(Debug, Clone, Copy)]
struct Dual64 {
val: f64,
deriv: f64,
}
impl Dual64 {
const fn constant(val: f64) -> Self {
Self { val, deriv: 0.0 }
}
const fn variable(val: f64) -> Self {
Self { val, deriv: 1.0 }
}
}
impl std::ops::Add for Dual64 {
type Output = Self;
fn add(self, rhs: Self) -> Self {
Self {
val: self.val + rhs.val,
deriv: self.deriv + rhs.deriv,
}
}
}
impl std::ops::Mul for Dual64 {
type Output = Self;
fn mul(self, rhs: Self) -> Self {
Self {
val: self.val * rhs.val,
deriv: self.deriv * rhs.val + self.val * rhs.deriv,
}
}
}
impl std::ops::Mul<f64> for Dual64 {
type Output = Self;
fn mul(self, rhs: f64) -> Self {
Self {
val: self.val * rhs,
deriv: self.deriv * rhs,
}
}
}
fn compute_autodiff_gradient<G>(function: &G, parameters: &Array1<f64>) -> Result<Array1<f64>>
where
G: Fn(&[Dual64]) -> Dual64,
{
let num_params = parameters.len();
let mut gradient = Array1::zeros(num_params);
let mut duals: Vec<Dual64> = parameters.iter().map(|&p| Dual64::constant(p)).collect();
for i in 0..num_params {
duals[i] = Dual64::variable(parameters[i]);
gradient[i] = function(&duals).deriv;
duals[i] = Dual64::constant(parameters[i]);
}
Ok(gradient)
}
fn compute_natural_gradient<F>(function: &F, parameters: &Array1<f64>) -> Result<Array1<f64>>
where
F: Fn(&Array1<f64>) -> Result<f64>,
{
let gradient = compute_parameter_shift_gradient(function, parameters)?;
let damping = 1e-4;
let natural_gradient = gradient.mapv(|g| g / g.mul_add(g, damping));
Ok(natural_gradient)
}
pub fn benchmark_optimizers() -> Result<HashMap<String, f64>> {
let mut results = HashMap::new();
let optimizers = vec!["adam", "sgd", "rmsprop", "lbfgs"];
for optimizer in optimizers {
let benchmark_time = benchmark_optimizer(optimizer)?;
results.insert(optimizer.to_string(), benchmark_time);
}
Ok(results)
}
fn benchmark_optimizer(optimizer: &str) -> Result<f64> {
let start = std::time::Instant::now();
let mut params = Array1::from_vec(vec![1.0, 2.0, 3.0, 4.0]);
let target = Array1::<f64>::zeros(4);
let lr = 0.1;
let num_params = params.len();
match optimizer {
"sgd" => {
for _iteration in 0..100 {
let gradient = ¶ms - ⌖
params = ¶ms - lr * &gradient;
}
}
"adam" => {
let beta1 = 0.9;
let beta2 = 0.999;
let eps = 1e-8;
let mut m = Array1::<f64>::zeros(num_params);
let mut v = Array1::<f64>::zeros(num_params);
for iteration in 1..=100 {
let gradient = ¶ms - ⌖
m = beta1 * &m + (1.0 - beta1) * &gradient;
v = beta2 * &v + (1.0 - beta2) * gradient.mapv(|g| g * g);
let bias_correction1 = 1.0 - beta1.powi(iteration);
let bias_correction2 = 1.0 - beta2.powi(iteration);
let m_hat = &m / bias_correction1;
let v_hat = &v / bias_correction2;
let update = &m_hat / (v_hat.mapv(f64::sqrt) + eps);
params = ¶ms - lr * &update;
}
}
"rmsprop" => {
let decay = 0.9;
let eps = 1e-8;
let mut mean_square = Array1::<f64>::zeros(num_params);
for _iteration in 0..100 {
let gradient = ¶ms - ⌖
mean_square = decay * &mean_square + (1.0 - decay) * gradient.mapv(|g| g * g);
let update = &gradient / (mean_square.mapv(f64::sqrt) + eps);
params = ¶ms - lr * &update;
}
}
"lbfgs" => {
const HISTORY_SIZE: usize = 5;
let mut s_history: Vec<Array1<f64>> = Vec::new();
let mut y_history: Vec<Array1<f64>> = Vec::new();
let mut prev_params: Option<Array1<f64>> = None;
let mut prev_gradient: Option<Array1<f64>> = None;
for _iteration in 0..100 {
let gradient = ¶ms - ⌖
if let (Some(pp), Some(pg)) = (&prev_params, &prev_gradient) {
let s = ¶ms - pp;
let y = &gradient - pg;
if y.dot(&y) > 1e-14 {
s_history.push(s);
y_history.push(y);
if s_history.len() > HISTORY_SIZE {
s_history.remove(0);
y_history.remove(0);
}
}
}
let mut q = gradient.clone();
let mut alphas = vec![0.0; s_history.len()];
let mut rhos = vec![0.0; s_history.len()];
for i in (0..s_history.len()).rev() {
let rho = 1.0 / y_history[i].dot(&s_history[i]);
let alpha = rho * s_history[i].dot(&q);
q = &q - alpha * &y_history[i];
alphas[i] = alpha;
rhos[i] = rho;
}
let gamma = match (s_history.last(), y_history.last()) {
(Some(s), Some(y)) => s.dot(y) / y.dot(y),
_ => 1.0,
};
let mut z = &q * gamma;
for i in 0..s_history.len() {
let beta = rhos[i] * y_history[i].dot(&z);
z = &z + (alphas[i] - beta) * &s_history[i];
}
prev_params = Some(params.clone());
prev_gradient = Some(gradient);
params = ¶ms - lr * &z;
}
}
_ => {
return Err(crate::error::SimulatorError::InvalidInput(format!(
"Unknown optimizer: {optimizer}"
)))
}
}
Ok(start.elapsed().as_secs_f64() * 1000.0)
}
pub fn run_comprehensive_benchmarks() -> Result<HashMap<String, HashMap<String, f64>>> {
let mut all_results = HashMap::new();
all_results.insert("algorithms".to_string(), benchmark_quantum_ml_algorithms()?);
all_results.insert("gradients".to_string(), benchmark_gradient_methods()?);
all_results.insert("optimizers".to_string(), benchmark_optimizers()?);
Ok(all_results)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_autodiff_gradient_is_exact_and_distinct_from_parameter_shift() {
let test_function_dual = |params: &[Dual64]| -> Dual64 {
params
.iter()
.enumerate()
.fold(Dual64::constant(0.0), |acc, (i, &x)| {
acc + (x * x) * (i as f64 + 1.0)
})
};
let test_function = |params: &Array1<f64>| -> Result<f64> {
Ok(params
.iter()
.enumerate()
.map(|(i, &x)| (i as f64 + 1.0) * x * x)
.sum::<f64>())
};
let params = Array1::from_vec(vec![0.1, 0.2, 0.3, 0.4]);
let autodiff_grad = compute_autodiff_gradient(&test_function_dual, ¶ms)
.expect("autodiff gradient should succeed");
let parameter_shift_grad = compute_parameter_shift_gradient(&test_function, ¶ms)
.expect("parameter-shift gradient should succeed");
for (i, &x) in params.iter().enumerate() {
let expected = 2.0 * (i as f64 + 1.0) * x;
assert!(
(autodiff_grad[i] - expected).abs() < 1e-10,
"autodiff gradient[{i}] = {}, expected exact {expected}",
autodiff_grad[i]
);
}
let mut any_differs = false;
for i in 0..params.len() {
if (autodiff_grad[i] - parameter_shift_grad[i]).abs() > 1e-6 {
any_differs = true;
}
}
assert!(
any_differs,
"autodiff and parameter-shift must not be the same algorithm in disguise"
);
}
#[test]
fn test_natural_gradient_differs_from_parameter_shift() {
let test_function = |params: &Array1<f64>| -> Result<f64> {
Ok(params
.iter()
.enumerate()
.map(|(i, &x)| (i as f64 + 1.0) * x * x)
.sum::<f64>())
};
let params = Array1::from_vec(vec![0.1, 0.2, 0.3, 0.4]);
let natural_grad = compute_natural_gradient(&test_function, ¶ms)
.expect("natural gradient should succeed");
let parameter_shift_grad = compute_parameter_shift_gradient(&test_function, ¶ms)
.expect("parameter-shift gradient should succeed");
let mut any_differs = false;
for i in 0..params.len() {
if (natural_grad[i] - parameter_shift_grad[i]).abs() > 1e-6 {
any_differs = true;
}
}
assert!(
any_differs,
"natural gradient must apply real Fisher preconditioning, not alias parameter-shift"
);
}
#[test]
fn test_optimizers_are_genuinely_distinct_update_rules() {
fn run(optimizer: &str, iterations: usize) -> Array1<f64> {
let mut params = Array1::from_vec(vec![1.0, 2.0, 3.0, 4.0]);
let target = Array1::<f64>::zeros(4);
let lr = 0.1;
let num_params = params.len();
match optimizer {
"sgd" => {
for _ in 0..iterations {
let gradient = ¶ms - ⌖
params = ¶ms - lr * &gradient;
}
}
"adam" => {
let (beta1, beta2, eps) = (0.9, 0.999, 1e-8);
let mut m = Array1::<f64>::zeros(num_params);
let mut v = Array1::<f64>::zeros(num_params);
for t in 1..=iterations {
let gradient = ¶ms - ⌖
m = beta1 * &m + (1.0 - beta1) * &gradient;
v = beta2 * &v + (1.0 - beta2) * gradient.mapv(|g| g * g);
let m_hat = &m / (1.0 - beta1.powi(t as i32));
let v_hat = &v / (1.0 - beta2.powi(t as i32));
let update = &m_hat / (v_hat.mapv(f64::sqrt) + eps);
params = ¶ms - lr * &update;
}
}
"rmsprop" => {
let (decay, eps) = (0.9, 1e-8);
let mut mean_square = Array1::<f64>::zeros(num_params);
for _ in 0..iterations {
let gradient = ¶ms - ⌖
mean_square =
decay * &mean_square + (1.0 - decay) * gradient.mapv(|g| g * g);
let update = &gradient / (mean_square.mapv(f64::sqrt) + eps);
params = ¶ms - lr * &update;
}
}
_ => unreachable!(),
}
params
}
let sgd_result = run("sgd", 10);
let adam_result = run("adam", 10);
let rmsprop_result = run("rmsprop", 10);
assert!(
(0..sgd_result.len()).any(|i| (sgd_result[i] - adam_result[i]).abs() > 1e-6),
"adam must diverge from plain sgd once its moment estimates kick in"
);
assert!(
(0..sgd_result.len()).any(|i| (sgd_result[i] - rmsprop_result[i]).abs() > 1e-6),
"rmsprop must diverge from plain sgd once its running average kicks in"
);
assert!(
(0..adam_result.len()).any(|i| (adam_result[i] - rmsprop_result[i]).abs() > 1e-6),
"adam and rmsprop must be genuinely different update rules"
);
}
#[test]
fn test_benchmark_optimizer_runs_all_named_optimizers() {
for name in ["sgd", "adam", "rmsprop", "lbfgs"] {
let elapsed_ms = benchmark_optimizer(name)
.unwrap_or_else(|e| panic!("benchmark_optimizer({name}) failed: {e}"));
assert!(elapsed_ms >= 0.0);
}
}
#[test]
fn test_benchmark_optimizer_unknown_name_errors() {
let result = benchmark_optimizer("not_a_real_optimizer");
assert!(result.is_err());
}
}