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

השוואת הגדרות ה-Transpiler

הערכת שימוש: פחות מדקה על מעבד Eagle r3 (הערה: זוהי הערכה בלבד. זמן הריצה בפועל עשוי להשתנות.)

רקע

כדי להבטיח תוצאות מהירות ויעילות יותר, החל מ-1 במרץ 2024, יש להמיר Circuit ואובסרבבלים כך שישתמשו רק בהוראות שהמעבד הקוונטי (QPU) תומך בהן, לפני הגשתם ל-Qiskit Runtime primitives. אנו מכנים אותם Circuit ואובסרבבלים מסוג instruction set architecture (ISA). דרך נפוצה לעשות זאת היא שימוש בפונקציה generate_preset_pass_manager של ה-Transpiler. עם זאת, ייתכן שתעדיפו לבצע את התהליך בצורה ידנית יותר.

לדוגמה, ייתכן שתרצו לכוון לתת-קבוצה ספציפית של Qubits על מכשיר מסוים. הדרכה זו בוחנת את הביצועים של הגדרות Transpiler שונות על-ידי ביצוע התהליך המלא של יצירת Circuit, טרנספילציה והגשה.

דרישות

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

  • Qiskit SDK גרסה 1.2 ומעלה, עם תמיכה ב-visualization
  • Qiskit Runtime גרסה 0.28 ומעלה (pip install qiskit-ibm-runtime)

הגדרה

# Added by doQumentation — required packages for this notebook
!pip install -q qiskit qiskit-ibm-runtime
# Create circuit to test transpiler on
from qiskit import QuantumCircuit
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit.circuit.library import GroverOperator, Diagonal

# Use Statevector object to calculate the ideal output
from qiskit.quantum_info import Statevector
from qiskit.visualization import plot_histogram
from qiskit.transpiler import PassManager

from qiskit.circuit.library import XGate
from qiskit.quantum_info import hellinger_fidelity

# Qiskit Runtime
from qiskit_ibm_runtime import (
QiskitRuntimeService,
Batch,
SamplerV2 as Sampler,
)
from qiskit_ibm_runtime.transpiler.passes.scheduling import (
ASAPScheduleAnalysis,
PadDynamicalDecoupling,
)

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

צרו Circuit קטן שה-Transpiler ינסה לייעל. דוגמה זו יוצרת Circuit המבצע את אלגוריתם גרובר עם אורקל שמסמן את המצב 111. לאחר מכן, סמלצו את ההתפלגות האידיאלית (מה שהייתם מצפים למדוד אם הייתם מריצים זאת על מחשב קוונטי מושלם אינסוף פעמים) לצורך השוואה בהמשך.

# To run on hardware, select the backend with the fewest number of jobs in the queue
service = QiskitRuntimeService()
backend = service.least_busy(
operational=True, simulator=False, min_num_qubits=127
)
backend.name
'ibm_brisbanse'
oracle = Diagonal([1] * 7 + [-1])
qc = QuantumCircuit(3)
qc.h([0, 1, 2])
qc = qc.compose(GroverOperator(oracle))

qc.draw(output="mpl", style="iqp")

Output of the previous code cell

ideal_distribution = Statevector.from_instruction(qc).probabilities_dict()

plot_histogram(ideal_distribution)

Output of the previous code cell

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

כעת, בצעו טרנספילציה של ה-Circuits עבור ה-QPU. תשוו את ביצועי ה-Transpiler כשרמת optimization_level מוגדרת ל-0 (הנמוכה ביותר) לעומת 3 (הגבוהה ביותר). רמת האופטימיזציה הנמוכה ביותר עושה את המינימום ההכרחי כדי להפעיל את ה-Circuit על המכשיר; היא ממפה את ה-Qubits של ה-Circuit ל-Qubits של המכשיר ומוסיפה שערי SWAP כדי לאפשר את כל הפעולות דו-Qubit. רמת האופטימיזציה הגבוהה ביותר חכמה הרבה יותר ומשתמשת בהמון טריקים כדי להפחית את מספר ה-Gates הכולל. מכיוון שלשערים רב-Qubit יש שיעורי שגיאה גבוהים ו-Qubits מתנוונים עם הזמן, ה-Circuits הקצרים יותר אמורים לתת תוצאות טובות יותר.

