Aller au contenu principal
Ce projet est en phase bêta. Tu as trouvé un bug ? Ouvrir un ticket GitHub. Une question ou une idée ? Pose ta question dans Discussions — tes retours sont les bienvenus !
Quelle est la qualité de la traduction sur cette page ?

Estimation de phase quantique avec Qiskit Functions de Q-CTRL

Estimation d'utilisation : 40 secondes sur un processeur Heron r2. (REMARQUE : Il ne s'agit que d'une estimation. Ton 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 φ\varphi associée à un état propre d'un opérateur unitaire, encodée dans la relation

U∣φ⟩=e2πiφ∣φ⟩,U \lvert \varphi \rangle = e^{2\pi i \varphi} \lvert \varphi \rangle,

et la détermine avec une précision de ϵ=O(1/2m)\epsilon = O(1/2^m) en utilisant mm qubits de comptage [1]. En préparant ces qubits en superposition, en appliquant des puissances contrôlées de UU, 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 φ\varphi. Dans le cas idéal, le résultat de mesure le plus probable correspond directement au développement 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 au développement binaire correct de φ\varphi.

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 tu puisses identifier de manière fiable la phase propre correcte même en présence de bruit.

Prérequis​

Avant de commencer ce tutoriel, assure-toi d'avoir installé les éléments suivants :

  • Qiskit SDK v1.4 ou version ultérieure, avec la prise en charge de la 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, authentifie-toi à l'aide de ta clé API IBM Quantum. Ensuite, sélectionne la fonction Qiskit comme suit. (Ce code suppose que tu as déjà enregistré ton compte dans ton environnement local.)

# Added by doQumentation — installs the packages this notebook needs if they are missing
import importlib.util

_needed = {"matplotlib": "matplotlib", "numpy": "numpy", "qctrlvisualizer": "qctrlvisualizer", "qiskit": "qiskit", "qiskit_aer": "qiskit-aer", "qiskit_ibm_catalog": "qiskit-ibm-catalog", "qiskit_ibm_runtime": "qiskit-ibm-runtime"}
_missing = [pip for module, pip in _needed.items()
if importlib.util.find_spec(module) is None]
# One at a time, so a package that fails to install does not block the others
for _pip in _missing:
%pip install -q {_pip}
if not _missing:
print("\u2713 All required packages are installed")
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 :

U(θ)=(100eiθ)=eiθ ∣1⟩ ⁣⟨1∣.U(\theta)= \begin{pmatrix} 1 & 0\\[2pt] 0 & e^{i\theta} \end{pmatrix} = e^{i\theta\,|1\rangle\!\langle 1|}.

Nous préparons son état propre ∣ψ⟩=∣1⟩|\psi\rangle=|1\rangle. Puisque ∣1⟩|1\rangle est un vecteur propre de U(θ)U(\theta) avec la valeur propre eiθe^{i\theta}, la phase propre à estimer est :

φ=θ2π(mod1)\varphi = \frac{\theta}{2\pi} \pmod{1}

Nous fixons θ=16⋅2π\theta=\tfrac{1}{6}\cdot 2\pi, de sorte que la phase de référence est φ=1/6\varphi=1/6. Le circuit QPE implémente les puissances contrôlées U2kU^{2^k} en appliquant des rotations de phase contrôlées avec des angles θ⋅2k\theta\cdot2^k, 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 1/61/6.

Le circuit utilise mm 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 φ=1/6\varphi = 1/6 en utilisant mm 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 IBM Quantum Compute Service.
ibm_default_job = sampler.run([isa_circuits], shots=shot_count)

Exécuter sur le matériel avec 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},
)

É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

Sortie de la cellule de code précédente

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​

Prends une minute pour donner ton avis sur ce tutoriel. Tes commentaires nous aideront à améliorer notre contenu et l'expérience utilisateur.

Lien vers l'enquête