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

ביטול שגיאות הסתברותי עם קונוסי אור מוצללים

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

תוצרי למידה

  • מהו ביטול שגיאות הסתברותי (PEC), ומדוע תקורת הדגימה שלו γ2\gamma^2 גדלה באופן אקספוננציאלי עם סך הרעש הפועל על המעגל

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

  • כיצד ללמוד רעש שכבות באמצעות NoiseLearnerV3 ולהזריק אנטי-רעש דרך samplomatic והפרימיטיב Executor

  • כיצד לשלב PEC ו-PEC+SLC עם TREX ופוסט-סלקציה כדי להעריך ערך תוחלת על חומרה

דרישות מוקדמות

רקע

מדריך זה מדגים כיצד להקהות שגיאות באמצעות ה-addon של קונוס אור מוצלל (SLC). addon זה הוא התפתחות של טכניקת ביטול השגיאות ההסתברותי (PEC), שבה משתמש לומד את הרעש של שכבות ייחודיות במעגל ולאחר מכן מבטל את הרעש על ידי הפעלת שערי קיוביט-בודד וטכניקות עיבוד לאחר-מדידה. בהשוואה לשיטות אחרות, PEC מציע גבולות איתנים יותר על ההטיה של התוצאה המוקהית, אך נוטה לסבול מתקורה גבוהה יותר במונחי זמן QPU. במהלך PEC, כדי לפצות על החלשת ערך התוחלת על ידי רעש, התוצאה הממוצעת מנורמלת מחדש בגורם γ=exp(l,σ2λl,σ)\gamma = \exp(\sum_{l,\sigma} 2\lambda_{l,\sigma}), כאשר λl,σ\lambda_{l,\sigma} הוא קצב הרעש הנלמד של פאולי שגיאה σ\sigma בשכבה ll במעגל. נרמול מחדש זה מגדיל את השונות בגורם γ2\gamma^2, וכך גם מכפיל את מספר הרצות המעגל הנדרשות על ה-QPU ב-γ2\gamma^2, מה שאנו מכנים עלות הדגימה או תקורת הדגימה. מכיוון ש-γ\gamma גדל באופן אקספוננציאלי, PEC לרוב מוגבל למעגלים רדודים או בעלי מעט קיוביטים. למד עוד על PEC בביטול שגיאות הסתברותי עם מודלי פאולי-לינדבלד דלילים על מעבדים קוונטיים רועשים.

אם נוכל לזהות שגיאות שאין צורך להקהות, נוכל להקטין את עלות הדגימה הזו באופן אקספוננציאלי. צעד ראשון בכיוון זה הוא יישום הקהיית שגיאות מודעת-מקומית, המשתמשת ב"קונוס אור" קונבנציונלי הניתן לחישוב מהיר כדי להקטין את תקורת ה-PEC על ידי חסימת הרגישות של אובזרבבל לשגיאות לאורך המעגל, ומרחיבה את ההיתכנות של PEC לקני מידה גדולים יותר עבור בעיות מסוימות. שגיאות מחוץ לקונוס אור זה אינן יכולות להשפיע על התוצאה הנמדדת ולכן ניתן להוציאן מתהליך ביטול השגיאות. הוצאה זו מקטינה את תקורת הדגימה, במקרים מסוימים באופן משמעותי, מבלי להכניס הטיה נוספת. באופן ספציפי, למדידת אובזרבבל מקומי OO של מעגל בעומק קבוע, תקורת הדגימה הנדרשת מגיעה בסופו של דבר למישור כאשר מגדילים את מספר הקיוביטים במעגל (ראה תרשים 2ב במקומיות והקהיית שגיאות של מעגלים קוונטיים).

קונוסי אור מוצללים (SLC) הולכים רחוק יותר, בשימוש בסימולציות קלאסיות כדי לחסום בצורה הדוקה יותר את הרגישות לשגיאות לאורך המעגל. זה מחליף מעט זמן QPU בזמן CPU ומקטין את תקורת הדגימה הנדרשת לנרמול מחדש של ההטיה. במקום חיתוך קשיח, לכל שגיאה פוטנציאלית במעגל מוקצה "גוון" מדורג החוסם מלמעלה את הרגישות של ערך התוחלת של האובזרבבל לאותה שגיאה. (עבור הגבולות המופצים-לאחור המוצגים להלן, רגישות זו תלויה גם במצב ההתחלתי של המעגל — מצב האפסים בזרימות עבודה טיפוסיות, וגם במדריך זה.) אפיון מעודן זה מאפשר יישומים יעילים וממוקדים יותר של PEC עם שונות מופחתת, תוך מתן יכולת למשתמש לכוונן באופן נשלט את ההטיה בהערכת האובזרבבל. ראה הצללת קונוס אור להקהיית שגיאות קוונטיות מואצת קלאסית לפרטים נוספים.

זרימת העבודה שלנו עבור ה-addon של SLC ממנפת את הספרייה samplomatic יחד עם המחלקות QuantumProgram ו-Executor שנוספו ל-Qiskit Runtime ב-qiskit-ibm-runtime v0.47.0, ומאפשרת למשתמשים שליטה מודולרית יותר בהגדרות ביצוע לדיכוי והקהיית שגיאות תוך שמירה על קלות שימוש. קרא עוד במדריך מודל ביצוע מכוון.

זרימת עבודה של הקהיית שגיאות SLC במבט כללי

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

א. CPU — חסימת ההשפעה של שגיאות פאולי של קיוביט-בודד ושני-קיוביטים לכל שגיאה

  1. התפשטות קדימה (חסימת ההשפעה על האובזרבבל). מפיץ כל שגיאה לסוף המעגל ומחשב את הקומוטטור שלה עם האובזרבבל.

    • קיצוץ איברי אופרטור במהלך האבולוציה כדי לשמור על אפשרות חישוב.

    • חיזוק נוסף של גבולות אלו על ידי התפשטות אחורנית רופפת של האובזרבבל המבוססת על מגבלות מהירות קוונטית.

  2. התפשטות אחורנית (חסימת ההשפעה על המצב ההתחלתי). מפיץ כל שגיאה להתחלת המעגל ומחשב את הקומוטטור שלה עם המצב ההתחלתי.

ב. QPU — למד קצבי רעש. השתמש ב-NoiseLearnerV3 להערכת קצבי מודל הרעש פאולי-לינדבלד.

