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

מעבר מ-Sampler ל-Executor

המדריך הזה מתאר כיצד להעביר עומסי עבודה של דגימה קוונטית מהפרימיטיב Sampler של IBM Quantum® לפרימיטיב Executor.

שחרור בטא

הפרימיטיב Executor הוא חלק מ directed execution model. כל הרכיבים ב-directed execution model הם כרגע בבטא ועשויים לא להיות יציבים. אתה מוזמן לבדוק אותם ולספק משוב על ידי פתיחת issue במאגרי GitHub של Samplomatic או qiskit-ibm-runtime.

האם כדאי לך לעבור?

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

עבור ל-Executor אם אתה מדען מידע קוונטי שמריץ ניסויים בקנה מידה של תועלת מעשית (utility-scale) וזקוק לשליטה עדינה ובת-שחזור בטכניקות כמו Pauli twirling, למידה והזרקה של מודל רעש, ושינויי בסיס — או שזקוק לאחת מהיכולות הנוספות שמספק Executor.

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

מגבלות ואזהרות

מכיוון ש-Executor ומודל הביצוע המכוון (directed execution model) הם בבטא, שים לב לדברים הבאים לפני שתחליט לעבור:

  • אין עדיין תמיכה בסימולטור: בניגוד ל-Sampler, שיש לו מימוש AerSampler ב-qiskit-aer לסימולציה מקומית, כרגע אין backend סימולטור עבור Executor. תמיכה בסימולטור צפויה להגיע בקרוב. בינתיים, אתה עדיין יכול לבדוק ולדגום את מעגל התבנית באופן מקומי כדי לוודא את תהליך העבודה שלך לפני שאתה מגיש אותו לחומרה.

  • המדריך הזה מכסה רק את Sampler, לא את Estimator. מעבר מ-Estimator ל- Executor הרבה יותר מורכב ממעבר מ-Sampler מכיוון ש-Estimator מחשב ערכי ציפייה במקום להחזיר דגימות גולמיות. שחזור ההתנהגות של Estimator עם Executor דורש עיבוד נוסף לאחר מכן. פונקציות עזר שיסייעו במעבר מ-Estimator ל-Executor עדיין בפיתוח, כך שהמדריך הזה מתאר בכוונה רק את תהליך העבודה של Sampler.

הבדלים מרכזיים בין Executor ל-Sampler

גם Sampler וגם Executor דוגמים את רגיסטרי הפלט של מעגלים קוונטיים, אבל הם מכוונים למשתמשים שונים:

  • Sampler הוא הפשטה ברמה גבוהה. יש לו את המאפיינים הבאים:

    • יש לו דיכוי שגיאות מובנה (dynamical decoupling ו-twirling).

    • הוא מקבל החלטות משתמעות עבורך.

    • הוא מתוכנן כך שמפתחי אלגוריתמים יוכלו להתמקד בחדשנות ולא בהמרת נתונים.

  • Executor הוא חלק מ-directed execution model. הוא שונה מ-Sampler בהרבה דרכים ויש לו את המאפיינים הבאים:

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

    • הוא לא מקבל שום החלטות משתמעות. הוא פועל לפי ההנחיות שלך במדויק, ומעניק שליטה ושקיפות מלאה.

    • Executor ו-Samplomatic יחד חושפים יכולות נוספות ש-Sampler לא מציע, כולל (אך לא רק) את הבאות:

      • יותר קבוצות twirling: Samplomatic מאפשר לך לבחור איזו קבוצת twirling להחיל לכל box, במקום להיות מוגבל לאסטרטגיה היחידה ש-Sampler מחיל עבורך. הוא גם תומך בקבוצות twirling שאינן Pauli, כמו קבוצת ה-twirling "local_c1".
      • מדידות kerneled וclassified יחד: הגדרת QuantumProgram.meas_level = "both" (שנוספה ב-qiskit-ibm-runtime v0.48.0) מבקשת שמדידות classified וkerneled יופיעו שתיהן בתוצאות, במקום לבחור סוג מדידה יחיד לכל עבודה.
      • Twirling עבור מעגלים עם שערים חלקיים (fractional gates): Executor יכול להחיל twirling על מעגלים המכילים שערים חלקיים.
      • הפחתת שגיאות עדינה וניתנת להרכבה: לדוגמה, בחירה אילו שכבות מעגל להפחית והתאמת קצבי הרעש שמוזרקים למעגל.
      הערות
      • יכולות חדשות עתידיות צפויות להשתחרר ל-Executor קודם ועשויות שלא להיות מועברות ל-Sampler. אם אתה מסתמך על גישה לתכונות העדכניות ביותר, Executor היא הבחירה הבטוחה יותר לעתיד.
      • חבילת ה-Qiskit הבסיסית עדיין לא מספקת מחלקת בסיס (base class) עבור הפרימיטיב Executor (אך מספקת עבור SamplerV2).

