Estimación de fase cuántica con el controlador de fase de Q-CTRL ( Qiskit Functions )
Estimación de uso: 40 segundos en un procesador Heron r2. (NOTA: Esto es sólo una estimación. Su tiempo de ejecución puede variar)
En segundo plano
La estimación cuántica de fase (QPE) es un algoritmo fundacional de la computación cuántica que constituye la base de muchas aplicaciones importantes, como el algoritmo de Shor, la estimación de la energía del estado básico de la química cuántica y los problemas de valores propios. QPE estima la fase asociada a un estado propio de un operador unitario, codificada en la relación
y lo determina con una precisión de utilizando qubits contadores [1]. Preparando estos qubits en superposición, aplicando potencias controladas de , y utilizando a continuación la transformada cuántica de Fourier inversa (QFT) para extraer la fase en resultados de medición codificados en binario, la QPE produce una distribución de probabilidad con un pico en las cadenas de bits cuyas fracciones binarias se aproximan a . En el caso ideal, el resultado de medición más probable corresponde directamente a la expansión binaria de la fase, mientras que la probabilidad de otros resultados disminuye rápidamente con el número de qubits de recuento. Sin embargo, ejecutar circuitos QPE profundos en hardware presenta desafíos: el gran número de qubits y operaciones de enredo hacen que el algoritmo sea muy sensible a la decoherencia y a los errores de puerta. Esto da lugar a distribuciones ampliadas y desplazadas de las cadenas de bits, enmascarando la verdadera fase propia. Como consecuencia, es posible que la cadena de bits con mayor probabilidad ya no corresponda a la expansión binaria correcta de .
En este tutorial, presentamos una implementación del algoritmo QPE utilizando las herramientas de supresión de errores y gestión de rendimiento Fire Opal de Q-CTRL, ofrecidas como una función Qiskit (consulte la documentación de Fire Opal ). Fire Opal aplica automáticamente optimizaciones avanzadas, como el desacoplamiento dinámico, mejoras en la disposición de los qubits y técnicas de supresión de errores, lo que se traduce en resultados de mayor fidelidad. Estas mejoras acercan las distribuciones de cadenas de bits de hardware a las obtenidas en simulaciones sin ruido, de modo que se puede identificar con fiabilidad la fase propia correcta incluso bajo los efectos del ruido.
Requisitos
Antes de empezar este tutorial, asegúrate de tener instalado lo siguiente:
- Qiskit SDK v1.4 o posterior, con soporte de visualización
- Qiskit Runtime v0.40 o posterior (
pip install qiskit-ibm-runtime) - Qiskit Functions Catalog v0.9.0 (
pip install qiskit-ibm-catalog) - Fire Opal SDK v9.0.2 o posterior (
pip install fire-opal) - Q-CTRL Visualizer v8.0.2 o posterior (
pip install qctrl-visualizer)
Configuración
Primero, autentíquese utilizando su clave API IBM Quantum. A continuación, selecciona la función de Qiskit de la siguiente manera. (Este código da por hecho que ya has guardado tu cuenta en tu entorno local.)
from qiskit import QuantumCircuit
import numpy as np
import matplotlib.pyplot as plt
import qiskit
from qiskit import qasm2
from qiskit_aer import AerSimulator
from qiskit_ibm_runtime import QiskitRuntimeService
from qiskit_ibm_runtime import SamplerV2 as Sampler
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
import qctrlvisualizer as qv
from qiskit_ibm_catalog import QiskitFunctionsCatalog
plt.style.use(qv.get_qctrl_style())catalog = QiskitFunctionsCatalog(channel="ibm_quantum_platform")
# Access Function
perf_mgmt = catalog.load("q-ctrl/performance-management")Paso 1: Asignar entradas clásicas a un problema cuántico
En este tutorial, ilustramos QPE para recuperar la eigenfase de un unitario single-qubit conocido. El unitario cuya fase queremos estimar es la puerta de fase de un solo qubit aplicada al qubit objetivo:
Preparamos su estado propio . Dado que es un vector propio de con valor propio , la fase propia a estimar es:
Fijamos , de modo que la fase de verdad fundamental es . El circuito QPE implementa las potencias controladas aplicando rotaciones de fase controladas con ángulos , luego aplica la QFT inversa al registro de recuento y lo mide. Las cadenas de bits resultantes se concentran en torno a la representación binaria de .
El circuito utiliza qubits contadores (para establecer la precisión de la estimación) más un qubit objetivo. Empezaremos definiendo los componentes necesarios para implementar la QPE: la transformada cuántica de Fourier (QFT) y su inversa, funciones de utilidad para mapear entre fracciones decimales y binarias de la fase propia, y ayudantes para normalizar los recuentos brutos en probabilidades para comparar los resultados de simulación y hardware.
def inverse_quantum_fourier_transform(quantum_circuit, number_of_qubits):
"""
Apply an inverse Quantum Fourier Transform the first `number_of_qubits` qubits in the
`quantum_circuit`.
"""
for qubit in range(number_of_qubits // 2):
quantum_circuit.swap(qubit, number_of_qubits - qubit - 1)
for j in range(number_of_qubits):
for m in range(j):
quantum_circuit.cp(-np.pi / float(2 ** (j - m)), m, j)
quantum_circuit.h(j)
return quantum_circuitdef bitstring_count_to_probabilities(data, shot_count):
"""
This function turns an unsorted dictionary of bitstring counts into a sorted dictionary
of probabilities.
"""
# Turn the bitstring counts into probabilities.
probabilities = {
bitstring: bitstring_count / shot_count
for bitstring, bitstring_count in data.items()
}
sorted_probabilities = dict(
sorted(probabilities.items(), key=lambda x: x[1], reverse=True)
)
return sorted_probabilitiesPaso 2: Optimizar el problema para la ejecución en hardware cuántico
Construimos el circuito QPE preparando los qubits contadores en superposición, aplicando rotaciones de fase controladas para codificar la fase propia objetivo y terminando con una QFT inversa antes de la medición.
def quantum_phase_estimation_benchmark_circuit(
number_of_counting_qubits, phase
):
"""
Create the circuit for quantum phase estimation.
Parameters
----------
number_of_counting_qubits : The number of qubits in the circuit.
phase : The desired phase.
Returns
-------
QuantumCircuit
The quantum phase estimation circuit for `number_of_counting_qubits` qubits.
"""
qc = QuantumCircuit(
number_of_counting_qubits + 1, number_of_counting_qubits
)
target = number_of_counting_qubits
# |1> eigenstate for the single-qubit phase gate
qc.x(target)
# Hadamards on counting register
for q in range(number_of_counting_qubits):
qc.h(q)
# ONE controlled phase per counting qubit: cp(phase * 2**k)
for k in range(number_of_counting_qubits):
qc.cp(phase * (1 << k), k, target)
qc.barrier()
# Inverse QFT on counting register
inverse_quantum_fourier_transform(qc, number_of_counting_qubits)
qc.barrier()
for q in range(number_of_counting_qubits):
qc.measure(q, q)
return qcPaso 3: Ejecutar utilizando Qiskit primitives
Fijamos el número de disparos y qubits para el experimento, y codificamos la fase objetivo utilizando dígitos binarios. Con estos parámetros, construimos el circuito QPE que se ejecutará en simulación, hardware por defecto y backends mejorados Fire Opal.
shot_count = 10000
num_qubits = 35
phase = (1 / 6) * 2 * np.pi
circuits_quantum_phase_estimation = (
quantum_phase_estimation_benchmark_circuit(
number_of_counting_qubits=num_qubits, phase=phase
)
)Ejecutar simulación MPS
En primer lugar, generamos una distribución de referencia utilizando el simulador matrix_product_state y convertimos los recuentos en probabilidades normalizadas para su posterior comparación con los resultados del hardware.
# Run the algorithm on the IBM Aer simulator.
aer_simulator = AerSimulator(method="matrix_product_state")
# Transpile the circuits for the simulator.
transpiled_circuits = qiskit.transpile(
circuits_quantum_phase_estimation, aer_simulator
)simulated_result = (
aer_simulator.run(transpiled_circuits, shots=shot_count)
.result()
.get_counts()
)simulated_result_probabilities = []
simulated_result_probabilities.append(
bitstring_count_to_probabilities(
simulated_result,
shot_count=shot_count,
)
)Ejecutar en hardware
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False)
pm = generate_preset_pass_manager(backend=backend, optimization_level=3)
isa_circuits = pm.run(circuits_quantum_phase_estimation)# Run the algorithm with IBM default.
sampler = Sampler(backend)
# Run all circuits using Qiskit Runtime.
ibm_default_job = sampler.run([isa_circuits], shots=shot_count)Ejecutar en hardware con Fire Opal
# Run the circuit using Sampler
fire_opal_job = perf_mgmt.run(
primitive="sampler",
pubs=[qasm2.dumps(circuits_quantum_phase_estimation)],
backend_name=backend.name,
options={"default_shots": shot_count},
)Paso 4: Procesamiento posterior y devolución del resultado en el formato clásico deseado
# Retrieve results.
ibm_default_result = ibm_default_job.result()
ibm_default_probabilities = []
for idx, pub_result in enumerate(ibm_default_result):
ibm_default_probabilities.append(
bitstring_count_to_probabilities(
pub_result.data.c0.get_counts(),
shot_count=shot_count,
)
)fire_opal_result = fire_opal_job.result()
fire_opal_probabilities = []
for idx, pub_result in enumerate(fire_opal_result):
fire_opal_probabilities.append(
bitstring_count_to_probabilities(
pub_result.data.c0.get_counts(),
shot_count=shot_count,
)
)data = {
"simulation": simulated_result_probabilities,
"default": ibm_default_probabilities,
"fire_opal": fire_opal_probabilities,
}def plot_distributions(
data,
number_of_counting_qubits,
top_k=None,
by="prob",
shot_count=None,
):
def nrm(d):
s = sum(d.values())
return {k: (v / s if s else 0.0) for k, v in d.items()}
def as_float(d):
return {k: float(v) for k, v in d.items()}
def to_space(d):
if by == "prob":
return nrm(as_float(d))
else:
if shot_count and 0.99 <= sum(d.values()) <= 1.01:
return {
k: v * float(shot_count) for k, v in as_float(d).items()
}
else:
return as_float(d)
def topk(d, k):
items = sorted(d.items(), key=lambda kv: kv[1], reverse=True)
return items[: (k or len(d))]
phase = "1/6"
sim = to_space(data["simulation"])
dft = to_space(data["default"])
qct = to_space(data["fire_opal"])
correct = max(sim, key=sim.get) if sim else None
print("Correct result:", correct)
sim_items = topk(sim, top_k)
dft_items = topk(dft, top_k)
qct_items = topk(qct, top_k)
sim_keys, y_sim = zip(*sim_items) if sim_items else ([], [])
dft_keys, y_dft = zip(*dft_items) if dft_items else ([], [])
qct_keys, y_qct = zip(*qct_items) if qct_items else ([], [])
fig, axes = plt.subplots(3, 1, layout="constrained")
ylab = "Probabilities"
def panel(ax, keys, ys, title, color):
x = np.arange(len(keys))
bars = ax.bar(x, ys, color=color)
ax.set_title(title)
ax.set_ylabel(ylab)
ax.set_xticks(x)
ax.set_xticklabels(keys, rotation=90)
ax.set_xlabel("Bitstrings")
if correct in keys:
i = keys.index(correct)
bars[i].set_edgecolor("black")
bars[i].set_linewidth(2)
return max(ys, default=0.0)
c_sim, c_dft, c_qct = (
qv.QCTRL_STYLE_COLORS[5],
qv.QCTRL_STYLE_COLORS[1],
qv.QCTRL_STYLE_COLORS[0],
)
m1 = panel(axes[0], list(sim_keys), list(y_sim), "Simulation", c_sim)
m2 = panel(axes[1], list(dft_keys), list(y_dft), "Default", c_dft)
m3 = panel(axes[2], list(qct_keys), list(y_qct), "Q-CTRL", c_qct)
for ax, m in zip(axes, (m1, m2, m3)):
ax.set_ylim(0, 1.05 * (m or 1.0))
for ax in axes:
ax.label_outer()
fig.suptitle(
rf"{number_of_counting_qubits} counting qubits, $2\pi\varphi$={phase}"
)
fig.set_size_inches(20, 10)
plt.show()experiment_index = 0
phase_index = 0
distributions = {
"simulation": data["simulation"][phase_index],
"default": data["default"][phase_index],
"fire_opal": data["fire_opal"][phase_index],
}
plot_distributions(
distributions, num_qubits, top_k=100, by="prob", shot_count=shot_count
)Output:
Correct result: 00101010101010101010101010101010101
La simulación establece la línea de base para la fase propia correcta. Las ejecuciones de hardware por defecto muestran ruido que oscurece este resultado, ya que el ruido reparte la probabilidad entre muchas cadenas de bits incorrectas. Con Q-CTRL Performance Management, la distribución se hace más nítida y se recupera el resultado correcto, lo que permite una QPE fiable a esta escala.
Referencias
[1] Clase 7: Estimación de fase y factorización. IBM Quantum Learning - Fundamentos de los algoritmos cuánticos. Recuperado el 3 de octubre de 2025.
Encuesta tutorial
Tómese un minuto para comentar este tutorial. Su opinión nos ayudará a mejorar nuestra oferta de contenidos y la experiencia de los usuarios.