Estimation de phase quantique avec les fonctions Qiskit de Q-CTRL
Estimation d'utilisation : 40 secondes sur un processeur Heron r2. (REMARQUE : Il ne s'agit que d'une estimation. Votre temps d'exécution peut varier.)
Contexte
L'estimation de phase quantique (QPE) est un algorithme fondamental en informatique quantique qui constitue la base de nombreuses applications importantes telles que l'algorithme de Shor, l'estimation de l'énergie de l'état fondamental en chimie quantique et les problèmes de valeurs propres. La QPE estime la phase associée à un état propre d'un opérateur unitaire, encodée dans la relation
et la détermine avec une précision de en utilisant qubits de comptage [1]. En préparant ces qubits en superposition, en appliquant des puissances contrôlées de , puis en utilisant la transformée de Fourier quantique inverse (QFT) pour extraire la phase sous forme de résultats de mesure encodés en binaire, la QPE produit une distribution de probabilité concentrée autour des chaînes de bits dont les fractions binaires approximent . Dans le cas idéal, le résultat de mesure le plus probable correspond directement à l'expansion binaire de la phase, tandis que la probabilité des autres résultats diminue rapidement avec le nombre de qubits de comptage. Cependant, l'exécution de circuits QPE profonds sur du matériel réel présente des défis : le grand nombre de qubits et d'opérations d'intrication rend l'algorithme très sensible à la décohérence et aux erreurs de porte. Cela entraîne des distributions de chaînes de bits élargies et décalées, masquant la véritable phase propre. En conséquence, la chaîne de bits ayant la plus haute probabilité peut ne plus correspondre à l'expansion binaire correcte de .
Dans ce tutoriel, nous présentons une implémentation de l'algorithme QPE utilisant les outils de suppression d'erreurs et de gestion des performances Fire Opal de Q-CTRL, proposés sous forme de fonction Qiskit (voir la documentation Fire Opal). Fire Opal applique automatiquement des optimisations avancées, notamment le découplage dynamique, l'amélioration de la disposition des qubits et des techniques de suppression d'erreurs, ce qui permet d'obtenir des résultats de plus haute fidélité. Ces améliorations rapprochent les distributions de chaînes de bits matérielles de celles obtenues par des simulations sans bruit, de sorte que vous puissiez identifier de manière fiable la phase propre correcte même en présence de bruit.
Prérequis
Avant de commencer ce tutoriel, assurez-vous d'avoir installé les éléments suivants :
- Qiskit SDK v1.4 ou version ultérieure, avec le support de visualisation
- Qiskit Runtime v0.40 ou version ultérieure (
pip install qiskit-ibm-runtime) - Qiskit Functions Catalog v0.9.0 (
pip install qiskit-ibm-catalog) - Fire Opal SDK v9.0.2 ou version ultérieure (
pip install fire-opal) - Q-CTRL Visualizer v8.0.2 ou version ultérieure (
pip install qctrl-visualizer)
Configuration
Tout d'abord, authentifiez-vous à l'aide de votre clé API IBM Quantum. Ensuite, sélectionnez la fonction Qiskit comme suit. (Ce code suppose que vous avez déjà enregistré votre compte dans votre environnement local.)
# Added by doQumentation — installs packages not in the Binder environment
%pip install -q qctrlvisualizer
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")
Étape 1 : Traduire les entrées classiques en un problème quantique
Dans ce tutoriel, nous illustrons la QPE pour retrouver la phase propre d'un opérateur unitaire à un seul qubit connu. L'opérateur unitaire dont nous voulons estimer la phase est la porte de phase à un seul qubit appliquée au qubit cible :
Nous préparons son état propre . Puisque est un vecteur propre de avec la valeur propre , la phase propre à estimer est :
Nous fixons , de sorte que la phase de référence est . Le circuit QPE implémente les puissances contrôlées en appliquant des rotations de phase contrôlées avec des angles , puis applique la QFT inverse au registre de comptage et le mesure. Les chaînes de bits résultantes se concentrent autour de la représentation binaire de .
Le circuit utilise qubits de comptage (pour définir la précision de l'estimation) plus un qubit cible. Nous commençons par définir les blocs de construction nécessaires à l'implémentation de la QPE : la transformée de Fourier quantique (QFT) et son inverse, des fonctions utilitaires pour convertir entre fractions décimales et binaires de la phase propre, ainsi que des fonctions auxiliaires pour normaliser les comptages bruts en probabilités afin de comparer les résultats de simulation et du matériel.
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_circuit
def 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_probabilities
Étape 2 : Optimiser le problème pour l'exécution sur du matériel quantique
Nous construisons le circuit QPE en préparant les qubits de comptage en superposition, en appliquant des rotations de phase contrôlées pour encoder la phase propre cible, et en terminant par une QFT inverse avant la mesure.
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 qc
Étape 3 : Exécuter à l'aide des primitives Qiskit
Nous définissons le nombre de tirs et de qubits pour l'expérience, et encodons la phase cible en utilisant chiffres binaires. Avec ces paramètres, nous construisons le circuit QPE qui sera exécuté sur la simulation, le matériel par défaut et les backends améliorés par 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
)
)
Exécuter la simulation MPS
Tout d'abord, nous générons une distribution de référence à l'aide du simulateur matrix_product_state et convertissons les comptages en probabilités normalisées pour une comparaison ultérieure avec les résultats du matériel.
# 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,
)
)
Exécuter sur le matériel
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)
Exécuter sur le matériel avec Fire Opal
# Run the circuit using the 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},
)
Étape 4 : Post-traiter et renvoyer le résultat dans le format classique souhaité
# 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
)
Correct result: 00101010101010101010101010101010101

La simulation établit la référence pour la phase propre correcte. Les exécutions par défaut sur le matériel montrent du bruit qui obscurcit ce résultat, car le bruit répartit la probabilité sur de nombreuses chaînes de bits incorrectes. Avec la gestion des performances Q-CTRL, la distribution devient plus nette et le résultat correct est retrouvé, permettant une QPE fiable à cette échelle.
Références
[1] Cours 7 : Estimation de phase et factorisation. IBM Quantum Learning - Fondamentaux des algorithmes quantiques. Consulté le 3 octobre 2025.
Enquête sur le tutoriel
Veuillez prendre une minute pour donner votre avis sur ce tutoriel. Vos commentaires nous aideront à améliorer notre contenu et l'expérience utilisateur.