דלג לתוכן הראשי

אומדן פאזה קוונטי עם הפונקציות של Q-CTRL ב-Qiskit

הערכת שימוש: 40 שניות על מעבד Heron r2. (הערה: זוהי אומדן בלבד. זמן הריצה בפועל עשוי להשתנות.)

רקע

אומדן פאזה קוונטי (QPE) הוא אלגוריתם יסודי בחישוב קוונטי המהווה את הבסיס ליישומים חשובים רבים כגון אלגוריתם שור, הערכת אנרגיית מצב היסוד בכימיה קוונטית ובעיות ערכים עצמיים. QPE מעריך את הפאזה φ\varphi המקושרת למצב עצמי של אופרטור אוניטרי, המקודדת ביחס

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

וקובע אותה לדיוק של ϵ=O(1/2m)\epsilon = O(1/2^m) באמצעות mm קיוביטי ספירה [1]. על ידי הכנת קיוביטים אלה בסופרפוזיציה, הפעלת חזקות מבוקרות של UU, ולאחר מכן שימוש בהתמרת פורייה קוונטית הפוכה (QFT) כדי לחלץ את הפאזה לתוצאות מדידה מקודדות בינארית, QPE מייצר התפלגות הסתברות עם שיא בסדרות ביטים ששברים בינאריים שלהן מקרבים את φ\varphi. במקרה האידיאלי, תוצאת המדידה הסבירה ביותר מתאימה ישירות להרחבה בינארית של הפאזה, בעוד שההסתברות לתוצאות אחרות יורדת במהירות עם מספר קיוביטי הספירה. עם זאת, הרצת מעגלי QPE עמוקים על חומרה מציגה אתגרים: המספר הגדול של קיוביטים ופעולות שזירה הופך את האלגוריתם לרגיש מאוד לדה-קוהרנטיות ולשגיאות שערים. התוצאה היא התפלגויות מורחבות ומוסטות של סדרות ביטים, המסתירות את הפאזה העצמית האמיתית. כתוצאה מכך, סדרת הביטים עם ההסתברות הגבוהה ביותר עשויה לא להתאים יותר להרחבה הבינארית הנכונה של φ\varphi.

במדריך זה, אנו מציגים מימוש של אלגוריתם QPE באמצעות כלי דיכוי שגיאות וניהול ביצועים של Fire Opal מבית Q-CTRL, המוצעים כפונקציית Qiskit (ראה את תיעוד Fire Opal). Fire Opal מיישם אוטומטית אופטימיזציות מתקדמות, לרבות ניתוק דינמי, שיפורי פריסת קיוביטים וטכניקות דיכוי שגיאות, וכתוצאה מכך תוצאות בעלות נאמנות גבוהה יותר. שיפורים אלה מקרבים את התפלגויות סדרות הביטים בחומרה לאלה המתקבלות בסימולציות חסרות רעש, כך שתוכלו לזהות באופן אמין את הפאזה העצמית הנכונה גם תחת השפעות הרעש.

דרישות

לפני התחלת מדריך זה, ודאו שמותקנים לכם הרכיבים הבאים:

  • Qiskit SDK גרסה 1.4 ומעלה, עם תמיכה בויזואליזציה
  • Qiskit Runtime גרסה 0.40 ומעלה (pip install qiskit-ibm-runtime)
  • Qiskit Functions Catalog גרסה 0.9.0 (pip install qiskit-ibm-catalog)
  • Fire Opal SDK גרסה 9.0.2 ומעלה (pip install fire-opal)
  • Q-CTRL Visualizer גרסה 8.0.2 ומעלה (pip install qctrl-visualizer)

הגדרה

ראשית, בצעו אימות באמצעות מפתח API של IBM Quantum שלכם. לאחר מכן, בחרו את פונקציית Qiskit כדלקמן. (קוד זה מניח שכבר שמרתם את החשבון שלכם לסביבה המקומית שלכם.)

# Added by doQumentation — required packages for this notebook
!pip install -q matplotlib numpy qctrlvisualizer qiskit qiskit-aer qiskit-ibm-catalog qiskit-ibm-runtime
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")

