Tutorial 8: Variational Quantum Eigensolver (VQE)¶
Time: 30 minutes
Level: Advanced
Concepts: Hybrid quantum-classical algorithms, parameterized circuits, optimization
Try it interactively
This tutorial is also available as a Jupyter notebook you can run locally:
Open 15_vqe.ipynb
What is VQE?¶
The Variational Quantum Eigensolver finds the ground state energy of a quantum system (like a molecule) using a hybrid approach:
- Quantum computer: Prepares a parameterized trial state and measures energy
- Classical computer: Optimizes the parameters to minimize energy
This is a NISQ-era algorithm — designed to work on near-term noisy hardware.
The Variational Principle¶
For any trial state \(|\psi(\theta)\rangle\):
where \(E_0\) is the true ground state energy. By minimizing \(E(\theta)\), we approach \(E_0\).
Implementation: Simple 2-Qubit VQE¶
We'll find the ground state of a simple Hamiltonian: \(H = Z_0 Z_1 + 0.5 X_0 + 0.5 X_1\)
import quantsdk as qs
import math
import numpy as np
def ansatz(params: list[float]) -> qs.Circuit:
"""Create a parameterized trial state (ansatz).
Args:
params: List of 4 rotation angles.
"""
circuit = qs.Circuit(2, name="vqe-ansatz")
circuit.ry(0, params[0])
circuit.ry(1, params[1])
circuit.cx(0, 1)
circuit.ry(0, params[2])
circuit.ry(1, params[3])
return circuit
def measure_zz(params: list[float], shots: int = 4096) -> float:
"""Measure <Z0 Z1> expectation value."""
circuit = ansatz(params)
circuit.measure_all()
result = qs.run(circuit, shots=shots)
# Calculate <ZZ>: Z|0>=+1, Z|1>=-1
exp_val = 0.0
for bitstring, count in result.counts.items():
z0 = 1 - 2 * int(bitstring[0])
z1 = 1 - 2 * int(bitstring[1])
exp_val += z0 * z1 * count
return exp_val / shots
def measure_x(qubit: int, params: list[float], shots: int = 4096) -> float:
"""Measure <X> on a specific qubit."""
circuit = ansatz(params)
# Rotate to X basis: H then measure
circuit.h(qubit)
circuit.measure_all()
result = qs.run(circuit, shots=shots)
exp_val = 0.0
for bitstring, count in result.counts.items():
x_val = 1 - 2 * int(bitstring[qubit])
exp_val += x_val * count
return exp_val / shots
def energy(params: list[float]) -> float:
"""Compute <H> = <Z0Z1> + 0.5*<X0> + 0.5*<X1>."""
zz = measure_zz(params)
x0 = measure_x(0, params)
x1 = measure_x(1, params)
return zz + 0.5 * x0 + 0.5 * x1
Classical Optimization Loop¶
# Simple gradient-free optimization (coordinate descent)
best_params = [0.0, 0.0, 0.0, 0.0]
best_energy = energy(best_params)
print(f"Initial energy: {best_energy:.4f}")
learning_rate = 0.3
for iteration in range(50):
for i in range(len(best_params)):
# Try small perturbation
trial_params = best_params.copy()
trial_params[i] += learning_rate
e_plus = energy(trial_params)
trial_params[i] -= 2 * learning_rate
e_minus = energy(trial_params)
# Update in direction of lower energy
if e_plus < best_energy:
best_params[i] += learning_rate
best_energy = e_plus
elif e_minus < best_energy:
best_params[i] -= learning_rate
best_energy = e_minus
if iteration % 10 == 0:
print(f"Iteration {iteration}: energy = {best_energy:.4f}")
print(f"Final energy: {best_energy:.4f}")
print(f"Optimal params: {[f'{p:.3f}' for p in best_params]}")
Ansatz Design¶
The choice of ansatz is crucial. Common ansatz types:
| Ansatz | Description | Use Case |
|---|---|---|
| Hardware-efficient | Matches native gate set | General NISQ |
| UCCSD | Chemistry-inspired | Molecular simulation |
| QAOA | Problem-structure aware | Optimization |
Key Takeaways¶
- VQE is a hybrid algorithm — quantum state prep + classical optimization
- The ansatz defines the search space for trial states
- Shot noise makes energy estimates noisy — need many shots
- Works on near-term hardware (NISQ) with limited qubits and depth
- Applications: molecular chemistry, materials science, optimization
Next Tutorial¶
QAOA — quantum approximate optimization for combinatorial problems.