ג. CPU — תעדוף הקהיה

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

  2. דרג רכיבי רעש להקהיה באמצעות הגבולות שחושבו והקצבים הנלמדים. תעדף כל שגיאת רעש אפשרית בהתאם להשפעתה המוערכת על ההטיה ולעלות הנלווית לתיקונה.

ד. QPU — הזרק אנטי-רעש והרץ. הרץ את המעגל המבוקש עם אנטי-רעש (רעש הפוך) המצוין באמצעות הערות Box.

ה. CPU — הערך אובזרבבל. חשב את ערך התוחלת, תוך יישום פוסט-סלקציה מבוססת-מדידה כדי להקטין את השפעת הרעש הלא-מרקובי.

סקירה כללית של למידת רעש

למידת רעש היא צעד נפוץ במספר שיטות הקהיית שגיאות, המבוצע על ידי לומד הרעש; זה מופיע גם במדריך הגברת שגיאות הסתברותית. ב-NoiseLearnerV3, ניתן לזהות באופן ספציפי את שכבות הרעש שיש ללמוד כאובייקטים מסוג CircuitInstruction, כך שניתן לחשב את גבולות הרעש הרצויים של SLC עבור כל שכבה באופן שתואר לעיל. מודל פאולי-לינדבלד הנלמד מספק מקדמים לשימוש בתעדוף PEC+SLC. את האופן שבו השערים נאספים לשכבות ניתן לקבוע באמצעות הפונקציות הנוחות generate_boxing_pass_manager ו-find_unique_box_instructions, ולאחר מכן להזין אותן לפונקציית העזר של SLC generate_noise_model_paulis, כפי שמתואר בשלב 2 להלן.

חלק 1חלק 2חלק 3
טוויירלינג פאולי לשכבות שערים בני שני קיוביטיםחזרה על זוגות זהות של שכבות ולמידת רעשגזירת נאמנות (שגיאה לכל ערוץ רעש)
Pauli twirlingLearn layerCurve fit

סקירה כללית של עיבוד לאחר-מדידה

לאחר ביצוע על חומרה קוונטית באמצעות מסגרת העבודה samplomatic ו-Executor, אנו ממירים את מדידות מחרוזות הביטים שלנו לערך האובזרבבל הרצוי. במדריך זה, אנו מדגימים את זרימת העבודה על מעגל איזינג משוקף (הנבנה בשלב 1 להלן), שעבורו ערך התוחלת האידיאלי של האובזרבבל הנמדד הוא 1 מכיוון שהמחצית השנייה של המעגל מבטלת את הראשונה. בעת חישוב ערך האובזרבבל עם הפונקציה executor_expectation_values, אנו מיישמים מספר טכניקות עיבוד לאחר-מדידה המקטינות את השפעת הרעש. אלו כוללות הסרת shots שהושפעו מרעש לא-מרקובי, הקהיית שגיאת קריאה, והתחשבות בפרטי יישום ה-PEC שלנו. הפרטים נדונים בשלב 4 להלן.

דרישות

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

  • Qiskit SDK v2.2 ומעלה, עם תמיכת ויזואליזציה

  • Qiskit Runtime v0.47 ומעלה (pip install qiskit-ibm-runtime)

  • Shaded lightcones Qiskit addon v0.1 ומעלה (pip install qiskit-addon-slc)

  • Qiskit addon utils v0.3 ומעלה (pip install qiskit-addon-utils)

  • Samplomatic v0.13 ומעלה (pip install samplomatic)

הגדרה

ראשית, ייבא את החבילות והפונקציות הדרושות להרצת מחברת זו.

# Added by doQumentation — required packages for this notebook
!pip install -q matplotlib numpy qiskit qiskit-addon-slc qiskit-addon-utils qiskit-ibm-runtime samplomatic
from multiprocessing import set_start_method

# Setting this value prevents itertools.starmap deadlock on UNIX systems
set_start_method("spawn")

# Needed to prevent PySCF from parallelizing internally (SLC only)
%set_env OMP_NUM_THREADS=1
env: OMP_NUM_THREADS=1
import numpy as np
from matplotlib import pyplot as plt

from qiskit import QuantumCircuit
from qiskit.quantum_info import SparsePauliOp
from qiskit.transpiler import generate_preset_pass_manager, PassManager

from qiskit_ibm_runtime import (
QiskitRuntimeService,
QuantumProgram,
Executor,
NoiseLearnerV3,
)

import samplomatic
from samplomatic.utils import find_unique_box_instructions
from samplomatic.transpiler import generate_boxing_pass_manager

from qiskit_addon_utils.exp_vals.measurement_bases import (
get_measurement_bases,
)
from qiskit_addon_utils.exp_vals.expectation_values import (
executor_expectation_values,
)
from qiskit_addon_utils.noise_management import (
gamma_from_noisy_boxes,
trex_factors,
)
from qiskit_addon_utils.noise_management.post_selection import PostSelector
from qiskit_addon_utils.noise_management.post_selection.transpiler.passes import (
AddPostSelectionMeasures,
AddSpectatorMeasures,
)

from qiskit_addon_slc.bounds import (
compute_backward_bounds,
compute_forward_bounds,
compute_local_scales,
merge_bounds,
tighten_with_speed_limit,
)
from qiskit_addon_slc.utils import (
generate_noise_model_paulis,
map_modifier_ref_to_ref,
)
from qiskit_addon_slc.visualization import draw_shaded_lightcone

דוגמת סימולטור בקנה מידה קטן

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

  • NoiseLearnerV3 מאפיין באופן ניסויי את ערוץ הרעש פאולי-לינדבלד הדליל על כל שכבת שני-קיוביטים ייחודית. על סימולטור חסר רעש אין רעש לבטל.

  • הפרימיטיב Executor דוגם את המעגלים המטוויירלים, בעלי הזרקת האנטי-רעש, שנוצרו על ידי samplomatic על גבי backend.

חישוב הגבולות של קונוס האור המוצלל הוא קלאסי, אך הוא משמעותי רק ביחס לקצבי הרעש הנלמדים של החומרה, הקובעים את תקציב ההקהיה ותקורת הדגימה. מסיבות אלו אנו מדלגים על דוגמת הסימולטור בקנה מידה קטן ומדגימים את זרימת העבודה המלאה של PEC+SLC ישירות על חומרה, כשכל שלב בדפוס Qiskit מפורק להלן.

