Estimation de phase quantique avec l' Qiskit Functions de Q-CTRL
Estimation d'utilisation : 40 secondes sur un processeur Heron r2. (NOTE : Il s'agit uniquement d'une estimation. Votre durée d'exécution peut varier.)
Arrière-plan
L'estimation de phase quantique (QPE) est un algorithme fondamental de l'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 de la chimie quantique et les problèmes de valeurs propres. QPE estime la phase associée à un état propre d'un opérateur unitaire, encodé dans la relation
et la détermine avec une précision de en utilisant des 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 en résultats de mesure codés de manière binaire, le QPE produit une distribution de probabilité qui culmine à des chaînes de bits dont les fractions binaires se rapprochent de . 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 présente des difficultés : le grand nombre de qubits et d'opérations d'enchevêtrement rend l'algorithme très sensible à la décohérence et aux erreurs de porte. Il en résulte des distributions élargies et décalées des chaînes de bits, masquant la véritable phase propre. Par conséquent, la chaîne de bits ayant la probabilité la plus élevée peut ne plus correspondre à l'expansion binaire correcte de .
Dans ce tutoriel, nous présentons une implémentation de l'algorithme QPE en utilisant les outils de gestion des performances et de suppression des erreurs Fire Opal de Q-CTRL, proposés en tant que 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 les techniques de suppression des erreurs, ce qui permet d'obtenir des résultats plus fidèles. Ces améliorations rapprochent les distributions de chaînes de bits matérielles de celles obtenues dans les simulations sans bruit, de sorte que vous pouvez identifier de manière fiable la phase propre correcte, même sous l'effet du bruit.
Exigences
Avant de commencer ce tutoriel, assurez-vous que les éléments suivants sont installés :
- Qiskit SDK v1.4 ou plus tard, avec prise en charge de la visualisation
- Qiskit Runtime v0.40 ou plus tard (
pip install qiskit-ibm-runtime) - Qiskit Functions Catalog v0.9.0 (
pip install qiskit-ibm-catalog) - Fire Opal SDK v9.0.2 ou plus récent (
pip install fire-opal) - Q-CTRL Visualizer v8.0.2 ou plus récent (
pip install qctrl-visualizer)
Configuration
Commencez par vous authentifier à l'aide de votre clé API IBM Quantum. Ensuite, sélectionnez la fonction Qiskit comme suit. (Ce code part du principe que vous avez déjà enregistré votre compte dans votre environnement 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")Étape 1 : Mettre en correspondance les entrées classiques avec un problème quantique
Dans ce tutoriel, nous illustrons le QPE pour récupérer la phase propre d'une unité connue à un seul qubit. L'unité dont nous voulons estimer la phase est la porte de phase du qubit unique appliquée au qubit cible :
Nous préparons son état propre . Comme est un vecteur propre de avec la valeur propre , la phase propre à estimer est :
Nous fixons , de sorte que la phase de vérité de base est . Le circuit QPE met en œuvre 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 la 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 éléments de base nécessaires à la mise en œuvre du QPE : la transformée de Fourier quantique (QFT) et son inverse, les fonctions utilitaires permettant de passer des fractions décimales aux fractions binaires de la phase propre, et les aides permettant de normaliser les comptes bruts en probabilités afin de comparer les résultats de la 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_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_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 coder 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écutez à l'aide d' Qiskit primitives
Nous fixons le nombre de tirs et de qubits pour l'expérience et codons la phase cible à l'aide de 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
)
)Lancer 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 comptes en probabilités normalisées pour une comparaison ultérieure avec les résultats matériels.
# 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,
)
)Fonctionner sur du 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)Fonctionne sur du matériel équipé de 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-traitement et restitution du 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
)Output:
Correct result: 00101010101010101010101010101010101
La simulation établit la ligne de base pour la phase propre correcte. Les exécutions matérielles par défaut montrent un 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 récupéré, ce qui permet un QPE fiable à cette échelle.
Références
[1] Lecture 7 : Estimation de la phase et factorisation. IBM Quantum Learning - Principes fondamentaux des algorithmes quantiques. Consulté le 3 octobre 2025.
Enquête tutorielle
Veuillez prendre une minute pour nous faire part de vos commentaires sur ce tutoriel. Vos commentaires nous aideront à améliorer nos offres de contenu et l'expérience des utilisateurs.