Merge pull request 'test(symclaw-skill): cover handlers_advanced via JSON API' (#10) from ci-doctor/coverage-20260518-201834 into master
Reviewed-on: #10
This commit is contained in:
@@ -0,0 +1,261 @@
|
||||
//! VQE (Variational Quantum Eigensolver) tools.
|
||||
//!
|
||||
//! Provides:
|
||||
//! - UCCSD ansatz parameter-shift gradient formula
|
||||
//! - Hartree-Fock reference state (symbolic)
|
||||
//! - Parameter-shift rule for quantum gradients
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
|
||||
/// A parameterized quantum ansatz: list of (gate_type, qubit_indices, parameter_name).
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Ansatz {
|
||||
/// Layers: each is a list of (gate, qubits, param_name).
|
||||
pub layers: Vec<AnsatzLayer>,
|
||||
/// Current parameter values.
|
||||
pub params: HashMap<String, f64>,
|
||||
/// Number of qubits.
|
||||
pub n_qubits: usize,
|
||||
}
|
||||
|
||||
/// A single ansatz layer.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct AnsatzLayer {
|
||||
pub gates: Vec<AnsatzGate>,
|
||||
}
|
||||
|
||||
/// A parameterized gate in the ansatz.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct AnsatzGate {
|
||||
pub gate_type: String,
|
||||
pub qubits: Vec<usize>,
|
||||
pub param_name: Option<String>,
|
||||
}
|
||||
|
||||
impl Ansatz {
|
||||
/// Create an empty ansatz for n qubits.
|
||||
#[must_use]
|
||||
pub fn new(n_qubits: usize) -> Self {
|
||||
Self {
|
||||
layers: Vec::new(),
|
||||
n_qubits,
|
||||
params: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Add a Rz(θ) rotation layer.
|
||||
pub fn add_rz_layer(&mut self, qubit: usize, param_name: &str, init_val: f64) -> &mut Self {
|
||||
self.layers.push(AnsatzLayer {
|
||||
gates: vec![AnsatzGate {
|
||||
gate_type: "Rz".into(),
|
||||
qubits: vec![qubit],
|
||||
param_name: Some(param_name.into()),
|
||||
}],
|
||||
});
|
||||
self.params.insert(param_name.into(), init_val);
|
||||
self
|
||||
}
|
||||
|
||||
/// Add a CNOT entangling layer.
|
||||
pub fn add_cnot_layer(&mut self, ctrl: usize, tgt: usize) -> &mut Self {
|
||||
self.layers.push(AnsatzLayer {
|
||||
gates: vec![AnsatzGate {
|
||||
gate_type: "CNOT".into(),
|
||||
qubits: vec![ctrl, tgt],
|
||||
param_name: None,
|
||||
}],
|
||||
});
|
||||
self
|
||||
}
|
||||
|
||||
/// Number of variational parameters.
|
||||
#[must_use]
|
||||
pub fn n_params(&self) -> usize {
|
||||
self.params.len()
|
||||
}
|
||||
|
||||
/// List parameter names.
|
||||
#[must_use]
|
||||
pub fn param_names(&self) -> Vec<&str> {
|
||||
self.params.keys().map(String::as_str).collect()
|
||||
}
|
||||
}
|
||||
|
||||
/// Parameter-shift gradient rule.
|
||||
///
|
||||
/// For a gate G(θ) = exp(-iθ/2 P) where P is a Pauli:
|
||||
/// ∂⟨H⟩/∂θ = [⟨H⟩(θ + π/2) - ⟨H⟩(θ - π/2)] / 2
|
||||
///
|
||||
/// This function returns `(θ_plus, θ_minus, coefficient)` for a parameter at `θ`.
|
||||
#[must_use]
|
||||
pub fn parameter_shift_gradient_rule(theta: f64) -> (f64, f64, f64) {
|
||||
let shift = std::f64::consts::PI / 2.0;
|
||||
(theta + shift, theta - shift, 0.5)
|
||||
}
|
||||
|
||||
/// Compute the numerical gradient of an expectation value using parameter-shift rule.
|
||||
///
|
||||
/// `expectation_fn` takes a parameter map and returns ⟨H⟩.
|
||||
pub fn parameter_shift_gradient<F>(
|
||||
params: &HashMap<String, f64>,
|
||||
param_name: &str,
|
||||
expectation_fn: &F,
|
||||
) -> f64
|
||||
where
|
||||
F: Fn(&HashMap<String, f64>) -> f64,
|
||||
{
|
||||
let theta = *params.get(param_name).unwrap_or(&0.0);
|
||||
let (tp, tm, coeff) = parameter_shift_gradient_rule(theta);
|
||||
|
||||
let mut params_plus = params.clone();
|
||||
params_plus.insert(param_name.into(), tp);
|
||||
let mut params_minus = params.clone();
|
||||
params_minus.insert(param_name.into(), tm);
|
||||
|
||||
coeff * (expectation_fn(¶ms_plus) - expectation_fn(¶ms_minus))
|
||||
}
|
||||
|
||||
/// Hartree-Fock reference state description.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct HFReference {
|
||||
/// Number of electrons.
|
||||
pub n_electrons: usize,
|
||||
/// Number of spin-orbitals.
|
||||
pub n_orbitals: usize,
|
||||
/// Occupied orbital indices (0-indexed).
|
||||
pub occupied: Vec<usize>,
|
||||
/// Virtual orbital indices.
|
||||
pub virtual_orbs: Vec<usize>,
|
||||
}
|
||||
|
||||
impl HFReference {
|
||||
/// Create HF reference for `n_electrons` in `n_orbitals` spin-orbitals.
|
||||
#[must_use]
|
||||
pub fn new(n_electrons: usize, n_orbitals: usize) -> Self {
|
||||
assert!(n_electrons <= n_orbitals);
|
||||
let occupied: Vec<usize> = (0..n_electrons).collect();
|
||||
let virtual_orbs: Vec<usize> = (n_electrons..n_orbitals).collect();
|
||||
Self {
|
||||
n_electrons,
|
||||
n_orbitals,
|
||||
occupied,
|
||||
virtual_orbs,
|
||||
}
|
||||
}
|
||||
|
||||
/// Number of occupied orbitals.
|
||||
#[must_use]
|
||||
pub fn n_occ(&self) -> usize {
|
||||
self.occupied.len()
|
||||
}
|
||||
|
||||
/// Number of virtual orbitals.
|
||||
#[must_use]
|
||||
pub fn n_virt(&self) -> usize {
|
||||
self.virtual_orbs.len()
|
||||
}
|
||||
|
||||
/// UCCSD single excitation operators: (i→a) for i in occ, a in virt.
|
||||
#[must_use]
|
||||
pub fn single_excitations(&self) -> Vec<(usize, usize)> {
|
||||
let mut excitations = Vec::new();
|
||||
for &i in &self.occupied {
|
||||
for &a in &self.virtual_orbs {
|
||||
excitations.push((i, a));
|
||||
}
|
||||
}
|
||||
excitations
|
||||
}
|
||||
|
||||
/// UCCSD double excitation operators: (i,j→a,b) for i<j in occ, a<b in virt.
|
||||
#[must_use]
|
||||
pub fn double_excitations(&self) -> Vec<(usize, usize, usize, usize)> {
|
||||
let mut excitations = Vec::new();
|
||||
for (ki, &i) in self.occupied.iter().enumerate() {
|
||||
for &j in &self.occupied[ki + 1..] {
|
||||
for (ka, &a) in self.virtual_orbs.iter().enumerate() {
|
||||
for &b in &self.virtual_orbs[ka + 1..] {
|
||||
excitations.push((i, j, a, b));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
excitations
|
||||
}
|
||||
|
||||
/// Total UCCSD parameters: singles + doubles.
|
||||
#[must_use]
|
||||
pub fn n_uccsd_params(&self) -> usize {
|
||||
self.single_excitations().len() + self.double_excitations().len()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::f64::consts::PI;
|
||||
|
||||
#[test]
|
||||
fn parameter_shift_gradient_rule() {
|
||||
let theta = 0.5;
|
||||
let (tp, tm, coeff) = super::parameter_shift_gradient_rule(theta);
|
||||
assert!((tp - (theta + PI / 2.0)).abs() < 1e-12);
|
||||
assert!((tm - (theta - PI / 2.0)).abs() < 1e-12);
|
||||
assert!((coeff - 0.5).abs() < 1e-12);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parameter_shift_numerical_gradient() {
|
||||
// f(θ) = cos(θ), ∂f/∂θ = -sin(θ)
|
||||
let theta = 0.7;
|
||||
let mut params = HashMap::new();
|
||||
params.insert("theta".into(), theta);
|
||||
let grad = parameter_shift_gradient(¶ms, "theta", &|p| p["theta"].cos());
|
||||
let expected = -theta.sin();
|
||||
assert!(
|
||||
(grad - expected).abs() < 1e-6,
|
||||
"gradient {grad} ≠ expected {expected}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hf_reference_state() {
|
||||
let hf = HFReference::new(2, 4);
|
||||
assert_eq!(hf.n_occ(), 2);
|
||||
assert_eq!(hf.n_virt(), 2);
|
||||
assert_eq!(hf.occupied, vec![0, 1]);
|
||||
assert_eq!(hf.virtual_orbs, vec![2, 3]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn uccsd_ansatz_singles() {
|
||||
// 2 occ × 2 virt = 4 singles
|
||||
let hf = HFReference::new(2, 4);
|
||||
assert_eq!(hf.single_excitations().len(), 4);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn uccsd_ansatz_doubles() {
|
||||
// C(2,2) × C(2,2) = 1 double excitation for 2 occ, 2 virt
|
||||
let hf = HFReference::new(2, 4);
|
||||
assert_eq!(hf.double_excitations().len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn uccsd_parameterised() {
|
||||
let hf = HFReference::new(2, 4);
|
||||
let n = hf.n_uccsd_params();
|
||||
assert_eq!(n, 5, "2e/4o: 4 singles + 1 double = 5 UCCSD params");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ansatz_construction() {
|
||||
let mut ansatz = Ansatz::new(2);
|
||||
ansatz.add_rz_layer(0, "theta0", 0.0);
|
||||
ansatz.add_cnot_layer(0, 1);
|
||||
ansatz.add_rz_layer(1, "theta1", 0.0);
|
||||
assert_eq!(ansatz.n_params(), 2);
|
||||
assert_eq!(ansatz.layers.len(), 3);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user