שילוב אפשרויות הפחתת שגיאות עם ה-Estimator primitive
הערכת זמן שימוש: 7 דקות על מעבד Heron r2 (הערה: זוהי הערכה בלבד. זמן הריצה שלך עשוי להשתנות.)
תוצאות למידה
אנו מציעים שמשתמשים יכירו את הנושאים הבאים לפני שיעברו על מדריך זה:
- עקרונות הבסיס של dynamical decoupling, הפחתת שגיאות מדידה, gate twirling ואקסטרפולציית רעש אפס, כפי שמתואר במדריך זה.
דרישות מוקדמות
לאחר שיעברו על מדריך זה, משתמשים אמורים להבין:
- כיצד טכניקות הפחתת השגיאות האמורות מיושמות באופן סלקטיבי על חומרה.
- כיצד הן משתוות במונחים של יכולתן להפחית רעש חומרה.
רקע
מדריך זה חוקר את אפשרויות דיכוי השגיאות והפחתת השגיאות הזמינות עם ה-Estimator primitive של Qiskit Runtime. מדריך זה מראה כיצד ליישם כל אחת מהשיטות הבאות בנפרד:
- Dynamical decoupling
- הפחתת שגיאות מדידה (Measurement error mitigation)
- Gate twirling
- אקסטרפולציית רעש אפס (Zero-noise extrapolation - ZNE)
שימו לב שחלופה ליישום טכניקות אלה בנפרד היא יישומן באמצעות רמת חוסן, כאשר resilience_level מקבל ערכים 0, 1, 2:
- 0 : לא מיושמת הפחתה.
- 1 : מיושמת הפחתת שגיאות מדידה.
- 2 : מיושמים gate twirling, הפחתת שגיאות מדידה ו-ZNE.
במדריך זה, תבנו מעגל ו-observable ותגישו משימות באמצעות ה-Estimator primitive תוך שימוש בשילובים שונים של הגדרות הפחתת שגיאות. לאחר מכן, תציגו את התוצאות כדי לצפות בהשפעות של ההגדרות השונות. רוב המדריך משתמש במעגל בעל 10 קיוביטים כדי להקל על ויזואליזציה, ובסוף תגדילו את תהליך העבודה ל-50 קיוביטים.
דרישות
לפני תחילת מדריך זה, ודאו שהדברים הבאים מותקנים:
- Qiskit SDK גרסה 2.1 ומעלה, עם תמיכה ב-visualization
- Qiskit Runtime גרסה 0.40 ומעלה (
pip install qiskit-ibm-runtime)
הגדרה
# Added by doQumentation — required packages for this notebook
!pip install -q matplotlib numpy qiskit qiskit-ibm-runtime
import matplotlib.pyplot as plt
import numpy as np
from qiskit.circuit.library import efficient_su2, unitary_overlap
from qiskit.quantum_info import SparsePauliOp
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit_ibm_runtime import QiskitRuntimeService
from qiskit_ibm_runtime import Batch, EstimatorV2 as Estimator
דוגמת סימולטור בקנה מידה קטן
נדלג על שלב זה מכיוון שהפחתת שגיאות בזמן ריצה אינה נתמכת בסימולטורים.
דוגמת חומרה
שלב 1: מיפוי קלטים קלאסיים לבעיה קוונטית
מדריך זה מניח שהבעיה הקלאסית כבר מופתה לקוונטית. התחילו בבניית מעגל ו-observable למדידה. בעוד שהטכניקות המשמשות כאן חלות על סוגים רבים ושונים של מעגלים, לשם פשטות מדריך זה משתמש במעגל efficient_su2 הכלול בספריית המעגלים של Qiskit.
efficient_su2 הוא מעגל קוונטי מפרמטר שתוכנן להיות ניתן להרצה יעילה על חומרה קוונטית עם קישוריות קיוביטים מוגבלת, תוך שהוא עדיין מספיק אקספרסיבי כדי לפתור בעיות בתחומי יישום כמו אופטימיזציה וכימיה. הוא בנוי על ידי לסירוגין שכבות של שערי qubit יחיד מפרמטרים עם שכבה המכילה תבנית קבועה של שערי שני קיוביטים, למספר נבחר של חזרות. התבנית של שערי שני הקיוביטים יכולה להיקבע על ידי המשתמש. כאן תוכלו להשתמש בתבנית המובנית pairwise מכיוון שהיא ממזערת את עומק המעגל על ידי אריזת שערי שני הקיוביטים בצפיפות מירבית. תבנית זו יכולה להתבצע באמצעות קישוריות קיוביטים לינארית בלבד.
n_qubits = 10
reps = 1
circuit = efficient_su2(n_qubits, entanglement="pairwise", reps=reps)
circuit.decompose().draw("mpl", scale=0.7)

