Inicio/Blog/Construyendo un VQE con PennyLane: Una guía práctica
VQEPennyLaneAlgorithmsQML

Construyendo un VQE con PennyLane: Una guía práctica

Implementación paso a paso del Variational Quantum Eigensolver usando PennyLane — desde la configuración del hamiltoniano hasta el bucle de optimización clásica.

FreeQuantumComputing
·· 9 min read

El Variational Quantum Eigensolver (VQE) es uno de los algoritmos cuánticos a corto plazo más importantes. Encuentra la energía del estado fundamental de una molécula o un material — un cálculo que es exponencialmente difícil para las computadoras clásicas pero abordable en dispositivos NISQ. Esta guía recorre una implementación completa de VQE en PennyLane.

Qué hace VQE

VQE encuentra el valor propio mínimo de un hamiltoniano H (que normalmente representa la energía de una molécula). Funciona mediante:

  1. Preparar un estado de prueba parametrizado |ψ(θ)⟩ usando un circuito cuántico
  2. Medir el valor esperado ⟨ψ(θ)|H|ψ(θ)⟩ en la QPU
  3. Usar un optimizador clásico para actualizar θ y minimizar la energía
  4. Repetir hasta la convergencia

El principio variacional garantiza que ⟨ψ(θ)|H|ψ(θ)⟩ ≥ E₀ para cualquier estado — por lo que minimizar esta cantidad da una cota superior de la verdadera energía del estado fundamental E₀.

Configuración

Instala PennyLane con su complemento de química:

pip install pennylane pennylane-qchem

Para la molécula de hidrógeno (H₂) — el benchmark clásico de VQE — necesitamos dos electrones y cuatro espín-orbitales (4 qubits):

import pennylane as qml
from pennylane import numpy as np
import pennylane.qchem as qchem

# H2 at equilibrium bond length (Angstrom)
symbols = ["H", "H"]
coordinates = np.array([[0.0, 0.0, -0.6614], [0.0, 0.0, 0.6614]])

# Build the qubit Hamiltonian
H, qubits = qchem.molecular_hamiltonian(
    symbols,
    coordinates,
    basis="sto-3g"
)
print(f"Hamiltonian: {len(H.ops)} terms, {qubits} qubits")
# Hamiltonian: 15 terms, 4 qubits

Definiendo el ansatz

El ansatz es el circuito parametrizado que prepara el estado de prueba. Para problemas de química, el ansatz UCCSD (Unitary Coupled-Cluster Singles and Doubles) es el estándar:

# Get UCCSD circuit parameters
electrons = 2
singles, doubles = qchem.excitations(electrons, qubits)
s_wires, d_wires = qchem.excitations_to_wires(singles, doubles, wires=range(qubits))

# Initial Hartree-Fock state (reference state)
hf_state = qchem.hf_state(electrons, qubits)

dev = qml.device("default.qubit", wires=qubits)

@qml.qnode(dev)
def circuit(weights, wires, s_wires=[], d_wires=[], hf_state=hf_state):
    # Prepare HF reference state
    qml.BasisState(hf_state, wires=wires)

    # Apply UCCSD excitations
    qml.UCCSD(weights, wires, s_wires=s_wires, d_wires=d_wires, init_state=hf_state)

    return qml.expval(H)

Ejecutando la optimización de VQE

Con la diferenciación automática de PennyLane, podemos usar optimizadores basados en gradientes directamente:

# Initial parameters (all zeros = Hartree-Fock state)
init_params = np.zeros(len(singles) + len(doubles), requires_grad=True)

# Adam optimizer (works well for VQE)
opt = qml.AdamOptimizer(stepsize=0.4)

# Optimization loop
energy_history = []
params = init_params.copy()

for step in range(200):
    params, energy = opt.step_and_cost(
        lambda p: circuit(p, range(qubits), s_wires=s_wires, d_wires=d_wires),
        params
    )
    energy_history.append(energy)

    if step % 20 == 0:
        print(f"Step {step:3d}: E = {energy:.6f} Ha")

print(f"\nVQE ground state energy: {energy:.6f} Ha")
print(f"Reference (exact): -1.136189 Ha")

Salida típica:

Step   0: E = -1.117498 Ha
Step  20: E = -1.133254 Ha
Step  40: E = -1.135901 Ha
Step  60: E = -1.136140 Ha
...
VQE ground state energy: -1.136174 Ha
Reference (exact): -1.136189 Ha

VQE alcanza un valor dentro de ~0.015 mHa de la energía exacta — precisión química para H₂.

Usando un optimizador sin gradientes

Para hardware ruidoso, los optimizadores sin gradientes como COBYLA o SPSA suelen ser mejores, ya que los gradientes del hardware son ruidosos:

from scipy.optimize import minimize

# Objective function (no gradient needed)
def objective(params):
    return float(circuit(params, range(qubits), s_wires=s_wires, d_wires=d_wires))

result = minimize(
    objective,
    x0=init_params,
    method="COBYLA",
    options={"maxiter": 500, "rhobeg": 0.1}
)

print(f"COBYLA energy: {result.fun:.6f} Ha")

Ejecutando VQE con HLQuantum

HLQuantum incluye una implementación de VQE integrada que funciona en todos los backends:

import hlquantum as hlq

# Define the Hamiltonian in HLQuantum's format
H = hlq.hamiltonians.h2_molecule(bond_length=1.32)

# Run VQE on any backend
result = hlq.algorithms.vqe(
    hamiltonian=H,
    ansatz="uccsd",
    optimizer="adam",
    max_iterations=200,
    backend="pennylane",   # or "qiskit", "cudaq"
)

print(f"Ground state energy: {result.energy:.6f} Ha")
print(f"Optimal parameters: {result.params}")
print(f"Converged in {result.iterations} iterations")

Consejos para hardware real

Al ejecutar VQE en QPUs reales (IBM Quantum, IonQ), se aplican varias consideraciones adicionales:

Usa menos shots por paso. 1000 shots por paso de optimización suele ser suficiente para la estimación del gradiente. No uses 10.000 shots en cada paso — desperdicia tiempo de QPU.

Empieza con circuitos poco profundos. Menos puertas CNOT = menos ruido. Para hardware, considera circuitos de ansatz eficientes en hardware en lugar de UCCSD.

Habilita la mitigación de errores. El error_mitigation="zne" de HLQuantum aplica Zero Noise Extrapolation, que puede mejorar significativamente los resultados en hardware ruidoso:

result = hlq.run(vqe_circuit, backend="qiskit", device="ibm_sherbrooke",
                 error_mitigation="zne", shots=2048)

Consulta la guía completa de PennyLane y la referencia de algoritmos de HLQuantum para más detalles sobre simulaciones de química cuántica.