מיפוי מושגי

הטבלה הבאה מדגימה כיצד מושגי Sampler ממופים ל-Executor.

מושגSamplerExecutor
ייבואfrom qiskit_ibm_runtime import SamplerV2from qiskit_ibm_runtime import Executor
קלטרשימה של PUBs (tuples)QuantumProgram של אובייקטי QuantumProgramItem
מעגל ופרמטרים(circuit, params, shots) tupleprogram.append_circuit_item(circuit, circuit_arguments=...)
TwirlingTwirlingOptionsבמפורש באמצעות תיבות מוערות ו-samplex (append_samplex_item)
קריאת הרצהsampler.run([pub, ...])executor.run(program)
סוג תוצאהPrimitiveResult של SamplerPubResultQuantumProgramResult (ניתן לאיטרציה)
גישה לנתוניםresult[0].data.<register> (BitArray)result[0]["<register>"] (np.ndarray)
ניהול רעשאפשרויות מובנותחייב להיות מורכב ידנית (annotations, samplex, NoiseLearnerV3)

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

  1. התקן את Samplomatic.

  2. שנה את ה-imports.

  3. החלף tuples של PUB.

  4. שנה כיצד shots מבוטאים.

  5. עדכן אפשרויות אחרות כנדרש.

  6. עדכן את פקודת ה-run.

  7. עדכן את ניתוח התוצאות.

  8. בטל twirling.

שלב 1. התקנת החבילות הנדרשות

Executor ו-directed execution model דורשים את החבילה samplomatic:

pip install qiskit qiskit-ibm-runtime samplomatic

# For visualization support:
# pip install samplomatic[vis]
הערות גרסה
  • מומלץ qiskit-ibm-runtime v0.48.0 מכיוון שהוא מוסיף את האפשרות meas_level = "both" ואת קבוצת ה-twirling local_c1.
  • נדרש qiskit >= 2.3.0.
  • נדרש samplomatic >= 0.18.0.

שלב 2. שינוי ה-imports

Sampler:

from qiskit_ibm_runtime import SamplerV2 as Sampler

Executor:

from qiskit_ibm_runtime import Executor, QuantumProgram

שלב 3. החלפת tuples של PUB ב-QuantumProgram

במקום להעביר רשימה של tuples (PUBs), כשאתה משתמש ב-Executor, אתה בונה QuantumProgram ומצרף אליו items.

QuantumProgram מקבל items מסוג circuit ו-samplex:

  • append_circuit_item: מצרף CircuitItem, שהוא מעגל ו(אופציונלית) ערכי הפרמטרים שלו. הוא מבוצע כמות שהוא, ללא כל אקראיות.

    השתמש בזה כשאתה פשוט רוצה לדגום מעגל, בדיוק כפי ש-Sampler היה עושה עם PUB שאין לו twirling; לדוגמה, כשאתה מגיש עבודת דגימה פשוטה, או כשכבר כללת ידנית כל וריאנט שתרצה.

  • append_samplex_item: מצרף samplexItem, שהוא מעגל תבנית בתוספת samplex שיוצר קבוצות פרמטרים אקראיות בצד השרת.

    השתמש בזה כשאתה רוצה שהתוכן של המעגל יהיה אקראי. המקרה העיקרי הוא עם twirling (של שער או מדידה) או הזרקת רעש. היכולת הזאת מחליפה את ה-twirling המובנה של Sampler.

QuantumProgram יחיד יכול לקבל את שני סוגי הפריטים; כל פריט מצורף מבוצע כ משימה עצמאית ומייצר רשומה משלו בתוצאות. באופן כללי, השתמש ב-append_circuit_item כשהמעגל שלך לא צריך להיות אקראי. אחרת, השתמש ב-append_samplex_item.

הסעיפים הבאים מציגים כל אחד בתורו: מעגלים פרמטריים שמשתמשים ב- append_circuit_item, ומעבר twirling באמצעות append_samplex_item.

בדוגמאות הקוד הבאות, isa_circuit מתייחס למעגל שעבר טרנספילציה כדי לתאם לארכיטקטורת סט ההוראות (Instruction Set Architecture) (ISA) של ה-Backend היעד. isa_circuit זה מכיל שני פרמטרים.

שלב 3א. העברת מעגלים פרמטריים

עם Sampler, ערכי הפרמטרים הם האיבר השני בטאפל ה-PUB. עם Executor, העבר אותם כ-circuit_arguments ל-append_circuit_item.

Sampler:

params = np.random.rand(10, circuit.num_parameters) # 10 parameter sets
pubs = (isa_circuit, params)

Executor

program = QuantumProgram(shots=1024)
program.append_circuit_item(
isa_circuit,
circuit_arguments=np.random.rand(10, circuit.num_parameters), # 10 sets
)

