Cada vez que ejecutas un circuito cuántico en hardware real, cuesta tiempo de QPU y dinero. Los algoritmos variacionales como VQE y QAOA pueden requerir cientos de miles de ejecuciones de circuito para converger; a $0.075–$0.90 por tarea en QPU en la nube, eso se acumula rápidamente.
La buena noticia: la mayoría de las implementaciones por defecto son tremendamente ineficientes en disparos. Con las técnicas adecuadas, puedes reducir los requisitos de disparos en un 50–90% sin sacrificar la calidad de los resultados.
Por qué el número de disparos es tan alto por defecto
VQE calcula valores esperados de operadores de Pauli. Un hamiltoniano se descompone en una suma de términos de Pauli, y cada término requiere una ejecución de circuito separada. Para una molécula como H₂ (4 qubits), hay ~15 términos de Pauli. Para moléculas más grandes el número se dispara:
| Molécula | Qubits | Términos de Pauli | Disparos ingenuos/iteración |
|---|---|---|---|
| H₂ | 4 | 15 | 15,000 |
| LiH | 12 | 631 | 631,000 |
| BeH₂ | 14 | 666 | 666,000 |
| H₂O | 14 | 1,086 | 1,086,000 |
Con 200 iteraciones del optimizador, H₂O requiere de forma ingenua 217 millones de disparos. Las técnicas siguientes reducen esto en un 80–95%.
Técnica 1: Agrupación de mediciones (la mayor ganancia)
Muchos términos de Pauli conmutan: pueden medirse simultáneamente en una sola ejecución de circuito en lugar de por separado. Agrupar observables que conmutan es la mayor reducción disponible por sí sola.
from qiskit.primitives import StatevectorEstimator
from qiskit_nature.second_q.mappers import JordanWignerMapper
from qiskit_algorithms import VQE
from qiskit_algorithms.optimizers import COBYLA
# Qiskit automatically groups commuting Paulis in the Estimator primitive
# This reduces shots from O(n_terms) to O(n_groups) — often 5-10x reduction
estimator = StatevectorEstimator()
# With PennyLane, use grouping explicitly:
import pennylane as qml
H = qml.Hamiltonian(coeffs, observables)
# Group commuting terms — usually reduces term count by 5-10x
groups = qml.grouping.group_observables(observables, grouping_type='qwc')
print(f"Original terms: {len(observables)}, Groups: {len(groups)}")
# Original terms: 631, Groups: 68 (for LiH)
Ahorro esperado: 5–15× en hamiltonianos de química típicos.
Técnica 2: Optimizadores frugales en disparos
Los optimizadores clásicos como L-BFGS-B o ADAM asumen evaluaciones de función sin ruido: solicitan más evaluaciones de gradiente de las necesarias cuando los resultados son ruidosos. Los optimizadores frugales en disparos asignan disparos de forma adaptativa según la varianza de las mediciones.
from pennylane.optimize import AdaptiveOptimizer, ShotAdaptiveOptimizer
dev = qml.device("default.qubit", wires=4)
@qml.qnode(dev)
def circuit(params):
# ansatz
...
return qml.expval(H)
# ShotAdaptiveOptimizer: allocates more shots to high-variance directions
opt = ShotAdaptiveOptimizer(min_shots=10)
params = np.random.uniform(-np.pi, np.pi, n_params)
for i in range(100):
params, _, shots_used = opt.step_and_cost(circuit, params)
print(f"Step {i}: shots used = {shots_used}")
El optimizador comienza con pocos disparos por evaluación y los aumenta solo cuando el gradiente es incierto. Una ejecución típica de VQE usa 5–10× menos disparos frente a COBYLA con disparos fijos.
Técnica 3: Gradientes por desplazamiento de parámetros (usa menos evaluaciones)
La estimación ingenua de gradiente por diferencias finitas (f(x+ε) - f(x))/ε tiene alta varianza con ε pequeño y gran sesgo con ε grande. La regla de desplazamiento de parámetros da gradientes exactos con solo 2 evaluaciones de circuito por parámetro:
# PennyLane uses parameter-shift by default for gradients
@qml.qnode(dev, diff_method="parameter-shift") # 2 evals per param
def circuit(params):
...
# Compare to finite-difference (requires 1 eval per param but biased):
@qml.qnode(dev, diff_method="finite-diff") # 1 eval but approximate
# For large circuits, use "best" — PennyLane chooses adjoint on simulator,
# parameter-shift on hardware
@qml.qnode(dev, diff_method="best")
def circuit(params):
...
Con n parámetros, el desplazamiento de parámetros cuesta 2n evaluaciones por paso de gradiente. Usa optimizadores sin gradiente (COBYLA, SPSA, Nelder-Mead) cuando n sea grande.
Técnica 4: SPSA — Estimación estocástica de gradientes
La Aproximación Estocástica por Perturbación Simultánea (SPSA) estima el gradiente completo con solo 2 evaluaciones de circuito independientemente del número de parámetros, perturbando todos los parámetros simultáneamente:
from qiskit_algorithms.optimizers import SPSA
# SPSA: 2 evaluations per step regardless of parameter count
# vs parameter-shift: 2n evaluations per step
optimizer = SPSA(maxiter=300, learning_rate=0.1, perturbation=0.05)
# For 10 parameters:
# - Parameter shift: 2×10 = 20 evals/step × 300 steps = 6,000 total
# - SPSA: 2 evals/step × 300 steps = 600 total ← 10x reduction
Mejor cuando: el circuito tiene muchos parámetros (> 10). El compromiso es una convergencia más lenta por paso, pero menos disparos totales.
Técnica 5: Terminación temprana + umbral de varianza
No ejecutes los circuitos hasta el final cuando el resultado ya es suficientemente bueno:
from pennylane.optimize import AdamOptimizer
import numpy as np
opt = AdamOptimizer(stepsize=0.02)
params = init_params.copy()
prev_energy = float('inf')
for step in range(max_steps):
params, energy = opt.step_and_cost(circuit, params)
# Stop when change is below shot-noise floor
variance = 1.0 / np.sqrt(shots_per_eval) # shot noise floor
if abs(energy - prev_energy) < variance:
print(f"Converged at step {step}")
break
prev_energy = energy
Muchas ejecuciones de VQE se estabilizan pronto; continuar solo desperdicia disparos en fluctuaciones de ruido.
Técnica 6: Arranque en caliente
Inicializa los parámetros de QAOA o VQE a partir de una solución clásica relacionada en lugar de valores aleatorios:
# For QAOA on Max-Cut: warm start from a greedy classical solution
import networkx as nx
G = nx.from_edgelist(edges)
classical_cut = nx.algorithms.approximation.one_exchange(G)
# Map classical solution to initial QAOA angles
# γ₀ ≈ π/4 for a good cut, β₀ ≈ π/8
init_gamma = [np.pi / 4]
init_beta = [np.pi / 8]
# Warm-started QAOA typically converges in 30-50% fewer iterations
Combinándolo todo: una plantilla práctica de VQE
import pennylane as qml
import numpy as np
from pennylane.optimize import ShotAdaptiveOptimizer
dev = qml.device("default.qubit", wires=n_qubits, shots=512)
# 1. Group commuting terms (5-10x reduction in circuit count)
grouped_H = qml.Hamiltonian(*qml.grouping.group_observables(H))
@qml.qnode(dev, diff_method="parameter-shift")
def ansatz(params):
# Hardware-efficient ansatz
for i in range(n_qubits):
qml.RY(params[i], wires=i)
for i in range(n_qubits - 1):
qml.CNOT(wires=[i, i + 1])
return qml.expval(grouped_H)
# 2. Use shot-adaptive optimizer
opt = ShotAdaptiveOptimizer(min_shots=50)
# 3. Warm start
params = warm_start_params(H)
# 4. Run with early stopping
for step in range(300):
params, energy, shots = opt.step_and_cost(ansatz, params)
if check_convergence(energy, shots):
break
print(f"Ground state energy: {energy:.4f} Ha")
Referencia rápida: presupuesto de disparos por método
| Método | Disparos/paso | Mejor para |
|---|---|---|
| COBYLA + disparos fijos | n_terms × shots | Pocos parámetros |
| Desplazamiento de parámetros + Adam | 2n_params × shots | Circuitos diferenciables |
| SPSA | 2 × shots | Muchos parámetros |
| ShotAdaptiveOptimizer | Adaptativo | VQE general |
| Paulis agrupados | ÷5–15× | Aplicar siempre primero |
Aplica primero la agrupación de mediciones: es la mayor ganancia individual y no requiere cambios en tu optimizador ni en tu circuito.
Relacionado: VQE con PennyLane · Guía de acceso a QPU · HLQuantum