כ-observable שלנו, ניקח את אופרטור Pauli הפועל על הקיוביט האחרון, . שימו לב שהעובדה שהקיוביט האחרון מתאים לאלמנט הראשון של מחרוזת זו נובעת מהשימוש של Qiskit בסימון little-endian.
# Z on the last qubit (index -1) with coefficient 1.0
observable = SparsePauliOp.from_sparse_list(
[("Z", [-1], 1.0)], num_qubits=n_qubits
)
בשלב זה, תוכלו להמשיך להריץ את המעגל שלכם ולמדוד את ה-observable. עם זאת, תרצו גם להשוות את הפלט של המכשיר הקוונטי עם התשובה הנכונה — כלומר, הערך התיאורטי של ה-observable, אם המעגל היה מבוצע ללא שגיאה. עבור מעגלים קוונטיים קטנים אתם יכולים לחשב ערך זה על ידי סימולציה של המעגל על מחשב קלאסי, אבל זה לא אפשרי עבור מעגלים גדולים יותר, בקנה מידה שירותי. תוכלו לעקוף בעיה זו עם טכניקת "מעגל המראה" (ידועה גם בשם "compute-uncompute"), שהיא שימושית לבחינת ביצועים של מכשירים קוונטיים.
מעגל מראה
בטכניקת מעגל המראה, אתם משרשרים את המעגל עם המעגל ההפוך שלו, שנוצר על ידי הפיכת כל שער של המעגל בסדר הפוך. המעגל המתקבל מיישם את אופרטור הזהות, שניתן לסמלץ אותו בטריוויאליות. מכיוון שהמבנה של המעגל המקורי נשמר במעגל המראה, ביצוע מעגל המראה עדיין נותן מושג כיצד המכשיר הקוונטי יבצע את המעגל המקורי.
תא הקוד הבא מקצה פרמטרים אקראיים למעגל שלכם, ולאחר מכן בונה את מעגל המראה באמצעות מחלקת unitary_overlap. לפני שיקוף המעגל, הוסיפו הוראת barrier אליו כדי למנוע מה-transpiler למזג את שני חלקי המעגל משני צידי המחסום וכתוצאה מכך לקבל מעגל מתורגם ללא שערים כלל.
# Generate random parameters
rng = np.random.default_rng(1234)
params = rng.uniform(-np.pi, np.pi, size=circuit.num_parameters)
# Assign the parameters to the circuit
assigned_circuit = circuit.assign_parameters(params)
# Add a barrier to prevent circuit optimization of mirrored operators
assigned_circuit.barrier()
# Construct mirror circuit
mirror_circuit = unitary_overlap(assigned_circuit, assigned_circuit)
mirror_circuit.decompose().draw("mpl", scale=0.7)