# CircuitItem result shape: (parameter_sets, shots, register_bits) -> (10, 1024, 2)
result_0 = result[0]["meas"]

שלב 3ב. העברת twirling מובנה להערות מפורשות

זהו השינוי המשמעותי ביותר. Sampler מיישם twirling עבורך באמצעות אפשרויות. עם Executor, אתה מצהיר על הכוונה הזו במפורש באמצעות תיבות מוערות ו-samplex (מ-Samplomatic).

Sampler (twirling באמצעות אפשרויות):

sampler = Sampler(mode=backend)
sampler.options.twirling.enable_gates = True
sampler.options.twirling.enable_measure = True

Executor (twirling באמצעות תיבות ו-samplex):

from samplomatic import build
from samplomatic.transpiler import generate_boxing_pass_manager

# 1. Group gates and measurements into annotated boxes with twirling annotations
boxes_pm = generate_boxing_pass_manager(
enable_gates=True, # gate twirling
enable_measures=True, # measurement twirling
)
boxed_circuit = boxes_pm.run(isa_circuit)

# 2. Build the (template circuit, samplex) pair.
# The template circuit's single-qubit gates are replaced by parameterized gates;
# the samplex encodes how to generate the randomized parameters at runtime.
template_circuit, samplex = build(boxed_circuit)

# 3. Append as a samplex item, specifying the number of randomizations
program = QuantumProgram(shots=1024)
program.append_samplex_item(
template_circuit,
samplex=samplex,
samplex_arguments={
"parameter_values": np.random.rand(10, 2), # original circuit params
},
shape=(28, 10), # 28 randomizations x 10 parameter sets
)

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

אימות: דגימת מעגל התבנית באופן מקומי

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

# Check which inputs the samplex requires (for the twirling example above,
# this is just the original circuit's parameter values).
print(samplex.inputs())

# Bind the required inputs, then draw a few randomizations locally.
inputs = samplex.inputs().bind(
parameter_values=np.random.rand(2), # one set of the original circuit's params
)
outputs = samplex.sample(inputs, num_randomizations=3)

# Assign one randomization's parameter values to the template circuit and inspect it.
bound_template = template_circuit.assign_parameters(outputs["parameter_values"][0])
bound_template.draw("mpl", idle_wires=False)

כדי להתקדם עוד יותר, תוכל לוודא שכל רנדומיזציה שקולה לוגית למעגל המקורי, לדוגמה, על ידי המרת שניהם לאובייקטי Operator והשוואת המימושים היוניטריים שלהם (לאחר התחשבות בתיקוני outputs["measurement_flips.<register>"] שמבטלים twirling של מדידה), או על ידי השוואת ערכי ציפייה מהרצה מקומית של StatevectorSampler או StatevectorEstimator. ראה את מדריך קלטים ופלטים של Samplex של Samplomatic להדגמה מלאה.

שלב 4. שינוי אופן בקשת ה-shots

העבר shots מה-PUB אל QuantumProgram(shots=...). ב-Executor, shots חל על העבודה כולה. הגש עבודות מרובות אם אתה זקוק למספרי shots שונים.

Sampler:

# Run — shots are passed to run()
sampler = Sampler(mode=backend)
job = sampler.run([(isa_circuit, None, 25)])

Executor:

# Build a QuantumProgram — shots are on the program
program = QuantumProgram(shots=25)
program.append_circuit_item(isa_circuit)

שלב 5. עדכון אפשרויות לפי הצורך

יש פחות אפשרויות זמינות ל-Executor מאשר ל-Sampler, כי בחירות הפחתת שגיאות שוכנות כעת בהערות וב-samplex שלך במקום באפשרויות.

קיים גם הבדל מבני בהיכן ההגדרות שוכנות.

  • עם Sampler, הכל, כולל בחירות המשפיעות על עיבוד תוצאות לאחר מכן, מוגדר באפשרויות ה-Primitive או ב-PUB.

  • עם Executor, בחירות המשפיעות על אופן עיצוב ועיבוד תוצאות העבודה נקבעות ב-QuantumProgram, ולא ב- ExecutorOptions.

Examples:

SamplerExecutor
shotsQuantumProgram(shots=...)
meas_typeQuantumProgram(meas_level=...)

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

יש לציין שהאפשרויות twirling ו-dynamical_decoupling קיימות ב-Sampler אך לא ב-Executor. במקום זאת, ערכי אפשרויות אלה באים לידי ביטוי דרך מודל הביצוע המכוון.

Example:

from qiskit_ibm_runtime import Executor, ExecutorOptions

options = ExecutorOptions(
environment={"log_level": "INFO"},
execution={"init_qubits": True},
)
# or mutate after construction:
options = ExecutorOptions()
options.environment.log_level = "INFO"
options.execution.init_qubits = True