התא הבא מבצע טרנספילציה של qc לשתי ערכי optimization_level, מדפיס את מספר שערי ה-Qubit הכפול, ומוסיף את ה-Circuits המטרנספלים לרשימה. חלק מאלגוריתמי ה-Transpiler הם אקראיים, לכן מוגדר seed לצורך רבייה.

# Need to add measurements to the circuit
qc.measure_all()

# Find the correct two-qubit gate
twoQ_gates = set(["ecr", "cz", "cx"])
for gate in backend.basis_gates:
if gate in twoQ_gates:
twoQ_gate = gate

circuits = []
for optimization_level in [0, 3]:
pm = generate_preset_pass_manager(
optimization_level, backend=backend, seed_transpiler=0
)
t_qc = pm.run(qc)
print(
f"Two-qubit gates (optimization_level={optimization_level}): ",
t_qc.count_ops()[twoQ_gate],
)
circuits.append(t_qc)
Two-qubit gates (optimization_level=0):  21
Two-qubit gates (optimization_level=3): 14

מכיוון ש-CNOTs בדרך כלל בעלי שיעור שגיאה גבוה, ה-Circuit שטורנספל עם optimization_level=3 אמור לפעול הרבה יותר טוב.

דרך נוספת לשפר ביצועים היא באמצעות dynamic decoupling, על-ידי הפעלת רצף של Gates על Qubits סרק. זה מבטל חלק מהאינטראקציות הלא-רצויות עם הסביבה. התא הבא מוסיף dynamic decoupling ל-Circuit שטורנספל עם optimization_level=3 ומוסיף אותו לרשימה.

# Get gate durations so the transpiler knows how long each operation takes
durations = backend.target.durations()

# This is the sequence we'll apply to idling qubits
dd_sequence = [XGate(), XGate()]

# Run scheduling and dynamic decoupling passes on circuit
pm = PassManager(
[
ASAPScheduleAnalysis(durations),
PadDynamicalDecoupling(durations, dd_sequence),
]
)
circ_dd = pm.run(circuits[1])

# Add this new circuit to our list
circuits.append(circ_dd)
circ_dd.draw(output="mpl", style="iqp", idle_wires=False)

Output of the previous code cell

שלב 3: ביצוע באמצעות Qiskit primitives

בשלב זה, יש לכם רשימת Circuits מטרנספלים עבור ה-QPU שצוין. כעת, צרו מופע של ה-sampler primitive והתחילו עבודה בנפח (batch) באמצעות מנהל ההקשר (with ...:), שפותח וסוגר את ה-batch באופן אוטומטי.

בתוך מנהל ההקשר, דגמו את ה-Circuits ושמרו את התוצאות ב-result.

with Batch(backend=backend):
sampler = Sampler()
job = sampler.run(
[(circuit) for circuit in circuits], # sample all three circuits
shots=8000,
)
result = job.result()

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

לבסוף, השוו את התוצאות מריצות המכשיר מול ההתפלגות האידיאלית. ניתן לראות שהתוצאות עם optimization_level=3 קרובות יותר להתפלגות האידיאלית בשל מספר ה-Gates הנמוך יותר, ו-optimization_level=3 + dd קרובה אף יותר בשל ה-dynamic decoupling.

binary_prob = [
{
k: v / res.data.meas.num_shots
for k, v in res.data.meas.get_counts().items()
}
for res in result
]
plot_histogram(
binary_prob + [ideal_distribution],
bar_labels=False,
legend=[
"optimization_level=0",
"optimization_level=3",
"optimization_level=3 + dd",
"ideal distribution",
],
)

Output of the previous code cell

ניתן לאמת זאת על-ידי חישוב Hellinger fidelity בין כל קבוצת תוצאות לבין ההתפלגות האידיאלית (ערך גבוה יותר עדיף, ו-1 מייצג נאמנות מושלמת).

for prob in binary_prob:
print(f"{hellinger_fidelity(prob, ideal_distribution):.3f}")
0.848
0.945
0.990