דוגמת חומרה בקנה מידה גדול

אנו מריצים את זרימת העבודה המלאה של PEC+SLC על מעגל איזינג משוקף בן 20 קיוביטים המבוצע על חומרת IBM Quantum®, בהתאם לארבעת השלבים של דפוס Qiskit.

שלב 1: מיפוי הבעיה

ההדגמה שלנו מוגדרת כדלקמן:

  1. אנו בוחרים לבצע אבולוציית זמן תחת האמילטוניאן שרשרת איזינג חד-ממדית. שרשרת איזינג חד-ממדית נותנת מבנה מעגל צפוף ונוח, מה שנוח להצגת יישומי PEC.

  2. אנו בוחרים למדוד את האובזרבבל X6Z13\langle X_6 Z_{13}\rangle, המכיל רכיב XX אחד ורכיב ZZ אחד. זה נוח להדגשת האופן שבו גבולות ה-SLC מתייחסים אחרת לסוגי שגיאות שונים.

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

  4. לבסוף, השגת ערך התוחלת האידיאלי של 1 מחייבת שהמעגל יתחיל — ולכן, הודות לשיקוף, באופן אידיאלי גם יסתיים — במצב עצמי בעל ערך עצמי +1 של האובזרבבל. מכיוון שהאובזרבבל מכיל רכיב שאינו ZZ, והפרימיטיב Executor מתחשב בבסיס המדידה הרצוי בסוף המעגל, אנו מוסיפים בתחילת מעגל השיקוף את שערי הכנת הבסיס המתאימים באמצעות פונקציית prepare_basis. אנו משתמשים בפונקציה get_measurement_bases כדי לזהות אילו שערים דרושים והיכן למקם אותם, תוך שמירה על מעקב אחר דקויות אינדקס-קיוביט הנובעות מהמוסכמות של הערת box, כפי שנדון בסעיף על הכנת מדידות בסיס קנוניות.

# Width of the Ising chain (a free parameter of this demonstration)
num_qubits = 20

# The observable to measure: one X component (on qubit 6) and one Z component
# (on qubit 13), as motivated in "Step 1: Map the problem" above
target_obs_sparse = [("XZ", [6, 13], 1.0)]
observable = SparsePauliOp.from_sparse_list(
target_obs_sparse, num_qubits=num_qubits
)
# Determine the measurement basis each qubit requires for this observable.
# The reverser is used during post-processing in Step 4.
bases_virt, reverser_virt = get_measurement_bases(observable)
# Free parameters of the demonstration circuit: the number of Trotter steps
# sets the circuit depth, and rx_angle is the transverse-field rotation angle
# applied in each step (pi/4 makes the dynamics non-Clifford)
num_trotter_steps = 10
rx_angle = np.pi / 4
def construct_ising_circuit(
num_qubits: int,
num_trotter_steps: int,
rx_angle: float,
) -> QuantumCircuit:
circuit = QuantumCircuit(num_qubits)

for _ in range(num_trotter_steps):
circuit.rx(rx_angle, range(num_qubits))
for first_qubit in (1, 2):
for idx in range(first_qubit, num_qubits, 2):
# equivalent to Rzz(-pi/2):
circuit.sdg([idx - 1, idx])
circuit.cz(idx - 1, idx)

return circuit

def prepare_basis(
circuit: QuantumCircuit, basis: list[int]
) -> QuantumCircuit:
# basis is a list of integer values from 0 to 3. These map to the basis measurement as:
# 0 = I; 1 = Z; 2 = X; 3 = Y
assert len(basis) == circuit.num_qubits

out_circ = circuit.copy_empty_like()
for qb, bas in enumerate(basis):
if bas == 2:
out_circ.h(qb)
elif bas == 3:
out_circ.rx(-np.pi / 2, qb)

out_circ.barrier()
out_circ.compose(circuit, inplace=True)
return out_circ

def mirror_circuit(circuit: QuantumCircuit) -> QuantumCircuit:
mirror_circ = circuit.copy_empty_like()
mirror_circ.compose(circuit.inverse(), inplace=True)
mirror_circ.barrier()
mirror_circ.compose(circuit, inplace=True)
mirror_circ.measure_active()
return mirror_circ
# Instantiate the mirrored circuit and prepend the basis-preparation gates
circuit = construct_ising_circuit(num_qubits, num_trotter_steps, rx_angle)
mirrored_circuit = mirror_circuit(circuit)
mirrored_circuit = prepare_basis(mirrored_circuit, bases_virt[0])
mirrored_circuit.draw(
"mpl", fold=-1, scale=0.3, idle_wires=False, measure_arrows=False
)

Output of the previous code cell

שלב 2: אופטימיזציה

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

# Initialize IBM Quantum Compute Service using your saved credentials
service = QiskitRuntimeService()

# Select the least busy Heron device. This tutorial targets Heron processors:
# the usage estimate and the non-Markovian-noise post-selection discussed in
# Step 4 are specific to this processor family.
backend = service.least_busy(
operational=True,
simulator=False,
filters=lambda b: b.processor_type["family"] == "Heron",
)
# Re-fetch with fractional gates enabled (least_busy does not forward this)
# Fractional gates are enabled so the non-Clifford Rx rotations are supported natively.
backend = service.backend(backend.name, use_fractional_gates=True)
print(f"Selected backend: {backend.name}")
Selected backend: ibm_kingston

ראשית, נתרגם (transpile) את המעגל שלנו להוראות ISA, כנדרש להרצה על ה-QPU-ים שלנו. לבחירת הקיוביטים הפיזיים להרצה יש השפעה גדולה על איכות התוצאות, מכיוון שזמני קוהרנטיות ונאמנויות שערים משתנים לאורך מכשיר ונסחפים לאורך זמן. בניסוי קפדני, ייתכן שיהיה שווה לבחור ידנית שרשרת קיוביטים על סמך ניסויי בנצ'מרקינג מהירים (ראה, לדוגמה, המדריך בנצ'מרקינג בזמן אמת לבחירת קיוביטים). לצורכי מדריך זה, אנו מסתמכים על המתרגם (transpiler) של Qiskit לבחירת פריסה על סמך מפת הצימוד של ה-backend ונתוני הכיול העדכניים ביותר שלו.