שלב 1: מיפוי קלטים קלאסיים לבעיה קוונטית

במדריך זה, אנו ממחישים את QPE כדי לשחזר את הפאזה העצמית של אוניטרי ידוע של קיוביט יחיד. האוניטרי שאת הפאזה שלו אנו רוצים להעריך הוא שער הפאזה של קיוביט יחיד המופעל על קיוביט היעד:

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|}.

אנו מכינים את המצב העצמי שלו ψ=1|\psi\rangle=|1\rangle. מכיוון ש-1|1\rangle הוא וקטור עצמי של U(θ)U(\theta) עם ערך עצמי eiθe^{i\theta}, הפאזה העצמית שיש להעריך היא:

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

אנו מגדירים θ=162π\theta=\tfrac{1}{6}\cdot 2\pi, כך שהפאזה האמיתית היא φ=1/6\varphi=1/6. מעגל QPE מיישם את החזקות המבוקרות U2kU^{2^k} על ידי הפעלת סיבובי פאזה מבוקרים עם זוויות θ2k\theta\cdot2^k, לאחר מכן מיישם את ה-QFT ההפוך על רגיסטר הספירה ומודד אותו. סדרות הביטים המתקבלות מתרכזות סביב הייצוג הבינארי של 1/61/6.

המעגל משתמש ב-mm קיוביטי ספירה (כדי להגדיר את דיוק האומדן) בתוספת קיוביט יעד אחד. נתחיל בהגדרת אבני הבניין הנדרשות למימוש QPE: התמרת פורייה קוונטית (QFT) וההפוכה שלה, פונקציות עזר למיפוי בין שברים עשרוניים ובינאריים של הפאזה העצמית, ועוזרים לנרמול ספירות גולמיות להסתברויות לצורך השוואת תוצאות סימולציה וחומרה.

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

שלב 2: אופטימיזציה של הבעיה לביצוע על חומרה קוונטית

אנו בונים את מעגל QPE על ידי הכנת קיוביטי הספירה בסופרפוזיציה, הפעלת סיבובי פאזה מבוקרים כדי לקודד את הפאזה העצמית של היעד, וסיום עם QFT הפוך לפני המדידה.

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

שלב 3: ביצוע באמצעות פרימיטיבים של Qiskit

אנו מגדירים את מספר הירי (shots) והקיוביטים לניסוי, ומקודדים את פאזת היעד φ=1/6\varphi = 1/6 באמצעות mm ספרות בינאריות. עם פרמטרים אלה, אנו בונים את מעגל QPE שיבוצע בסימולציה, בחומרה ברירת מחדל ובחומרה משופרת עם 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
)
)

הרצת סימולציית MPS

ראשית, אנו מייצרים התפלגות ייחוס באמצעות סימולטור matrix_product_state וממירים את הספירות להסתברויות מנורמלות לשם השוואה מאוחרת יותר עם תוצאות חומרה.

# 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,
)
)

הרצה על חומרה

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)

הרצה על חומרה עם 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},
)

שלב 4: עיבוד והחזרת תוצאה בפורמט קלאסי רצוי

# 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

Output of the previous code cell

הסימולציה קובעת את קו הבסיס לפאזה העצמית הנכונה. ריצות חומרה עם ברירת מחדל מראות רעש המעמעם תוצאה זו, שכן הרעש מפזר הסתברות על פני סדרות ביטים שגויות רבות. עם ניהול הביצועים של Q-CTRL ההתפלגות הופכת חדה יותר והתוצאה הנכונה משוחזרת, מה שמאפשר QPE אמין בקנה מידה זה.

הפניות

[1] הרצאה 7: אומדן פאזה ופירוק לגורמים. IBM Quantum Learning - יסודות של אלגוריתמים קוונטיים. הועבר ב-3 באוקטובר, 2025.

סקר המדריך

אנא הקדישו דקה כדי לספק משוב על מדריך זה. התובנות שלכם יעזרו לנו לשפר את הצעות התוכן ואת חוויית המשתמש שלנו.

קישור לסקר