Status: Proposed Date: 2026-02-06 Authors: ruv.io, RuVector Team Deciders: Architecture Review Board
| Version | Date | Author | Changes |
|---|---|---|---|
| 0.1 | 2026-02-06 | ruv.io | Initial VQE architecture proposal |
The Variational Quantum Eigensolver (VQE) is one of the most important near-term quantum algorithms, with direct applications in computational chemistry, materials science, and combinatorial optimization. VQE computes ground-state energies of molecular Hamiltonians by variationally minimizing the expectation value of a Hamiltonian operator with respect to a parameterized quantum state (ansatz).
VQE sits at the intersection of quantum simulation and classical optimization, making it a natural fit for ruQu's hybrid classical-quantum architecture:
- Chemistry applications: Drug discovery, catalyst design, battery materials
- Optimization: QUBO problems, portfolio optimization, logistics
- Benchmarking: VQE circuits exercise the full gate set and serve as a representative workload for evaluating simulator performance
- Agent integration: ruVector agents can autonomously explore chemical configuration spaces using VQE as the inner evaluation kernel
| Requirement | Description | Priority |
|---|---|---|
| Parameterized circuits | Symbolic gate angles resolved at evaluation time | P0 |
| Hamiltonian decomposition | Represent H as sum of weighted Pauli strings | P0 |
| Exact expectation values | Direct state vector computation (no shot noise) | P0 |
| Gradient evaluation | Parameter-shift rule for classical optimizer | P0 |
| Shot-based sampling | Optional mode for hardware noise emulation | P1 |
| Classical optimizer interface | Trait-based abstraction for multiple optimizers | P1 |
| Hardware-efficient ansatz | Pre-built ansatz library for common topologies | P2 |
Without dedicated VQE support, users must manually:
- Construct parameterized circuits with explicit angle substitution per iteration
- Decompose Hamiltonians into individual Pauli measurements
- Implement gradient computation by duplicating circuit evaluations
- Wire up classical optimizers with no standard interface
This is error-prone and leaves significant performance on the table, since a state vector simulator can compute exact expectation values in a single pass without sampling overhead.
Circuits accept symbolic parameters that are resolved to numeric values per evaluation. This avoids circuit reconstruction on each VQE iteration.
┌──────────────────────────────────────────────────┐
│ Parameterized Circuit │
│ │
│ ┌─────┐ ┌──────────┐ ┌─────┐ ┌──────────┐ │
|0> ─────────┤ │ H ├──┤ Ry(θ[0]) ├──┤ CX ├──┤ Rz(θ[2]) ├──┤───
│ └─────┘ └──────────┘ └──┬──┘ └──────────┘ │
│ │ │
|0> ─────────┤──────────────────────────────●───── Ry(θ[1]) ────┤───
│ │
└──────────────────────────────────────────────────┘
│
▼
parameters: [θ[0], θ[1], θ[2]]
values: [0.54, 1.23, -0.87]
Data model:
/// A symbolic parameter in a quantum circuit.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Parameter {
pub name: String,
pub index: usize,
}
/// A gate that may reference symbolic parameters.
pub enum ParameterizedGate {
/// Fixed gate (no parameters)
Fixed(Gate),
/// Rotation gate with a symbolic angle
Rx(ParameterExpr),
Ry(ParameterExpr),
Rz(ParameterExpr),
/// Parameterized two-qubit gate
Rzz(ParameterExpr, Qubit, Qubit),
}
/// Expression for a gate parameter (supports linear combinations).
pub enum ParameterExpr {
/// Direct parameter reference: θ[i]
Param(usize),
/// Scaled parameter: c * θ[i]
Scaled(f64, usize),
/// Sum of expressions
Sum(Box<ParameterExpr>, Box<ParameterExpr>),
/// Constant value
Constant(f64),
}Resolution: When evaluate(params: &[f64]) is called, each ParameterExpr is resolved
to a concrete f64, and the corresponding unitary matrix is computed. This happens once per
VQE iteration and is negligible compared to state vector manipulation.
The Hamiltonian is represented as a sum of weighted Pauli strings:
H = c_0 * I + c_1 * Z_0 + c_2 * Z_1 + c_3 * Z_0 Z_1 + c_4 * X_0 X_1 + ...
where each term is a tensor product of single-qubit Pauli operators {I, X, Y, Z}.
/// A single Pauli operator on one qubit.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Pauli {
I,
X,
Y,
Z,
}
/// A Pauli string: tensor product of single-qubit Paulis.
/// Stored as a compact bitfield for n-qubit systems.
///
/// Encoding: 2 bits per qubit (00=I, 01=X, 10=Y, 11=Z)
/// For n <= 32 qubits, fits in a single u64.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct PauliString {
/// Packed Pauli operators (2 bits each)
pub ops: Vec<u64>,
/// Number of qubits
pub n_qubits: usize,
}
/// A Hamiltonian as a sum of weighted Pauli strings.
///
/// H = sum_j c_j P_j
pub struct PauliSum {
/// Terms: (coefficient, Pauli string)
pub terms: Vec<(Complex64, PauliString)>,
/// Number of qubits
pub n_qubits: usize,
}Optimization: Identity terms (all-I Pauli strings) contribute a constant energy offset and require no state vector computation. The implementation detects and separates these before the expectation loop.
This is the critical performance advantage of state vector simulation over real hardware. On physical quantum computers, expectation values must be estimated via repeated measurement (shot-based sampling), requiring O(1/epsilon^2) shots for epsilon precision.
In a state vector simulator, we compute the exact expectation value:
<psi| H |psi> = sum_j c_j * <psi| P_j |psi>
For each Pauli string P_j, the expectation value is:
<psi| P_j |psi> = sum_k psi_k* (P_j |psi>)_k
Since P_j is a tensor product of single-qubit Paulis, its action on a basis state |k> is:
- I: |k> -> |k>
- X: flips qubit, no phase
- Y: flips qubit, phase factor +/- i
- Z: no flip, phase factor +/- 1
This means each Pauli string maps each basis state to exactly one other basis state with a phase factor. The expectation value reduces to a sum over 2^n amplitudes.
impl QuantumState {
/// Compute the exact expectation value of a PauliSum.
///
/// Complexity: O(T * 2^n) where T = number of Pauli terms, n = qubits.
/// For a 12-qubit system with 100 Pauli terms:
/// 100 * 4096 = 409,600 operations ~ 0.5ms
pub fn expectation(&self, hamiltonian: &PauliSum) -> f64 {
let mut total = 0.0_f64;
for (coeff, pauli) in &hamiltonian.terms {
let mut term_val = Complex64::zero();
for k in 0..self.amplitudes.len() {
// Compute P_j |k>: determine target index and phase
let (target_idx, phase) = pauli.apply_to_basis(k);
// <k| P_j |psi> = phase * psi[target_idx]
// Accumulate psi[k]* * phase * psi[target_idx]
term_val += self.amplitudes[k].conj()
* phase
* self.amplitudes[target_idx];
}
total += (coeff * term_val).re;
}
total
}
}Function signature: QuantumState::expectation(PauliSum) -> f64
| Method | Precision | Evaluations | 12-qubit Cost |
|---|---|---|---|
| Shot-based (1000 shots) | ~3% | 1000 circuit runs per term | ~500ms |
| Shot-based (10000 shots) | ~1% | 10000 circuit runs per term | ~5s |
| Shot-based (1M shots) | ~0.1% | 1M circuit runs per term | ~500s |
| Exact (state vector) | Machine epsilon | 1 pass over state | ~0.5ms |
For VQE convergence, exact expectation values eliminate the statistical noise floor that plagues hardware-based VQE. Classical optimizers receive clean gradients, leading to:
- Faster convergence (fewer iterations)
- No barren plateau artifacts from shot noise
- Deterministic reproducibility
The parameter-shift rule provides exact analytic gradients for parameterized quantum gates. For a gate with parameter theta:
d/d(theta) <H> = [<H>(theta + pi/2) - <H>(theta - pi/2)] / 2
This requires two circuit evaluations per parameter per gradient component.
/// Compute the gradient of the expectation value with respect to all parameters.
///
/// Uses the parameter-shift rule:
/// grad_i = [E(theta_i + pi/2) - E(theta_i - pi/2)] / 2
///
/// Complexity: O(2 * n_params * circuit_eval_cost)
/// For 12 qubits, 20 parameters, 100 Pauli terms:
/// 2 * 20 * (circuit_sim + expectation) ~ 40 * 1ms = 40ms
pub fn gradient(
circuit: &ParameterizedCircuit,
hamiltonian: &PauliSum,
params: &[f64],
) -> Vec<f64> {
let n_params = params.len();
let mut grad = vec![0.0; n_params];
let shift = std::f64::consts::FRAC_PI_2; // pi/2
for i in 0..n_params {
// Forward shift
let mut params_plus = params.to_vec();
params_plus[i] += shift;
let e_plus = evaluate_energy(circuit, hamiltonian, ¶ms_plus);
// Backward shift
let mut params_minus = params.to_vec();
params_minus[i] -= shift;
let e_minus = evaluate_energy(circuit, hamiltonian, ¶ms_minus);
grad[i] = (e_plus - e_minus) / 2.0;
}
grad
}A trait-based abstraction supports plugging in different classical optimizers without changing the VQE loop:
/// Trait for classical optimizers used in the VQE outer loop.
pub trait ClassicalOptimizer: Send {
/// Initialize the optimizer with the parameter count.
fn initialize(&mut self, n_params: usize);
/// Propose next parameter values given current energy and optional gradient.
fn step(
&mut self,
params: &[f64],
energy: f64,
gradient: Option<&[f64]>,
) -> OptimizerResult;
/// Check if the optimizer has converged.
fn has_converged(&self) -> bool;
/// Get optimizer name for logging.
fn name(&self) -> &str;
}
/// Result of an optimizer step.
pub struct OptimizerResult {
pub new_params: Vec<f64>,
pub converged: bool,
pub iteration: usize,
}Provided implementations:
| Optimizer | Type | Gradient Required | Best For |
|---|---|---|---|
GradientDescent |
Gradient-based | Yes | Simple landscapes |
Adam |
Adaptive gradient | Yes | Noisy gradients, deep circuits |
LBFGS |
Quasi-Newton | Yes | Smooth landscapes, fast convergence |
COBYLA |
Derivative-free | No | Non-differentiable cost functions |
NelderMead |
Simplex | No | Low-dimensional problems |
SPSA |
Stochastic | No | Shot-based mode, noisy evaluations |
The complete VQE algorithm proceeds as follows:
VQE Iteration Loop
==================
Input: Hamiltonian H (PauliSum), Ansatz A (ParameterizedCircuit),
Optimizer O (ClassicalOptimizer), initial params theta_0
Output: Minimum energy E_min, optimal params theta_opt
theta = theta_0
O.initialize(len(theta))
repeat:
┌─────────────────────────────────────────────┐
│ 1. PREPARE STATE │
│ |psi(theta)> = A(theta) |0...0> │
│ [Simulate parameterized circuit] │
│ Cost: O(G * 2^n) where G = gate count │
└─────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────┐
│ 2. EVALUATE ENERGY │
│ E = <psi(theta)| H |psi(theta)> │
│ [Direct state vector expectation] │
│ Cost: O(T * 2^n) where T = Pauli terms │
└─────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────┐
│ 3. COMPUTE GRADIENT (if optimizer needs it) │
│ grad = parameter_shift(A, H, theta) │
│ [2 * n_params circuit evaluations] │
│ Cost: O(2P * (G + T) * 2^n) │
└─────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────┐
│ 4. CLASSICAL UPDATE │
│ theta_new = O.step(theta, E, grad) │
│ [Pure classical computation] │
│ Cost: O(P^2) for quasi-Newton │
└─────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────┐
│ 5. CONVERGENCE CHECK │
│ if |E_new - E_old| < tol: STOP │
│ else: theta = theta_new, continue │
└─────────────────────────────────────────────┘
return (E_min, theta_opt)
Pseudocode:
pub fn vqe(
ansatz: &ParameterizedCircuit,
hamiltonian: &PauliSum,
optimizer: &mut dyn ClassicalOptimizer,
config: &VqeConfig,
) -> VqeResult {
let n_params = ansatz.parameter_count();
let mut params = config.initial_params.clone()
.unwrap_or_else(|| vec![0.0; n_params]);
optimizer.initialize(n_params);
let mut best_energy = f64::INFINITY;
let mut best_params = params.clone();
let mut history = Vec::new();
for iteration in 0..config.max_iterations {
// Step 1+2: Simulate circuit and compute energy
let state = ansatz.simulate(¶ms);
let energy = state.expectation(hamiltonian);
// Track best
if energy < best_energy {
best_energy = energy;
best_params = params.clone();
}
// Step 3: Compute gradient if needed
let grad = if optimizer.needs_gradient() {
Some(gradient(ansatz, hamiltonian, ¶ms))
} else {
None
};
history.push(VqeIteration { iteration, energy, params: params.clone() });
// Step 4: Classical update
let result = optimizer.step(¶ms, energy, grad.as_deref());
params = result.new_params;
// Step 5: Convergence check
if result.converged || (iteration > 0 &&
(history[iteration].energy - history[iteration - 1].energy).abs()
< config.convergence_threshold) {
break;
}
}
VqeResult {
energy: best_energy,
optimal_params: best_params,
iterations: history.len(),
history,
converged: optimizer.has_converged(),
}
}For mimicking real hardware behavior and testing noise resilience:
/// Configuration for shot-based VQE mode.
pub struct ShotConfig {
/// Number of measurement shots per expectation estimation
pub shots: usize,
/// Random seed for reproducibility
pub seed: Option<u64>,
/// Readout error rate (probability of bit flip on measurement)
pub readout_error: f64,
}
impl QuantumState {
/// Estimate expectation value via shot-based sampling.
///
/// Samples the state `shots` times in the computational basis,
/// then computes the empirical expectation of each Pauli term.
pub fn expectation_sampled(
&self,
hamiltonian: &PauliSum,
config: &ShotConfig,
) -> (f64, f64) {
// Returns (mean, standard_error)
// Standard error = std_dev / sqrt(shots)
todo!()
}
}Pre-built ansatz constructors for common use cases:
Hardware-Efficient Ansatz (depth d, n qubits):
Layer 1..d:
┌─────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
┤ Ry ├──┤ Rz ├──┤ CNOT ├──┤ Ry ├──
└─────┘ └──────────┘ │ ladder │ └──────────┘
┌─────┐ ┌──────────┐ │ │ ┌──────────┐
┤ Ry ├──┤ Rz ├──┤ ├──┤ Ry ├──
└─────┘ └──────────┘ └──────────┘ └──────────┘
Parameters per layer: 3n (Ry + Rz + Ry per qubit)
Total parameters: 3nd
/// Pre-built ansatz constructors.
pub mod ansatz {
/// Hardware-efficient ansatz with Ry-Rz layers and linear CNOT entanglement.
pub fn hardware_efficient(n_qubits: usize, depth: usize) -> ParameterizedCircuit;
/// UCCSD (Unitary Coupled Cluster Singles and Doubles) for chemistry.
/// Generates excitation operators based on active space.
pub fn uccsd(n_electrons: usize, n_orbitals: usize) -> ParameterizedCircuit;
/// Hamiltonian variational ansatz: layers of exp(-i * theta_j * P_j)
/// for each term P_j in the Hamiltonian.
pub fn hamiltonian_variational(
hamiltonian: &PauliSum,
depth: usize,
) -> ParameterizedCircuit;
/// Symmetry-preserving ansatz that respects particle number conservation.
pub fn symmetry_preserving(
n_qubits: usize,
n_particles: usize,
depth: usize,
) -> ParameterizedCircuit;
}| Component | Operations | Time |
|---|---|---|
| State vector size | 2^12 = 4,096 complex amplitudes | 64 KB |
| Circuit simulation (50 gates) | 50 * 4096 = 204,800 ops | ~0.3ms |
| Expectation (100 Pauli terms) | 100 * 4096 = 409,600 ops | ~0.5ms |
| Gradient (20 params) | 40 * (0.3 + 0.5) ms | ~32ms |
| Classical optimizer step | O(20^2) | ~0.001ms |
| Total per iteration (with gradient) | ~33ms | |
| Total per iteration (no gradient) | ~0.8ms |
For gradient-free optimizers (COBYLA, Nelder-Mead), a 12-qubit VQE iteration completes in under 1ms. With parameter-shift gradients, the cost scales linearly with parameter count but remains under 50ms for typical chemistry ansatze.
Scaling with qubit count:
| Qubits | State Size | Memory | Energy Eval (100 terms) | Gradient (20 params) |
|---|---|---|---|---|
| 8 | 256 | 4 KB | ~0.03ms | ~2ms |
| 12 | 4,096 | 64 KB | ~0.5ms | ~33ms |
| 16 | 65,536 | 1 MB | ~8ms | ~500ms |
| 20 | 1,048,576 | 16 MB | ~130ms | ~8s |
| 24 | 16,777,216 | 256 MB | ~2s | ~130s |
| 28 | 268,435,456 | 4 GB | ~33s | ~35min |
ruVector agents can drive autonomous chemistry optimization using VQE as the evaluation kernel:
┌─────────────────────────────────────────────────────────────────┐
│ ruVector Agent Orchestration │
│ │
│ ┌──────────┐ ┌──────────────┐ ┌────────────────────┐ │
│ │ Research │───>│ Architecture │───>│ Chemistry Agent │ │
│ │ Agent │ │ Agent │ │ │ │
│ │ │ │ │ │ - Molecule spec │ │
│ │ Literature│ │ Hamiltonian │ │ - Basis set sel. │ │
│ │ search │ │ generation │ │ - Active space │ │
│ └──────────┘ └──────────────┘ │ - VQE execution │ │
│ │ - Result analysis │ │
│ └────────┬───────────┘ │
│ │ │
│ ┌────────▼───────────┐ │
│ │ ruQu VQE Engine │ │
│ │ │ │
│ │ Parameterized │ │
│ │ Circuit + PauliSum│ │
│ │ + Optimizer │ │
│ └────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
The agent workflow:
- Research agent retrieves molecular structure and prior computational results
- Architecture agent generates the qubit Hamiltonian (Jordan-Wigner or Bravyi-Kitaev transformation from fermionic operators)
- Chemistry agent selects ansatz, optimizer, and runs VQE iterations
- Results are stored in ruVector memory for pattern learning across molecules
- Exact expectation values eliminate sampling noise, enabling faster convergence and deterministic reproducibility -- a major advantage over hardware VQE
- Symbolic parameterization avoids circuit reconstruction overhead, reducing per-iteration cost to pure state manipulation
- Trait-based optimizer interface allows users to swap optimizers without touching VQE logic, and supports custom optimizer implementations
- Hardware-efficient ansatz library provides tested, production-quality circuit templates for common use cases
- Gradient support via parameter-shift rule enables modern gradient-based optimization (Adam, L-BFGS) that converges significantly faster than derivative-free methods
- Agent integration enables autonomous, memory-enhanced chemistry exploration that learns from prior VQE runs across molecular configurations
| Risk | Probability | Impact | Mitigation |
|---|---|---|---|
| Exponential memory scaling limits qubit count | High | Medium | Tensor network backend for >30 qubits (future ADR) |
| Parameter-shift gradient cost scales with parameter count | Medium | Medium | Batched gradient evaluation, simultaneous perturbation (SPSA) fallback |
| Hamiltonian term count explosion for large molecules | Medium | High | Pauli grouping (qubit-wise commuting), measurement reduction techniques |
| Optimizer convergence to local minima | Medium | Medium | Multi-start strategies, QAOA-inspired initialization |
| Decision | Advantage | Disadvantage |
|---|---|---|
| Exact expectation over sampling | Machine-precision accuracy | Not representative of real hardware noise |
| Parameter-shift over finite-difference | Exact gradients | 2x evaluations per parameter |
| Trait-based optimizer | Extensible | Slight abstraction overhead |
| Compact PauliString bitfield | Cache-friendly | Complex bit manipulation logic |
- Peruzzo, A. et al. "A variational eigenvalue solver on a photonic quantum processor." Nature Communications 5, 4213 (2014)
- McClean, J.R. et al. "The theory of variational hybrid quantum-classical algorithms." New Journal of Physics 18, 023023 (2016)
- Kandala, A. et al. "Hardware-efficient variational quantum eigensolver for small molecules." Nature 549, 242-246 (2017)
- Schuld, M. et al. "Evaluating analytic gradients on quantum hardware." Physical Review A 99, 032331 (2019)
- ADR-001: ruQu Architecture - Classical Nervous System for Quantum Machines
- ADR-QE-001 through ADR-QE-004: Prior quantum engine architecture decisions
- ruQu crate:
crates/ruQu/src/- existing syndrome processing and coherence gate infrastructure - ruVector memory system: pattern storage for cross-molecule VQE learning