isa_pm = generate_preset_pass_manager(backend=backend, optimization_level=1)

isa_circuit = isa_pm.run(mirrored_circuit)

# The chain of physical qubits selected by the transpiler
layout = isa_circuit.layout.final_index_layout()
print(f"Selected layout: {layout}")

isa_observable = observable.apply_layout(
layout, num_qubits=isa_circuit.num_qubits
)
Selected layout: [71, 58, 51, 50, 49, 48, 47, 46, 45, 44, 43, 42, 41, 36, 21, 22, 23, 16, 3, 2]
wire_order = layout + [
q for q in range(isa_circuit.num_qubits) if q not in layout
]
isa_circuit.draw(
"mpl",
fold=-1,
scale=0.3,
idle_wires=False,
wire_order=wire_order,
measure_arrows=False,
)

Output of the previous code cell

מיסוק המעגל

לצורך קלות היישום, אנו משתמשים במעבר תרגום generate_boxing_pass_manager, המציב את הוראות המעגל בתוך תיבות מוערות. תיבות אלו מציינות בבירור היכן, במקרה של PEC, יש להזריק אנטי-רעש למעגל. לפרטים על הגדרות, עיין בתיעוד Samplomatic.

שים לב שזרימת העבודה של SLC דורשת שימוש ב-inject_noise_strategy="individual_modification" בהמשך התהליך, מכיוון שזה מאפשר לנו לזהות באופן ייחודי את הערות InjectNoise של הוראות BoxOp במעגל. (לא ניתן לזהות תיבות ללא הערת InjectNoise באופן זה.)

זהירות

