Перехід від Sampler до Executor
Цей посібник описує, як перенести робочі навантаження квантової вибірки з примітиву IBM Quantum® Sampler на примітив Executor.
Примітив Executor є частиною моделі спрямованого виконання. Усі компоненти в моделі спрямованого виконання наразі перебувають у бета-версії і можуть бути нестабільними. Запрошуємо тебе протестувати їх і надати відгук, відкривши issue в репозиторіях GitHub Samplomatic або qiskit-ibm-runtime.
Чи варто тобі переходити?
Не всім варто переходити з Sampler на Executor. Між цими примітивами багато відмінностей, але наступні поради можуть допомогти тобі вирішити, чи варто переходити:
Переходь на Executor, якщо ти науковець з квантової інформації, який виконує експерименти промислового масштабу і потребує детального, відтворюваного контролю над такими техніками, як Pauli twirling, навчання й впровадження моделі шуму та зміни базису — або кому потрібна одна з додаткових можливостей, які надає Executor.
Продовжуй використовувати Sampler, якщо тобі потрібен простий інтерфейс високого рівня і ти хочеш, щоб примітив сам керував придушенням і пом'якшенням помилок за тебе.
Обмеження та застереження
Оскільки Executor і модель спрямованого виконання перебувають у бета-версії, зверни увагу на таке перед тим, як вирішити перейти:
-
Ще немає підтримки симулятора: На відміну від Sampler, який має реалізацію
AerSamplerуqiskit-aerдля локальної симуляції, наразі немає бекенду симулятора для Executor. Очікується, що підтримка симулятора з'явиться найближчим часом. Тим часом ти все ще можеш перевіряти і семплувати шаблонну схему локально, щоб перевірити свій робочий процес перед відправкою на апаратне забезпечення. -
Цей посібник охоплює лише Sampler, а не Estimator. Перехід з Estimator на Executor значно складніший, ніж перехід з Sampler, оскільки Estimator обчислює очікувані значення, а не повертає сирі семпли. Відтворення поведінки Estimator за допомогою Executor вимагає додаткової постобробки. Утилітарні функції для допомоги з переходом з Estimator на Executor все ще розробляються, тому цей посібник навмисно описує лише робочий процес Sampler.
Ключові відмінності між Executor і Sampler
Sampler і Executor обидва семплують вихідні регістри квантових схем, але вони орієнтовані на різних користувачів:
-
Sampler — це абстракція високого рівня. Вона має такі характеристики:
-
Вона має вбудоване придушення помилок (динамічне розчеплення і twirling).
-
Вона приймає неявні рішення за тебе.
-
Вона розроблена так, щоб розробники алгоритмів могли зосередитися на інноваціях, а не на перетворенні даних.
-
-
Executor є частиною моделі спрямованого виконання. Він відрізняється від Sampler у багатьох аспектах і має такі характеристики:
-
Він не має вбудованого придушення чи пом'якшення помилок. Натомість ти фіксуєш свій намір проєктування на стороні клієнта (за допомогою анотацій схеми та семплекса), а витратне генерування варіантів схеми переноситься на сторону сервера.
-
Він не приймає неявних рішень. Він точно слідує твоїм директивам, надаючи повний контроль і прозорість.
-
Executor і Samplomatic разом надають додаткові можливості, яких немає у Sampler, зокрема (але не обмежуючись):
- Більше груп twirling: Samplomatic дозволяє тобі вибирати, яку групу twirling застосувати
для кожного box, замість обмеження єдиною стратегією, яку Sampler застосовує за тебе. Він також підтримує групи twirling, відмінні від Pauli, наприклад групу twirling
"local_c1". - Кернельні та класифіковані вимірювання разом: Встановлення
QuantumProgram.meas_level = "both"(додано вqiskit-ibm-runtimev0.48.0) вимагає, щоб у результатах були присутні як класифіковані, так і кернельні вимірювання, замість вибору одного типу вимірювання на завдання. - Twirling для схем з дробовими вентилями: Executor може застосовувати twirling до схем, які містять дробові вентилі.
- Детальне, композиційне пом'якшення помилок: Наприклад, вибір, які шари схеми пом'якшувати, і налаштування рівнів шуму, що впроваджуються в схему.
Примітки- Очікується, що майбутні нові можливості будуть випущені для Executor першими і можуть не бути перенесені в Sampler. Якщо тобі важливий доступ до найновіших функцій, Executor — більш перспективний вибір.
- Базовий пакет Qiskit ще не надає
базового класу для примітиву Executor (на відміну від
SamplerV2).
- Більше груп twirling: Samplomatic дозволяє тобі вибирати, яку групу twirling застосувати
для кожного box, замість обмеження єдиною стратегією, яку Sampler застосовує за тебе. Він також підтримує групи twirling, відмінні від Pauli, наприклад групу twirling
-
Концептуальне зіставлення
Наступна таблиця демонструє, як концепції Sampler зіставляються з Executor.
| Концепція | Sampler | Executor |
|---|---|---|
| Імпорт | from qiskit_ibm_runtime import SamplerV2 | from qiskit_ibm_runtime import Executor |
| Вхідні дані | Список PUB (кортежів) | QuantumProgram з об'єктів QuantumProgramItem |
| Схема та параметри | Кортеж (circuit, params, shots) | program.append_circuit_item(circuit, circuit_arguments=...) |
| Twirling | TwirlingOptions | Явно через анотовані box і семплекс (append_samplex_item) |
| Виклик запуску | sampler.run([pub, ...]) | executor.run(program) |
| Тип результату | PrimitiveResult з SamplerPubResult | QuantumProgramResult (ітерований) |
| Доступ до даних | result[0].data.<register> (BitArray) | result[0]["<register>"] (np.ndarray) |
| Керування шумом | Вбудовані параметри | Потрібно компонувати вручну (анотації, семплекс, NoiseLearnerV3) |
Огляд кроків міграції
Крок 1. Встанови необхідні пакети
Executor і модель спрямованого виконання вимагають пакет samplomatic:
pip install qiskit qiskit-ibm-runtime samplomatic
# For visualization support:
# pip install samplomatic[vis]
- Рекомендується
qiskit-ibm-runtimev0.48.0, оскільки він додає параметрmeas_level = "both"і групу twirlinglocal_c1. - Потрібен
qiskit >= 2.3.0. - Потрібен
samplomatic >= 0.18.0.
Крок 2. Зміни імпорти
Sampler:
from qiskit_ibm_runtime import SamplerV2 as Sampler
Executor:
from qiskit_ibm_runtime import Executor, QuantumProgram
Крок 3. Заміни кортежі PUB на QuantumProgram
Замість передачі списку кортежів (PUB), під час використання Executor ти будуєш QuantumProgram і додаєш до нього елементи.
QuantumProgram приймає елементи типу circuit і samplex:
-
append_circuit_item: ДодаєCircuitItem, який є схемою та (опційно) її значеннями параметрів. Він виконується як є, без будь-якої рандомізації.Використовуй це, коли хочеш просто семплувати схему точно так, як це робив би Sampler з PUB без twirling; наприклад, при відправці простого завдання вибірки, або коли ти вже вручну включив будь-які варіанти, які хотів.
-
append_samplex_item: ДодаєsamplexItem, який є шаблонною схемою плюс семплекс, що генерує рандомізовані набори параметрів на стороні сервера.Використовуй це, коли хочеш, щоб вміст схеми був рандомізований. Основний випадок — це twirling (вентилів або вимірювання) чи впровадження шуму. Ця можливість замінює вбудований twirling Sampler.
Один QuantumProgram може приймати обидва типи елементів; кожен доданий елемент виконується як
незалежне завдання і створює власний запис у результатах. Загалом, використовуй append_circuit_item, коли твоя схема не потребує рандомізації. В іншому випадку використовуй append_samplex_item.
Наступні розділи показують кожен з них по черзі: параметризовані схеми, що використовують
append_circuit_item, і перехід twirling за допомогою append_samplex_item.
У наступних прикладах коду isa_circuit позначає схему, транспільовану відповідно до архітектури набору інструкцій (ISA) цільового бекенду. Ця isa_circuit містить два параметри.
Крок 3a. Перехід параметризованих схем
У 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"]
Крок 3b. Перехід вбудованого twirling до явних анотацій
Це найзначніша зміна. Sampler застосовує twirling за тебе за допомогою параметрів. З Executor ти явно оголошуєш цей намір за допомогою анотованих box і семплекса (з Samplomatic).
Sampler (twirling за допомогою параметрів):
sampler = Sampler(mode=backend)
sampler.options.twirling.enable_gates = True
sampler.options.twirling.enable_measure = True
Executor (twirling за допомогою box та семплекса):
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.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. Дивись посібник Samplomatic
Samplex inputs and outputs
для повного покрокового опису.
Крок 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, оскільки вибір щодо пом'якшення помилок тепер знаходиться в твоїх анотаціях і семплексі, а не в параметрах.
Також є структурна відмінність у тому, де зберігаються налаштування.
-
У Sampler усе, включно з вибором, що впливає на постобробку результатів, налаштовується в параметрах примітиву або в PUB.
-
В Executor вибір, що впливає на те, як формуються та постобробляються результати завдання, встановлюється в
QuantumProgram, а не вExecutorOptions.
Examples:
| Sampler | Executor |
|---|---|
shots | QuantumProgram(shots=...) |
meas_type | QuantumProgram(meas_level=...) |
ExecutorOptions містить лише налаштування виконання й середовища нижчого рівня, які не змінюють структуру повернутих даних. Він має три групи верхнього рівня:
-
environment(EnvironmentOptions) -
execution(ExecutionOptions): Містить менше параметрів, ніж у Sampler. Наприклад, немає параметраmeas_typeдля Executor.
Варто зазначити, що параметри 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 є програма, а не PUB.
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.
| Завдання | Sampler | Виконавець |
|---|---|---|
| Отримати дані регістру | result[0].data.meas | result[0]["meas"] |
| Тип даних | BitArray | np.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 та маски пост-селекції). Executor повертає необроблені масиви NumPy, тож ти можеш виконати цю постобробку за допомогою стандартних операцій NumPy.
Крок 8. Обробка результатів твірлінгу (виправлення бітових інверсій)
Коли ти застосовуєш твірлінг вимірювань через SamplexItem, Executor повертає сирі
(твірловані) вимірювання разом із виправленнями бітових інверсій, необхідними для скасування твірлінгу.
Тобі потрібно застосувати їх вручну; нічого не виправляється неявно.
Під час використання Executor скасовуй твірлінг явно за допомогою виправлень 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 немає еквівалентного кроку, оскільки він скасовує твірлінг за тебе.
Повний приклад: міграція базового завдання семплування
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"]