executor = Executor(mode=backend, options=options)

שלב 6. עדכון פקודת ה-run

הקלט לעבודת Executor הוא התוכנית, במקום PUBs.

Sampler:

# Submit a job
sampler.run([(isa_circuit, parameter_values)])

Executor:

# Submit a job
executor.run(program)

שלב 7. שינוי אופן הגישה לתוצאות

ב-Executor, התוצאות הן מערכי NumPy, לא אובייקטי BitArray. השתמש במחרוזת השם כאינדקס (result[0]["meas"]) וקבל בחזרה np.ndarray. אין צורך לזכור את נתיב התכונה .data.<register>.

כדי לעדכן מ-Sampler ל-Executor, שנה result[i].data.<reg> (BitArray) ל-result[i]["<reg>"] (np.ndarray), ואז כתוב מחדש עיבוד המבוסס על get_counts כפעולות NumPy.

משימהSamplerExecutor
קבלת נתוני רגיסטרresult[0].data.measresult[0]["meas"]
סוג נתוניםBitArraynp.ndarray
מילון ספירותresult[0].data.meas.get_counts()עיבוד המערך ידנית
רגיסטרים מרוביםresult[0].data.<name> עבור כל רגיסטרresult[0]["<name>"] עבור כל רגיסטר
צורת מערך של CircuitItem-(parameter_sets, shots, register_bits)
צורת מערך של SamplexItem-(randomizations, parameter_sets, shots, register_bits)
ביטול twirling של מדידהאוטומטיresult[i]["measurement_flips.<name>"] + XOR
הערה

ה-BitArray של Sampler מציע פונקציות עזר (get_counts, slice_bits, slice_shots, expectation_values, ומסכות post-selection). Executor מחזיר מערכי NumPy גולמיים כך שתוכל לבצע עיבוד לאחר-ריצה זה באמצעות פעולות NumPy סטנדרטיות.

שלב 8. טיפול בתוצאות twirled (תיקוני היפוך ביט)

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

בעת שימוש ב-Executor, בטל את ה-twirling במפורש באמצעות תיקוני measurement_flips.<reg> ו-XOR, כפי שמוצג בדוגמה הבאה:

# SamplexItem result shape: (randomizations, parameter_sets, shots, register_bits)
result_1 = result[1]["meas"] # example: (28, 10, 1024, 2)

# Bit-flip corrections to undo measurement twirling
flips_1 = result[1]["measurement_flips.meas"] # example: (28, 10, 1, 2)

# Undo the twirling through classical XOR (broadcasts over the shots axis)
unflipped_result_1 = result_1 ^ flips_1

אין שלב מקביל ב-Sampler מכיוון שהוא מבטל את ה-twirling עבורך.

דוגמה מלאה: העברת עבודת דגימה בסיסית

Sampler

import numpy as np
from qiskit.circuit import QuantumCircuit
from qiskit.transpiler import generate_preset_pass_manager
from qiskit_ibm_runtime import QiskitRuntimeService, SamplerV2 as Sampler

# 1. Account + backend
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False)

# 2. Circuit
circuit = QuantumCircuit(2)
circuit.h(0)
circuit.h(1)
circuit.cz(0, 1)
circuit.h(1)
circuit.measure_all()

# 3. Transpile to ISA
pm = generate_preset_pass_manager(optimization_level=1, backend=backend)
isa_circuit = pm.run(circuit)

# 4. Run — shots are passed to run()
sampler = Sampler(mode=backend)
job = sampler.run([(isa_circuit,)], shots=25)
result = job.result()

# 5. Access results: a BitArray keyed by register name
counts = result[0].data.meas.get_counts()

Executor

import numpy as np
from qiskit.circuit import QuantumCircuit
from qiskit.transpiler import generate_preset_pass_manager
from qiskit_ibm_runtime import QiskitRuntimeService, Executor
from qiskit_ibm_runtime.quantum_program import QuantumProgram

# 1. Account + backend (unchanged)
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False)

# 2. Circuit (unchanged)
circuit = QuantumCircuit(2)
circuit.h(0)
circuit.h(1)
circuit.cz(0, 1)
circuit.h(1)
circuit.measure_all()

# 3. Transpile to ISA (unchanged)
pm = generate_preset_pass_manager(optimization_level=1, backend=backend)
isa_circuit = pm.run(circuit)

# 4. Build a QuantumProgram — shots are on the program
program = QuantumProgram(shots=25)
program.append_circuit_item(isa_circuit)

# 5. Run
executor = Executor(mode=backend)
job = executor.run(program)
result = job.result()

# 6. Access results: a plain np.ndarray keyed by register name
# shape = (shots, register_bits)
meas = result[0]["meas"]

השלבים הבאים