חישובי הגבול של SLC מניחים כרגע שהערות ה-InjectNoise ממוקמות עם site="after" (ראו qiskit-addon-slc#33). מכיוון שערך ברירת המחדל של הגדרה זו השתנה בגרסאות עדכניות יותר של samplomatic (ראו samplomatic#372), אנו מגדירים inject_noise_site="after" באופן מפורש בהמשך.

הפונקציה find_unique_box_instructions עוברת על המעגל המתוייב שסופק ומזהה את אלה שיש להם שכבות שני-קיוביטים (2Q) או מדידות ייחודיות, לצורך למידת רעש והזרקת רעש.

# Box circuit with Twirl and InjectNoise annotations
boxes_pm = generate_boxing_pass_manager(
twirling_strategy="active",
inject_noise_strategy="individual_modification",
inject_noise_site="after",
inject_noise_targets="gates",
measure_annotations="all",
)

boxed_circuit = boxes_pm.run(isa_circuit)

# Find the unique instructions (layers) from boxed circuit
unique_2q_instructions = find_unique_box_instructions(
boxed_circuit, normalize_annotations=None, undress_boxes=True
)
boxed_circuit.draw(
"mpl",
fold=-1,
scale=0.3,
idle_wires=False,
wire_order=wire_order,
measure_arrows=False,
)

Output of the previous code cell

הכנת מדידות בסיס קנוניות

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

# Determine the canonical qubits order
meas_box = boxed_circuit.data[-1]
canonical_qubits = [
idx
for idx, qubit in enumerate(boxed_circuit.qubits)
if qubit in meas_box.qubits
]

# map canonical qubit to physical (isa) qubit
c_2_p = {c: p for c, p in enumerate(canonical_qubits)}
# map physical (isa) qubit to virtual qubit (index in original circuit)
p_2_v = {p: v for v, p in enumerate(layout)}
# compute map between virtual and canonical qubit indices.
c_2_v = {c: p_2_v[p] for c, p in c_2_p.items()}

assert len(c_2_v) == num_qubits

bases_canon = [
np.array([base_i[c_2_v[c]] for c in range(num_qubits)], dtype=np.uint8)
for base_i in bases_virt
]

זרימת עבודה להצללת קונוס אור, למידת רעש, והזרקת אנטי-רעש

הערה

במדריך זה, אנו מריצים את חישובי הגבול של SLC לפני שלמידת הרעש מסתיימת, כך שהמעגל שאמור לעבור הקהיית שגיאות רץ קרוב ככל האפשר בזמן למודל הרעש הנלמד. באופן עקרוני, ניתן לבצע במקביל את זרימת העבודה הזו עוד יותר: job של למידת רעש יכול לרוץ בעוד שבמקביל מוערכים גבולות הרעש. עבור מעגל קוונטי שרירותי, חישוב גבול הרעש יכול להתרחב עם תלות אקספוננציאלית חלשה, כך שביצוע חישוב הגבול במקביל (לדוגמה, על פני ליבות CPU רבות) מניב גבולות הדוקים יותר עבור תקציב זמן חישוב נתון, וניתן גם לבצע במקביל את הרצות ה-QPU וחישובי הגבולות עצמם עבור זרימת העבודה היעילה ביותר.

חיזוי פאולים של מודל הרעש שיש ללמוד

לפני הרצת כל למידת רעש על חומרה, אנו קודם כל מונים אילו איברי שגיאת פאולי יכיל מודל הרעש שלנו. נזכיר שאנו ממדלים את הרעש של כל שכבה ייחודית כמודל פאולי-לינדבלד דליל עם קצב שגיאה אחד לכל פאולי במשקל-אחד (XX, YY, או ZZ) בכל קיוביט פעיל, ואחד לכל פאולי במשקל-שניים על כל זוג מחובר של קיוביטים פעילים. הפונקציה generate_noise_model_paulis עוברת על כל שכבה מתוייבת ייחודית של המעגל שסופק ומייצרת בדיוק את רשימת איברי הפאולי הזו, תוך התחשבות בקישוריות הקיוביטים של המעגל.

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

noise_model_paulis = generate_noise_model_paulis(
unique_2q_instructions, backend.coupling_map, boxed_circuit
)
noise_model_rates = {ref: None for ref in noise_model_paulis}
א. חישוב גבולות קדימה

הפונקציה compute_forward_bounds מעריכה את יחסי הקומוטציה בין השערים בכל שכבה לבין איברי הפאולי שנוצרו לעיל, במונחים של האופן שבו שגיאות התפשטות-קדימה משפיעות על האובזרבבל הרצוי AA. עבור שערים המתחלפים (commute) עם איברי הפאולי, לא נעשה דבר. עבור שערי קליפורד, הם נדחפים לכיוון תחילת המעגל. עבור שערים שאינם קליפורד, אנו מקרבים את השפעתם על האובזרבבלים הרצויים כדי לתעדף אותם מאוחר יותר לביטול רעש (לאחר שכל הגבולות מוזגו). גבול זה מושג על ידי יישום תחילה של נורמת L2 (כלומר, שורש הריבועי של סכום ריבועי המקדמים הרלוונטיים של איברי הפאולי). כאשר יש יותר מדי איברי קיוביטים מעורבים, אנו חוזרים לגבול רופף יותר המשתמש באי-שוויון המשולש.

הגדר את פרמטרי חישוב הגבולות

ההגדרות הבאות שולטות בחישובי הגבולות הקלאסיים:

  • slc_atol: הסובלנות המוחלטת של החישובים. איברי אופרטור עם מקדמים מתחת לסף זה נחשבים זניחים ונמחקים.

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

  • slc_evolution_max_terms: המספר המרבי של איברי פאולי שנשמרים בעת התפשטות אופרטורים דרך המעגל. קיצוץ האיברים הקטנים ביותר שומר על אפשרות ביצוע החישוב במחיר של גבולות מעט רופפים יותר.

  • slc_num_processes: מספר תהליכי ה-CPU שביניהם מקבילים את חישוב הגבולות.

  • slc_timeout: תקציב הזמן (בשניות) לחישוב הגבולות. כל שכבות שלא הושגו בתוך תקציב זה מקבלות את הגבול הטריוויאלי של 2.

הערה

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

slc_atol = 1e-8
slc_eigval_max_qubits = 18
slc_evolution_max_terms = 1000
slc_num_processes = 8
slc_timeout = 60
forward_bounds = compute_forward_bounds(
boxed_circuit,
noise_model_paulis,
isa_observable,
evolution_max_terms=slc_evolution_max_terms,
eigval_max_qubits=slc_eigval_max_qubits,
atol=slc_atol,
num_processes=slc_num_processes,
timeout=slc_timeout,
)
Bounds computation timed out.

הדמיה של ה-SLC לבדיקה ידנית

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

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

  • אנו יכולים לראות שמדידת ה-X בקיוביט 6 מתחלפת (commute) עם שגיאת ה-X בשכבה הימנית ביותר.

  • אנו יכולים לראות שהפאולי Z בקיוביט 13 מתחלף עם שגיאת ה-Z בשכבה הימנית ביותר.

  • כאשר אנו מגיעים לתקציב הזמן שצוין לעיל, השכבות הנותרות משמאל ממולאות כולן בגבולות טריוויאליים של שתיים.

for p in "XYZ":
display(
draw_shaded_lightcone(
boxed_circuit,
forward_bounds,
noise_model_paulis,
pauli_filter=p,
scale=0.15,
fold=-1,
idle_wires=False,
wire_order=wire_order,
measure_arrows=False,
)
)

Output of the previous code cell

Output of the previous code cell

Output of the previous code cell

ב. חיזוק גבולות קדימה עם מגבלות מהירות קוונטית

בהמשך אנו מחזקים את הגבולות באמצעות הפונקציה tighten_with_speed_limit. ההתפשטות הגיאומטרית של קונוס האור של האובזרבבל כבר מובאת בחשבון באופן מלא בגבולות הקדימה שחושבו לעיל. מה שפונקציה זו מוסיפה הוא מעבר עצמי-עקבי הממנף את המהירות המוגבלת שבה מידע יכול לזרום דרך המעגל — קונוס האור גדל בכל היותר קיוביט אחד לכל שכבה של שערי שני-קיוביטים — כדי לחזק את הגבולות שחושבו קודם זה ביחס לזה. המידע זורם בשני הכיוונים:

  1. מגבולות הקומוטטור לגבולות על האובזרבבל המופץ-לאחור: אם האובזרבבל מתחלף עם שגיאת ZZ בקיוביט, אז לא יכול להיות לו הרבה רכיב XX או YY שם.

  2. מהגבולות על האובזרבבל המופץ-לאחור לגבולות הקומוטטור: אם לאובזרבבל יש מעט רכיב XX ו-YY בקיוביט, אז הוא חייב כמעט להתחלף עם ZZ שם.

forward_bounds_tighter = tighten_with_speed_limit(
forward_bounds, boxed_circuit, noise_model_paulis, isa_observable
)

הדמיה של ה-SLC לבדיקה ידנית

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

for p in "XYZ":
display(
draw_shaded_lightcone(
boxed_circuit,
forward_bounds_tighter,
noise_model_paulis,
pauli_filter=p,
scale=0.15,
fold=-1,
idle_wires=False,
wire_order=wire_order,
measure_arrows=False,
)
)

Output of the previous code cell

Output of the previous code cell

Output of the previous code cell

ג. חישוב גבולות אחורה

חלק זה של חיזוי הרעש מעריך כיצד שגיאה בשכבה מסוימת יכולה להשפיע על מצב הקלט ρ\rho. הפונקציה compute_backward_bounds הופכת תחילה את המעגל ומסירה שערי מדידה, ולאחר מכן ממשיכה בניתוח דומה לזה שבוצע עבור חישובי הגבולות הקדימה.

backward_bounds = compute_backward_bounds(
boxed_circuit,
noise_model_paulis,
evolution_max_terms=slc_evolution_max_terms,
num_processes=slc_num_processes,
timeout=slc_timeout,
)

הדמיה של ה-SLC לבדיקה ידנית

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

  • אנו יכולים לראות בבירור כיצד שגיאות Z מתחלפות בהתחלה עם המצב ההתחלתי |0⟩.

  • רק בקיוביט 6, שם אנו מאתחלים את המצב העצמי בעל ערך +1 של בסיס X, שגיאת Z לא מצליחה להתחלף, בעוד ששגיאת X כן מתחלפת.

for p in "XYZ":
display(
draw_shaded_lightcone(
boxed_circuit,
backward_bounds,
noise_model_paulis,
pauli_filter=p,
scale=0.15,
fold=-1,
idle_wires=False,
wire_order=wire_order,
measure_arrows=False,
)
)

Output of the previous code cell

Output of the previous code cell

Output of the previous code cell

תצוגה מקדימה של גבולות ממוזגים ללא קצבי רעש נלמדים

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

הערה חשובה

הנקודה האופטימלית שבה יש לעבור מגבולות אחורה לגבולות קדימה תלויה בקצבי הרעש הנלמדים. בשלב זה עדיין לא למדנו שום קצבים: noise_model_rates אותחל ל-None עבור כל שכבה, ש-merge_bounds מתייחס אליו כאל קצבי רעש אחידים. הגבולות הממוזגים המוצגים כאן הם אפוא רק תצוגה מקדימה; אנו מחשבים אותם מחדש עם הקצבים הנלמדים בפועל בשלב 3.

merged_bounds = merge_bounds(
boxed_circuit,
forward_bounds_tighter,
backward_bounds,
noise_model_rates,
)
Missing noise rates. Partitioning backward/forward commutator bounds by assuming uniform error rates.
Optimal spacetime partitioning not implemented!Just partitioning list of noisy boxes.

הדמיה של ה-SLC לבדיקה ידנית

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

  • הפונקציה שלעיל מלמדת אותנו שנבחרת חלוקה שבה מתרחש המעבר מגבולות אחורה לגבולות קדימה מחוזקים.

  • אנו יכולים לראות למטה שה-SLCים כעת מכילים גבולות אחוריים חלקיים וגבולות קדימה מחוזקים חלקיים.

for p in "XYZ":
display(
draw_shaded_lightcone(
boxed_circuit,
merged_bounds,
noise_model_paulis,
pauli_filter=p,
scale=0.15,
fold=-1,
idle_wires=False,
wire_order=wire_order,
measure_arrows=False,
)
)

Output of the previous code cell

Output of the previous code cell

Output of the previous code cell

שלב 3: ביצוע

בסעיף זה אנו מתחילים את חלק זרימת העבודה המשתמש במכשיר קוונטי אמיתי. עבור שיטת הקהיית שגיאות מבוססת-למידה זו, יש שני שלבים:

  1. למד את הרעש באמצעות NoiseLearnerV3.

  2. בצע מעגל הקהיית שגיאות עם מסגרת העבודה samplomatic ו-Executor.

עם השגיאות החסומות ממעגל הקוונטי שלנו, אנו לומדים את קצבי הרעש הקשורים כדי לתעדף את תקציב השגיאות שלנו, לקבוע את תקורת הדגימה, ולבצע על QPU.

א. למידת קצבי הרעש

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

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

post_selection_enabled = True
# Match the deepest noise-learning depth to the depth of the circuit being
# mitigated. NLv3 ``layer_pair_depths`` are measured in layer pairs (a layer
# plus its inverse), so the deepest value is half the circuit's two-qubit-layer
# depth. Learning to this depth markedly improves the quality of the mitigation.
#
# We measure the two-qubit-layer depth on the pre-boxed ISA circuit: after
# boxing, every two-qubit gate is hidden inside a full-width ``BoxOp``, so a
# ``num_qubits == 2`` filter on ``boxed_circuit`` matches nothing (and
# ``QuantumCircuit.depth`` does not recurse into boxes).
depth_2q = isa_circuit.depth(lambda instr: instr.operation.num_qubits == 2)
max_layer_pair_depth = depth_2q // 2 # dividing by 2 since we want pairs

# Use a fixed schedule of learning depths, but drop any that exceed the circuit's
# depth and always cap the deepest value at ``max_layer_pair_depth`` so we never
# learn deeper than the circuit being mitigated.
candidate_depths = [1, 2, 4, 8, 12, 16, 24, 32, 40, 48]
layer_pair_depths = sorted(
{d for d in candidate_depths if d < max_layer_pair_depth}
| {max_layer_pair_depth}
)

noise_learner_options = {
"num_randomizations": 64,
"shots_per_randomization": 128,
"layer_pair_depths": layer_pair_depths,
"post_selection": {
"enable": post_selection_enabled,
"strategy": "edge",
"x_pulse_type": "rx",
},
"environment": {"job_tags": ["TUT_SLC"]},
}

noise_learner = NoiseLearnerV3(backend, noise_learner_options)
noise_learner_job = noise_learner.run(unique_2q_instructions)
noise_learner_result = noise_learner_job.result()
if post_selection_enabled:
print(
"Minimum fraction of shots kept for noise learning experiments: ",
end="",
)
print(
f"{min([min(d.values()) for d in [nlr.metadata['post_selection']['fraction_kept'] for nlr in noise_learner_result[:2]]]):.2f}"
)
Minimum fraction of shots kept for noise learning experiments: 0.71
# Get a dict mapping each InjectNoise.ref to its learned PauliLindbladMap
refs_2_plm = noise_learner_result.to_dict(
unique_2q_instructions, require_refs=False
)

ב.i. עדכון גבולות ממוזגים עם קצבי רעש נלמדים בפועל

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

merged_bounds = merge_bounds(
boxed_circuit,
forward_bounds_tighter,
backward_bounds,
refs_2_plm,
)
Optimal spacetime partitioning not implemented!Just partitioning list of noisy boxes.

ב.ii. חישוב local_scales עבור ביצוע החומרה

compute_local_scales בוחן כל שגיאת רעש אפשרית במעגל ומעריך כמה שגיאה זו יכולה להטות את המדידה הסופית, וכן כמה יקר יהיה לתקן אותה. לאחר מכן היא מדרגת את השגיאות לפי כמה שווה להקהות אותן ובוחרת את קבוצת המשנה המקטינה את ההטיה במידה הרבה ביותר, תוך שמירה בגבולות תקציב עלות-הדגימה המותר (או השגת דיוק רצוי). התוצאה היא קבוצה של גורמי קנה-מידה המציינים אילו שגיאות יוקהו באופן פעיל ואילו יישארו לא-מוקהות (local_scales), יחד עם תקורת עלות הדגימה הכוללת החזויה (sampling_costs) וההטיה הנותרת (residual_bias_bound).

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

id_map = map_modifier_ref_to_ref(boxed_circuit)
summed_rates = 0.0
for box_id, noise_id in id_map.items():
learned_plm = refs_2_plm[noise_id]
summed_rates += np.sum(learned_plm.rates)
# print(f"{box_id}:\tgamma = {np.exp(2 * summed_rates):1.6e}\tsampling cost = {np.exp(4 * summed_rates):1.6e}")
total_gamma = np.exp(2 * summed_rates)
print(
f"Full PEC gamma={total_gamma}, sampling cost (gamma^2) = {total_gamma**2}"
)
Full PEC gamma=12.750876317157042, sampling cost (gamma^2) = 162.58484685543633
biases = []
costs = []
for bias in [0.0] + np.arange(0.001, 0.102, 0.01).tolist():
_, cost_, bias_ = compute_local_scales(
boxed_circuit,
merged_bounds,
refs_2_plm,
sampling_cost_budget=np.inf,
bias_tolerance=bias,
)
biases.append(bias_)
costs.append(cost_)

פשרה בין תקורת דגימה להטיה שיורית

xticks = np.arange(0, 11)

fig, ax = plt.subplots()
ax.scatter(
[0], [total_gamma**2], marker="D", c="tab:orange", label="full PEC"
)
ax.plot(
100 * np.array(biases),
np.array(costs),
"o-",
c="tab:blue",
label="PEC+SLC",
)
ax.set_yscale("log")
ax.set_xticks(xticks, [f"{x:.1f}" for x in xticks])

ax.set_xlabel("Remaining bias [%]")
ax.set_ylabel(r"Sampling overhead, $\gamma^2$")
ax.grid()
ax.legend()
fig.suptitle("PEC sampling overhead reduction due to SLC")
Text(0.5, 0.98, 'PEC sampling overhead reduction due to SLC')

Output of the previous code cell

chosen_bias_thres = 0.1
local_scales, sampling_cost, residual_bias_bound = compute_local_scales(
boxed_circuit,
merged_bounds,
refs_2_plm,
sampling_cost_budget=np.inf,
bias_tolerance=chosen_bias_thres,
)
print(
f"PEC+SLC sampling cost (gamma^2) = {sampling_cost} "
f"w/ remaining bias = {100 * residual_bias_bound:.1f}%"
)
PEC+SLC sampling cost (gamma^2) = 48.13646509622307 w/ remaining bias = 10.0%

ג. ביצוע המעגל המבוקש עם אנטי-רעש

ג.i. הכנת מעגל תבנית באמצעות samplex

ה-samplex הוא תוצר של השיטה build של Samplomatic, המקודדת את כל המידע הדרוש ליצירת פרמטרים אקראיים עבור template_circuit. אלו משמשים לאחר מכן להקמת אובייקטי QuantumProgram, המורצים בתורם על QPU עם הפרימיטיב Executor. כל QuantumProgram יכול להכיל מספר פריטים, שניתן לחשוב עליהם כזוג של template ו-samplex.

ראו את המדריך Hello samplomatic לפרטים.

# Build template circuit and samplex for later use with the "Executor"
template_circuit, samplex = samplomatic.build(boxed_circuit)
# Set up postselection if it's been enabled
if post_selection_enabled:
# Set up post selection PM (to add PS instructions)
post_selection_pm = PassManager(
[
AddSpectatorMeasures(backend.coupling_map),
AddPostSelectionMeasures(x_pulse_type="rx"),
]
)
final_template_circuit = post_selection_pm.run(template_circuit)
else:
final_template_circuit = template_circuit

c.ii. הגדרת ה-QuantumProgram

num_randomizations = 4096
shots_per_randomization = 64
chunk_size = 256
# Set up QuantumProgram
program = QuantumProgram(shots=shots_per_randomization, noise_maps=refs_2_plm)

# no EM

# Collect up a dict of the other arguments that need to be bound to samplex_inputs
samplex_inputs = {
f"noise_scales.{ref}": float(0) for ref in local_scales.keys()
}
samplex_inputs |= {"basis_changes": {"basis0": bases_canon[0]}}

# Convert samplex_inputs into a dict to pass to QuantumProgram
samplex_arguments = (
samplex.inputs().bind(**samplex_inputs).make_broadcastable()
)

program.append_samplex_item(
circuit=final_template_circuit,
samplex=samplex,
samplex_arguments=samplex_arguments,
shape=(num_randomizations,),
chunk_size=chunk_size,
)

# plain PEC

# Collect a dict of the other arguments that need to be bound to samplex_inputs
samplex_inputs = {
f"noise_scales.{ref}": float(-1) for ref in local_scales.keys()
}
samplex_inputs |= {"basis_changes": {"basis0": bases_canon[0]}}

# Convert samplex_inputs into a dict to pass to QuantumProgram
samplex_arguments = (
samplex.inputs().bind(**samplex_inputs).make_broadcastable()
)

program.append_samplex_item(
circuit=final_template_circuit,
samplex=samplex,
samplex_arguments=samplex_arguments,
shape=(num_randomizations,),
chunk_size=chunk_size,
)

# PEC+SLC

# Collect a dict of the other arguments that need to be bound to samplex_inputs
samplex_inputs = {
f"noise_scales.{ref}": float(-1) for ref in local_scales.keys()
}
samplex_inputs |= {"basis_changes": {"basis0": bases_canon[0]}}
samplex_inputs |= {"local_scales": local_scales}

# Convert samplex_inputs into a dict to pass to QuantumProgram
samplex_arguments = (
samplex.inputs().bind(**samplex_inputs).make_broadcastable()
)

program.append_samplex_item(
circuit=final_template_circuit,
samplex=samplex,
samplex_arguments=samplex_arguments,
shape=(num_randomizations,),
chunk_size=chunk_size,
)

c.iii. הרצת התוכנית עם הפרימיטיב Executor

executor = Executor(backend)
job_exec = executor.run(program)
results_exec = job_exec.result()

שלב 4: עיבוד לאחר מכן

כאשר אנחנו מחשבים את ערך התוחלת הסופי המעניין אותנו באמצעות executor_expectation_values, אנחנו מיישמים כמה טכניקות עיבוד-שלאחר-מכן כדי לעזור להבטיח שנקבל תוצאות באיכות הגבוהה ביותר האפשרית. ראשית, אנחנו מיישמים את twirled readout error extinction (TREX) שלנו, שמתחשב בכל שגיאה שמתרחשת במהלך תהליך הקריאה. לאחר מכן, אנחנו מתקנים שגיאות שנובעות מרעש לא-מרקובי במעבדי Heron שלנו באמצעות שיטת בחירה-לאחר-מכן (post-selection). שיטה זו מודדת קיוביטים פעילים וקיוביטים צופים (spectator), ואז מיישמת סיבוב איטי על כל קיוביט, ואז מודדת שוב. במקרים שבהם שתי המדידות לא מאשרות היפוך קיוביט כפי שצפוי, יריות (shots) אלה נזרקות באמצעות יישום mask מתוך ה-PostSelector. במסגרת חישוב המסכה, ניתן להגדיר אסטרטגיה מסוימת לסינון על בסיס צמתים בודדים של קיוביט אחד או קשתות שכנות של קיוביטים צופים, מה שיכול להשפיע הן על מספר היריות שמסוננות והן על איכות התוצאות.

measurement_noise_map = noise_learner_result[2].to_pauli_lindblad_map()
trex_scale_factors = trex_factors(measurement_noise_map, reverser_virt)
post_selection_strategy = "node"
def post_process_conv(datum, steps=16, gamma=None, ps=False, trex=False):
meas = datum["meas"]
flips = datum["measurement_flips.meas"]
signs = datum.get("pauli_signs", None)

meas_basis_axis = None
avg_axis = 0

mask = None
if ps and post_selection_enabled:
# Post-select the results
post_selector = PostSelector.from_circuit(
circuit=final_template_circuit, coupling_map=backend.coupling_map
)

# Compute the ps mask for filtering results
mask = post_selector.compute_mask(
datum, strategy=post_selection_strategy
)

# Compute fraction of shots kept from post selection
total_num_shots = num_randomizations * shots_per_randomization
ps_ratio = np.sum(mask) * 100 / total_num_shots / len(bases_canon)
print(
f"With {post_selection_strategy}-based post selection ({ps_ratio:.1f}% of shots kept):"
)

results = []
for i in range(steps, num_randomizations + 1, steps):
# Compute mitigated expvals w/out post-selection
res = executor_expectation_values(
meas[:i],
reverser_virt,
meas_basis_axis,
avg_axis=avg_axis,
measurement_flips=flips[:i],
pauli_signs=signs[:i] if signs is not None else None,
postselect_mask=mask[:i] if mask is not None else None,
rescale_factors=trex_scale_factors if trex else None,
gamma_factor=gamma,
)
results.append(res[0])
return results
gamma_pec = gamma_from_noisy_boxes(refs_2_plm, id_map)
gamma_slc = gamma_from_noisy_boxes(refs_2_plm, id_map, local_scales)
steps = 16
results = {}

for label, result_idx, gamma, use_ps, use_trex in [
("PEC", 1, gamma_pec, True, True),
("PEC+SLC", 2, gamma_slc, True, True),
("Unmitigated", 0, None, False, False),
]:
res = post_process_conv(
results_exec[result_idx],
steps=steps,
gamma=gamma,
ps=use_ps,
trex=use_trex,
)
results[label] = res
With node-based post selection (24.1% of shots kept):
With node-based post selection (24.2% of shots kept):

מבחינה בתוצאות הניסוי, אנחנו יכולים להשוות ישירות בין ההתנהגות של הגישות השונות: PEC, PEC בשילוב עם SLC, ובסיס ההשוואה הבלתי-ממותן. כמה פרטים ספציפיים כדאי להדגיש:

  • התוצאה הבלתי-ממותנת נמצאת הרחק מחוץ לרצועת ההטיה של 10% (בקרבת 0.35) ואינה מושפעת ממספר האקראיזציות.

  • במכשיר הזה, PEC מלא נושא תקורת דגימה (sampling overhead) של γ21.6×102\gamma^2 \approx 1.6\times10^2. לאחר תנודות חזקות מוקדמות, אומדן ה-PEC הפשוט משחזר את האות ומתייצב סביב 1.1, וחורג מעט מהערך המדויק. שימו לב שרצועת ההטיה של 10% חלה רק על אמדן ה-PEC+SLC: PEC פשוט הוא באופן עקרוני בלתי-מוטה, כך שהסטייה השארית הזו אינה מכוסה על ידי ערבות להטיה אלא משקפת תנודות סטטיסטיות ואי-דיוקים במודל הרעש הנלמד, מה שסביר בגודל בעיה זה.

  • SLC מפחית את התקורה בעוד כפי ~3.4 (ל-γ248\gamma^2 \approx 48, עבור גבול הטיה שארית של כ-10%). גם PEC+SLC מתנודד בחוזקה במספר קטן של אקראיזציות, ובתחילה אף מגיע לתחום הבלתי-פיזיקלי, אך האומדן שלו מתכנס בהתמדה לעבר הערך המדויק, ומתייצב סביב 1.0 היטב בתוך הרצועה. הוא תואם או עולה על הדיוק של PEC פשוט בכשליש מעלות הדגימה, מה שמדגים את היתרון של lightcone shading.

  • מרווחי השגיאה של שני האומדנים הממותנים מצטמצמים ככל שהאקראיזציות מצטברות; במספר קבוע של אקראיזציות, לאומדן PEC+SLC יש שונות סטטיסטית קטנה יותר, בהתאם ל-γ2\gamma^2 הקטן יותר שלו.

שימו לב שקצבי הרעש הנלמדים (ולכן התקורות והאומדנים הממותנים) תלויים ב-backend ובכיול שלו בזמן ההרצה, כך שהרצה חוזרת של המחברת הזו יכולה להניב ערכים שונים מבחינה כמותית.

fig, ax = plt.subplots(1, 1, figsize=(12, 6))

ax.axhline(1.0, color="black", label="Exact")
ax.fill_between(
[-50, 4100], -10, 0, color="grey", alpha=0.25, label="Unphysical"
)
ax.fill_between([-50, 4100], 1, 10, color="grey", alpha=0.25)
ax.fill_between(
[-50, 4100], 0.9, 1.1, color="red", alpha=0.25, label="10% bias"
)

for label, res in results.items():
ax.errorbar(
list(range(steps, num_randomizations + 1, steps)),
[r[0] for r in res],
yerr=[r[1] for r in res],
alpha=0.75,
marker="o",
linestyle="",
markerfacecolor="none",
label=label,
)

ax.set_ylabel(r"$\langle X_{6}Z_{13}\rangle$")
ax.set_xlabel("# randomizations")
ax.grid()

ax.legend(ncols=2)
ax.set_ylim([-0.1, 2.0])
ax.set_xlim([-50, 4100])
(-50.0, 4100.0)

Output of the previous code cell

צעדים הבאים

אם מצאתם את העבודה הזו מעניינת, ייתכן שתתעניינו בחומרים הבאים: