use ruqu_core::circuit::QuantumCircuit;
use ruqu_core::simulator::{SimConfig, Simulator};
use ruqu_core::types::{Hamiltonian, PauliOp, PauliString};
pub struct VqeConfig {
pub hamiltonian: Hamiltonian,
pub num_qubits: u32,
pub ansatz_depth: u32,
pub max_iterations: u32,
pub convergence_threshold: f64,
pub learning_rate: f64,
pub seed: Option<u64>,
}
pub struct VqeResult {
pub optimal_energy: f64,
pub optimal_parameters: Vec<f64>,
pub energy_history: Vec<f64>,
pub num_iterations: u32,
pub converged: bool,
}
pub fn num_parameters(num_qubits: u32, depth: u32) -> usize {
(2 * num_qubits as usize) * (depth as usize)
}
pub fn build_ansatz(num_qubits: u32, depth: u32, params: &[f64]) -> QuantumCircuit {
let expected = num_parameters(num_qubits, depth);
assert_eq!(
params.len(),
expected,
"build_ansatz: expected {} parameters, got {}",
expected,
params.len()
);
let mut circuit = QuantumCircuit::new(num_qubits);
let mut idx = 0;
for _layer in 0..depth {
for q in 0..num_qubits {
circuit.ry(q, params[idx]);
idx += 1;
}
for q in 0..num_qubits {
circuit.rz(q, params[idx]);
idx += 1;
}
for q in 0..num_qubits.saturating_sub(1) {
circuit.cnot(q, q + 1);
}
}
circuit
}
pub fn evaluate_energy(
config: &VqeConfig,
params: &[f64],
) -> ruqu_core::error::Result<f64> {
let circuit = build_ansatz(config.num_qubits, config.ansatz_depth, params);
let sim_config = SimConfig {
seed: config.seed,
noise: None,
shots: None,
};
let result = Simulator::run_with_config(&circuit, &sim_config)?;
Ok(result.state.expectation_hamiltonian(&config.hamiltonian))
}
pub fn run_vqe(config: &VqeConfig) -> ruqu_core::error::Result<VqeResult> {
let n_params = num_parameters(config.num_qubits, config.ansatz_depth);
let mut params = vec![0.1_f64; n_params];
let mut energy_history: Vec<f64> = Vec::with_capacity(config.max_iterations as usize);
let mut converged = false;
let mut best_energy = f64::MAX;
let mut best_params = params.clone();
for iteration in 0..config.max_iterations {
let energy = evaluate_energy(config, ¶ms)?;
energy_history.push(energy);
if energy < best_energy {
best_energy = energy;
best_params = params.clone();
}
if iteration > 0 {
let prev = energy_history[iteration as usize - 1];
if (prev - energy).abs() < config.convergence_threshold {
converged = true;
break;
}
}
let shift = std::f64::consts::FRAC_PI_2;
let mut gradient = vec![0.0_f64; n_params];
for i in 0..n_params {
let mut params_plus = params.clone();
let mut params_minus = params.clone();
params_plus[i] += shift;
params_minus[i] -= shift;
let e_plus = evaluate_energy(config, ¶ms_plus)?;
let e_minus = evaluate_energy(config, ¶ms_minus)?;
gradient[i] = (e_plus - e_minus) / 2.0;
}
for i in 0..n_params {
params[i] -= config.learning_rate * gradient[i];
}
}
let num_iterations = energy_history.len() as u32;
Ok(VqeResult {
optimal_energy: best_energy,
optimal_parameters: best_params,
energy_history,
num_iterations,
converged,
})
}
pub fn h2_hamiltonian() -> Hamiltonian {
Hamiltonian {
terms: vec![
(-1.0523, PauliString { ops: vec![] }),
(
0.3979,
PauliString {
ops: vec![(1, PauliOp::Z)],
},
),
(
-0.3979,
PauliString {
ops: vec![(0, PauliOp::Z)],
},
),
(
-0.0112,
PauliString {
ops: vec![(0, PauliOp::Z), (1, PauliOp::Z)],
},
),
(
0.1809,
PauliString {
ops: vec![(0, PauliOp::X), (1, PauliOp::X)],
},
),
],
num_qubits: 2,
}
}
pub fn single_z_hamiltonian() -> Hamiltonian {
Hamiltonian {
terms: vec![(
-1.0,
PauliString {
ops: vec![(0, PauliOp::Z)],
},
)],
num_qubits: 1,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_num_parameters() {
assert_eq!(num_parameters(2, 1), 4);
assert_eq!(num_parameters(4, 3), 24);
assert_eq!(num_parameters(1, 5), 10);
}
#[test]
fn test_build_ansatz_gate_count() {
let n = 3;
let depth = 2;
let params = vec![0.0; num_parameters(n, depth)];
let circuit = build_ansatz(n, depth, ¶ms);
assert_eq!(circuit.num_qubits(), n);
assert_eq!(circuit.gates().len(), 16);
}
#[test]
#[should_panic(expected = "expected 4 parameters")]
fn test_build_ansatz_wrong_param_count() {
build_ansatz(2, 1, &[0.0; 3]);
}
#[test]
fn test_h2_hamiltonian_structure() {
let h = h2_hamiltonian();
assert_eq!(h.num_qubits, 2);
assert_eq!(h.terms.len(), 5);
}
#[test]
fn test_single_z_hamiltonian() {
let h = single_z_hamiltonian();
assert_eq!(h.num_qubits, 1);
assert_eq!(h.terms.len(), 1);
}
}