שלב 2: אופטימיזציה של הבעיה להרצה על חומרה קוונטית
עליכם לבצע אופטימיזציה של המעגל שלכם לפני הרצתו על חומרה. תהליך זה כולל מספר שלבים:
- בחירת פריסת קיוביטים שממפה את הקיוביטים הוירטואליים של המעגל שלכם לקיוביטים פיזיים על החומרה.
- הוספת שערי swap לפי הצורך כדי לנתב אינטראקציות בין קיוביטים שאינם מחוברים.
- תרגום השערים במעגל שלכם להוראות Instruction Set Architecture (ISA) שניתן להריץ ישירות על החומרה.
- ביצוע אופטימיזציות מעגל כדי למזער את עומק המעגל וספירת השערים.
ה-transpiler המובנה ב-Qiskit יכול לבצע את כל השלבים הללו עבורכם. מכיוון שדוגמה זו משתמשת במעגל יעיל מבחינת חומרה, ה-transpiler אמור להיות מסוגל לבחור פריסת קיוביטים שאינה דורשת הכנסת שערי swap כלשהם עבור ניתוב אינטראקציות.
עליכם לבחור את מכשיר החומרה לשימוש לפני אופטימיזציה של המעגל שלכם. תא הקוד הבא מבקש את המכשיר הפחות עסוק עם לפחות 127 קיוביטים.
service = QiskitRuntimeService()
backend = service.least_busy(
operational=True, simulator=False, min_num_qubits=127
)
print(backend)
<IBMBackend('ibm_fez')>
תוכלו לתרגם את המעגל שלכם ל-backend שנבחר על ידי יצירת pass manager ולאחר מכן הרצת ה-pass manager על המעגל. דרך קלה ליצור pass manager היא להשתמש בפונקציה generate_preset_pass_manager. ראו Transpile with pass managers להסבר מפורט יותר על תרגום עם pass managers.
pass_manager = generate_preset_pass_manager(
optimization_level=3, backend=backend, seed_transpiler=1234
)
isa_circuit = pass_manager.run(mirror_circuit)
isa_circuit.draw("mpl", idle_wires=False, scale=0.7, fold=-1)

המעגל המתורגם מכיל כעת רק הוראות ISA. כל השערים פורקו במונחים של שערי וסיבובי , ו-שערי CZ.
תהליך התרגום מיפה את הקיוביטים הוירטואליים של המעגל לקיוביטים פיזיים על החומרה. המידע על פריסת הקיוביטים מאוחסן במאפיין layout של המעגל המתורגם. ה-observable גם הוגדר במונחים של הקיוביטים הוירטואליים, כך שתצטרכו להחיל פריסה זו על ה-observable, דבר שניתן לעשות באמצעות מתודת apply_layout של SparsePauliOp.
isa_observable = observable.apply_layout(isa_circuit.layout)
print("Original observable:")
print(observable)
print()
print("Observable with layout applied:")
print(isa_observable)
Original observable:
SparsePauliOp(['ZIIIIIIIII'],
coeffs=[1.+0.j])
Observable with layout applied:
SparsePauliOp(['IIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII'],
coeffs=[1.+0.j])
שלב 3: הרצה באמצעות Qiskit primitives
אתם כעת מוכנים להריץ את המעגל שלכם באמצעות ה-Estimator primitive.
כאן תגישו חמש משימות נפרדות, החל מללא דיכוי או הפחתת שגיאות, ובאופן עוקב תפעילו אפשרויות דיכוי והפחתת שגיאות שונות הזמינות ב-Qiskit Runtime. למידע על האפשרויות, ראו בעמודים הבאים:
- סקירה של כל האפשרויות
- Dynamical decoupling
- Resilience, כולל הפחתת שגיאות מדידה ואקסטרפולציית רעש אפס (ZNE)
- Twirling
מכיוון שמשימות אלה יכולות לרוץ באופן עצמאי זו מזו, תוכלו להשתמש ב-batch mode כדי לאפשר ל-Qiskit Runtime לבצע אופטימיזציה של תזמון הביצוע שלהן.
pub = (isa_circuit, isa_observable)
jobs = []
with Batch(backend=backend) as batch:
estimator = Estimator(mode=batch)
estimator.options.environment.job_tags = [
"TUT_CEM_SS"
] # add tag for this small scale job
# Set number of shots
estimator.options.default_shots = 100_000
# Disable runtime compilation and error mitigation
estimator.options.resilience_level = 0
# Run job with no error mitigation
job0 = estimator.run([pub])
jobs.append(job0)
# Add dynamical decoupling (DD)
estimator.options.dynamical_decoupling.enable = True
estimator.options.dynamical_decoupling.sequence_type = "XpXm"
job1 = estimator.run([pub])
jobs.append(job1)
# Add readout error mitigation (DD + TREX)
estimator.options.resilience.measure_mitigation = True
job2 = estimator.run([pub])
jobs.append(job2)
# Add gate twirling (DD + TREX + Gate Twirling)
estimator.options.twirling.enable_gates = True
estimator.options.twirling.num_randomizations = "auto"
job3 = estimator.run([pub])
jobs.append(job3)
# Add zero-noise extrapolation (DD + TREX + Gate Twirling + ZNE)
estimator.options.resilience.zne_mitigation = True
estimator.options.resilience.zne.noise_factors = (1, 3, 5)
estimator.options.resilience.zne.extrapolator = ("exponential", "linear")
job4 = estimator.run([pub])
jobs.append(job4)
שלב 4: עיבוד לאחר והחזרת התוצאה בפורמט קלאסי רצוי
לבסוף, תוכלו לנתח את הנתונים. כאן תשלפו את תוצאות המשימות, תחלצו את ערכי התוחלת הנמדדים מהן, ותציגו את הערכים, כולל סרגלי שגיאה של סטיית תקן אחת.
# Retrieve the job results
results = [job.result() for job in jobs]
# Unpack the PUB results (there's only one PUB result in each job result)
pub_results = [result[0] for result in results]
# Unpack the expectation values and standard errors
expectation_vals = np.array(
[float(pub_result.data.evs) for pub_result in pub_results]
)
standard_errors = np.array(
[float(pub_result.data.stds) for pub_result in pub_results]
)
# Plot the expectation values
fig, ax = plt.subplots()
labels = ["No mitigation", "+ DD", "+ TREX", "+ Twirling", "+ ZNE"]
ax.bar(
range(len(labels)),
expectation_vals,
yerr=standard_errors,
label="experiment",
)
ax.axhline(y=1.0, color="gray", linestyle="--", label="ideal")
ax.set_xticks(range(len(labels)))
ax.set_xticklabels(labels)
ax.set_ylabel("Expectation value")
ax.legend(loc="upper left")
plt.show()
בקנה מידה קטן זה, קשה לראות את ההשפעה של רוב טכניקות הפחתת השגיאות, אבל אקסטרפולציית רעש אפס אכן נותנת שיפור ניכר. עם זאת, שימו לב ששיפור זה אינו מגיע בחינם, מכיוון שלתוצאת ה-ZNE יש גם סרגל שגיאה גדול יותר.
דוגמת חומרה בקנה מידה גדול
בעת פיתוח ניסוי, שימושי להתחיל עם מעגל קטן כדי להקל על ויזואליזציות וסימולציות. כעת, לאחר שפיתחתם ובדקתם את תהליך העבודה שלנו על מעגל בעל 10 קיוביטים, תוכלו להגדיל אותו ל-50 קיוביטים. תא הקוד הבא חוזר על כל השלבים במדריך זה, אך כעת מיישם אותם על מעגל בעל 50 קיוביטים.
n_qubits = 50
reps = 1
# Construct circuit and observable
circuit = efficient_su2(n_qubits, entanglement="pairwise", reps=reps)
observable = SparsePauliOp.from_sparse_list(
[("Z", [-1], 1.0)], num_qubits=n_qubits
)
# Assign parameters to circuit
params = rng.uniform(-np.pi, np.pi, size=circuit.num_parameters)
assigned_circuit = circuit.assign_parameters(params)
assigned_circuit.barrier()
# Construct mirror circuit
mirror_circuit = unitary_overlap(assigned_circuit, assigned_circuit)
# Transpile circuit and observable
isa_circuit = pass_manager.run(mirror_circuit)
isa_observable = observable.apply_layout(isa_circuit.layout)
# Run jobs
pub = (isa_circuit, isa_observable)
jobs = []
with Batch(backend=backend) as batch:
estimator = Estimator(mode=batch)
estimator.options.environment.job_tags = [
"TUT_CEM_LS"
] # add tag for this large scale job
# Set number of shots
estimator.options.default_shots = 100_000
# Disable runtime compilation and error mitigation
estimator.options.resilience_level = 0
# Run job with no error mitigation
job0 = estimator.run([pub])
jobs.append(job0)
# Add dynamical decoupling (DD)
estimator.options.dynamical_decoupling.enable = True
estimator.options.dynamical_decoupling.sequence_type = "XpXm"
job1 = estimator.run([pub])
jobs.append(job1)
# Add readout error mitigation (DD + TREX)
estimator.options.resilience.measure_mitigation = True
job2 = estimator.run([pub])
jobs.append(job2)
# Add gate twirling (DD + TREX + Gate Twirling)
estimator.options.twirling.enable_gates = True
estimator.options.twirling.num_randomizations = "auto"
job3 = estimator.run([pub])
jobs.append(job3)
# Add zero-noise extrapolation (DD + TREX + Gate Twirling + ZNE)
estimator.options.resilience.zne_mitigation = True
estimator.options.resilience.zne.noise_factors = (1, 3, 5)
estimator.options.resilience.zne.extrapolator = ("exponential", "linear")
job4 = estimator.run([pub])
jobs.append(job4)
# Retrieve the job results
results = [job.result() for job in jobs]
# Unpack the PUB results (there's only one PUB result in each job result)
pub_results = [result[0] for result in results]
# Unpack the expectation values and standard errors
expectation_vals = np.array(
[float(pub_result.data.evs) for pub_result in pub_results]
)
standard_errors = np.array(
[float(pub_result.data.stds) for pub_result in pub_results]
)
# Plot the expectation values
fig, ax = plt.subplots()
labels = ["No mitigation", "+ DD", "+ TREX", "+ Twirling", "+ ZNE"]
ax.bar(
range(len(labels)),
expectation_vals,
yerr=standard_errors,
label="experiment",
)
ax.axhline(y=1.0, color="gray", linestyle="--", label="ideal")
ax.set_xticks(range(len(labels)))
ax.set_xticklabels(labels)
ax.set_ylabel("Expectation value")
ax.legend(loc="upper left")
plt.show()
כאשר תשוו את תוצאות 50 הקיוביטים עם תוצאות 10 הקיוביטים מקודם, תוכלו לשים לב לדברים הבאים (התוצאות שלכם עשויות להשתנות בין הרצות):
- כל הניסויים נותנים תוצאות קרובות יותר לערך האידיאלי וכל סרגלי השגיאה קטנים יותר.
- הוספת dynamical decoupling עשויה להחמיר את הביצועים בהשוואה למקרה ללא הפחתה. זה לא מפתיע, מכיוון שהמעגל צפוף מאוד. Dynamical decoupling שימושי בעיקר כאשר יש פערים גדולים במעגל, במהלכם קיוביטים יושבים במצב סרק ללא שערים המופעלים עליהם. כאשר פערים אלה אינם נוכחים, dynamical decoupling אינו יעיל, ויכול למעשה להחמיר את הביצועים עקב שגיאות בפולסים של dynamical decoupling עצמם. מעגל 10 הקיוביטים אולי היה קטן מדי עבורנו כדי לצפות בהשפעה זו.
- עם אקסטרפולציית רעש אפס, התוצאה קרובה מאוד לערך האידיאלי. זה מדגים את העוצמה של ZNE.
צעדים הבאים
אם עבודה זו עניינה אתכם, ייתכן שתתעניינו בחומר הבא על כמה טכניקות נוספות של הפחתת שגיאות ודיכוי שגיאות שלא הוזכרו במדריך זה: