Перейти до основного вмісту
Цей проєкт у бета-версії. Знайшов(-ла) помилку? Відкрити issue на GitHub. Є запитання чи ідея? Запитати в Discussions — будемо раді твоєму відгуку!
Яка якість перекладу на цій сторінці?

Входи та виходи Executor

Версії пакетів

Код на цій сторінці було розроблено з використанням таких вимог. Рекомендуємо використовувати ці або новіші версії.

qiskit[all]~=2.6.1
qiskit-ibm-runtime~=0.50.0
samplomatic~=0.21.0

Примітив Executor є частиною моделі спрямованого виконання, що надає більшу гнучкість при налаштуванні робочого процесу пом'якшення помилок.

Входи та виходи примітива Executor значно відрізняються від входів та виходів примітивів Sampler та Estimator. Наприклад, замість прийняття списку PUB як вхідних даних, Executor приймає QuantumProgram, що містить список об'єктів QuantumProgramItem. Ці контейнерні класи надають більшу гнучкість, ніж PUB, який є простою структурою даних у вигляді кортежу.

Вихід Executor — це QuantumProgramResult, який є ітерованим та містить один елемент для кожного вхідного QuantumProgramItem.

Входи: Квантові програми​

Як зазначалося раніше, вхідними даними для примітива Executor є QuantumProgram, що є ітерованим від об'єктів QuantumProgramItem. Ці об'єкти можуть бути двох типів:

  • CircuitItem, який зазвичай зберігає схему та її значення параметрів (якщо є).
  • SamplexItem, який зазвичай зберігає наступне:
    • Шаблонну схему
    • Об'єкт samplex, що використовується для генерації рандомізованих наборів параметрів під час виконання (наприклад, для виконання twirling або введення шуму)
    • Аргументи для samplex, які можуть включати значення параметрів для оригінальної схеми

Кожен із цих елементів — це окреме завдання для Executor.

Перш ніж почати​

Деякі приклади коду на цій сторінці використовують samplex, що є частиною пакету Samplomatic. Тому перед запуском цих блоків коду необхідно встановити Samplomatic, як показано в наступному блоці коду. Докладніше дивись у документації Samplomatic.

pip install samplomatic

# For visualization support, include the visualization dependencies.
# pip install samplomatic[vis]

Приклад: Створення QuantumProgram з двома різними завданнями​

Спочатку ініціалізуй квантову програму, потім додай елементи програми за допомогою append_circuit_item або append_samplex_item (якщо присутній samplex), як показано в наступних прикладах.

Наступна клітинка ініціалізує QuantumProgram та вказує, що програма повинна виконувати 1024 запуски для кожної конфігурації кожного елемента програми.

примітка

На відміну від Sampler, QuantumProgram приймає лише одне значення кількості запусків. Якщо потрібне інше значення, знадобиться окремий QuantumProgram, що буде окремим завданням.

# Added by doQumentation — installs the packages this notebook needs if they are missing
import importlib.util

_needed = {"numpy": "numpy", "qiskit": "qiskit", "qiskit_ibm_runtime": "qiskit-ibm-runtime", "samplomatic": "samplomatic"}
_missing = [pip for module, pip in _needed.items()
if importlib.util.find_spec(module) is None]
# One at a time, so a package that fails to install does not block the others
for _pip in _missing:
%pip install -q {_pip}
if not _missing:
print("\u2713 All required packages are installed")
from qiskit.transpiler import generate_preset_pass_manager
from qiskit_ibm_runtime.quantum_program import QuantumProgram
from qiskit_ibm_runtime import Executor, QiskitRuntimeService
from qiskit.circuit import Parameter, QuantumCircuit
import numpy as np
from samplomatic import build
from samplomatic.transpiler import generate_boxing_pass_manager

# Initialize an empty program
program = QuantumProgram(shots=1024)

# Initialize and transpile a 3-qubit quantum circuit with 2 parameters.
circuit = QuantumCircuit(3)
circuit.h(0)
circuit.cx(0, 1)
circuit.cx(1, 2)
circuit.rz(Parameter("theta"), 0)
circuit.rz(Parameter("phi"), 1)

# `measure_all` adds a 3-bit classical register named "meas"
circuit.measure_all()

# Choose the least busy backend
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False)

# Generate a preset pass manager
# This will be used to convert the abstract circuit to an
# equivalent Instruction Set Architecture (ISA) circuit.
preset_pass_manager = generate_preset_pass_manager(
backend=backend, optimization_level=0
)

# Transpile the circuit
isa_circuit = preset_pass_manager.run(circuit)

Додавання CircuitItem​

Далі додай цільову схему, транспільовану відповідно до архітектури набору інструкцій (ISA) бекенда, до QuantumProgram. Оскільки ця схема має два параметри, потрібно також надати значення параметрів (10 наборів у цьому прикладі). Виконання цього CircuitItem є першим завданням, яке виконає програма.

# Append the transpiled circuit and an array
# containing 10 sets of parameter values to the program
program.append_circuit_item(
isa_circuit,
circuit_arguments=np.random.rand(
10, 2
), # 10 sets of parameter values and 2 parameters
)

Додавання SamplexItem​

Елементи схем виконуються без будь-якої рандомізації. На противагу, елементи samplex дозволяють вказати, як рандомізувати їхній вміст. Наступна клітинка використовує функцію generate_boxing_pass_manager() для групування вентилів та вимірювань схеми у блоки та додавання анотації twirling до кожного блоку. Потім вона генерує пару шаблонна схема та samplex за допомогою функції build().

Виконання цього SamplexItem є другим завданням, яке виконає програма.

Дивись документацію API Samplomatic для повних деталей про samplex та його аргументи. Дивись Керівництво з транспілятора Samplomatic для інформації про використання функції generate_boxing_pass_manager().

# Transpile the circuit, additionally grouping gates and measurements into annotated boxes
preset_pass_manager = generate_preset_pass_manager(
backend=backend, optimization_level=0
)

# Use the boxing pass manager to group gates
# and measurements into boxes and add
# a`Twirl` annotation.
preset_pass_manager.post_scheduling = generate_boxing_pass_manager(
# Add gate twirling
enable_gates=True,
# Add measurement twirling
enable_measures=True,
)
boxed_circuit = preset_pass_manager.run(circuit)

# Build the template circuit and the samplex. The template circuit has parametric gates
# without fixed values and the samplex randomly generates the parameter
# values on the server side at runtime to perform twirling.
template_circuit, samplex = build(boxed_circuit)

# Determine what arguments are required by the samplex.
# Input the arguments in samplex_arguments.
print(samplex.inputs())
TensorInterface(<
- 'parameter_values' <float64[2]>: Input parameter values to use during sampling.
>)
# Append the template circuit and samplex as a samplex item
program.append_samplex_item(
template_circuit,
samplex=samplex,
samplex_arguments={
# the arguments required by the samplex.sample method
"parameter_values": np.random.rand(10, 2),
},
shape=(28, 10), # 28 randomizations and 10 sets of parameter values
)
# Initialize an Executor with the default options
executor = Executor(mode=backend)

# Submit the job
job = executor.run(program)

# Retrieve the result
result = job.result()

Виходи​

Вихід Executor — це QuantumProgramResult, який є ітерованим. Він містить один запис для кожного вхідного QuantumProgramItem у тому ж порядку, що й вхідні елементи. Кожен із цих вихідних елементів є словником, де ключі — це рядки, що відповідають назвам класичних регістрів у вхідних схемах (серед інших), тому більше не потрібно запам'ятовувати ці назви, як це робилося з виходом Sampler. Значення словника мають тип np.ndarray.

Результат попереднього прикладу містить такі елементи:

Результат CircuitItem​

Перший елемент містить результати виконання першого завдання (CircuitItem) у програмі. Він містить один ключ, meas, — назву класичного регістра у вхідній схемі. Значення цього ключа відповідає np.ndarray форми (parameter sets, shots, register bits), тобто (10, 1024, 3) для наведеного вище прикладу.

Наступний код ілюструє, як отримати доступ до цієї інформації:

# Access the results of the classical register of task #0, a CircuitItem
result_0 = result[0]["meas"]
print(f"Result shape: {result_0.shape}")
Result shape: (10, 1024, 3)

Крайня права вісь result_0 пробігає класичні біти в порядку little-endian. Отже, індекс 0 містить результат класичного біта 0, наймолодшого біта.

Результат SamplexItem​

Другий елемент містить результати виконання другого завдання (SamplexItem) у програмі. Цей елемент містить кілька ключів. Ключ meas, що є назвою класичного регістра вхідної схеми, відповідає масиву результатів цього регістра. Цей масив має форму (randomizations, parameter sets, shots, classical bits), або (28, 10, 1024, 3) у цьому прикладі.

Додатково вихід містить ключ measurement_flips.meas — бітові корекції для скасування twirling вимірювань для регістра meas. Форма цього виходу буде (28, 10, 1, 3) для нашого прикладу, бо для бітової інверсії потрібен лише один запуск.

Зауваж, що крайня права вісь result_1 пробігає класичні біти в порядку little-endian.

# Access the results of the classical register of task #1
result_1 = result[1]["meas"]
print(f"Result shape: {result_1.shape}")

# Access the bit-flip corrections
flips_1 = result[1]["measurement_flips.meas"]
print(f"Bit-flip corrections shape: {flips_1.shape}")

# Undo the bit flips via classical XOR
unflipped_result_1 = result_1 ^ flips_1
Result shape: (28, 10, 1024, 3)
Bit-flip corrections shape: (28, 10, 1, 3)

Переглянь проміжки виконання​

Один виклик Executor.run() надсилає одне завдання. Однак на стороні сервера сервіс не обов'язково виконує всі конфігурації квантової програми одночасно. Натомість він групує прив'язані кола елементів програми в порції і виконує по одній порції на QPU за раз. Тобі не потрібно розбивати свою програму чи надсилати додаткові завдання; поділ на порції відбувається автоматично в межах одного завдання. Атрибут metadata результату містить інформацію про час цих порцій у result.metadata.chunk_timing. Ці часові мітки — це фактичний час початку й завершення кожної порції на QPU. Ти можеш використовувати цю інформацію, щоб побачити, коли певні дані були зібрані на QPU, зрозуміти, як Executor розподілив роботу, або діагностувати проблеми з продуктивністю.

chunk_timing — це список, що містить один ChunkSpan на кожну виконану порцію. Кожен ChunkSpan має такі атрибути:

  • start і stop: часові мітки UTC, коли порція почала й завершила виконання на QPU.

  • parts: список об'єктів ChunkPart, які описують, які елементи програми внесли внесок у порцію. Кожен ChunkPart містить таке:

    • idx_item: індекс елемента програми в порядку, у якому елементи були додані до QuantumProgram.

    • size: кількість конфігурацій (елементів форми елемента) з цього елемента, що були виконані в цій порції. Наприклад, SamplexItem у попередньому прикладі має форму (28, 10), тож усього має 280 конфігурацій.

Наведений нижче код виводить тривалість кожної порції та вміст кожної порції для завдання, надісланого раніше:

for idx, span in enumerate(result.metadata.chunk_timing):
duration = (span.stop - span.start).total_seconds()
print(f"Chunk {idx}: {span.start} to {span.stop} ({duration:.3f} s)")
for part in span.parts:
print(f" Item {part.idx_item}: {part.size} configurations")
Chunk 0: 2026-11-06 01:47:46.009718+00:00 to 2026-11-06 01:49:05.577720+00:00 (79.568 s)
Item 0: 10 configurations
Item 1: 280 configurations

Властивість QuantumProgramResult.timing обгортає той самий список в об'єкт ChunkTiming, який також повідомляє глобальну інформацію про сукупність порцій:

timing = result.timing

print("Number of chunks:", len(timing))
print(" Start of the first chunk:", timing.start)
print(" End of the last chunk:", timing.stop)
print(" Total duration (s):", timing.duration)
Number of chunks: 1
Start of the first chunk: 2026-11-06 01:47:46.009718+00:00
End of the last chunk: 2026-11-06 01:49:05.577720+00:00
Total duration (s): 79.568002

Візуалізуй проміжки виконання​

Використовуй функцію draw_chunk_timings, щоб візуалізувати час порцій на інтерактивній стовпчастій діаграмі. Вісь x — це час, а кожна порція малюється як горизонтальний відрізок, що охоплює її час початку й завершення. Вісь y показує кумулятивну кількість завершених конфігурацій (або відсоток від загального навантаження, якщо ти задаєш normalize_y=True). Наведи курсор на відрізок, щоб побачити часові мітки, тривалість і частини цієї порції.

Функція повертає фігуру plotly, яку можна відобразити безпосередньо, зберегти у файл або зробити і те, і інше. Вона приймає або список chunk_timing, або об'єкт ChunkTiming. Ти можеш передати кілька з них, наприклад із різних завдань, щоб намалювати їх на одному графіку. Задай common_start=True, щоб вирівняти їх за спільним часом початку для порівняння.

Потрібен Plotly

draw_chunk_timings потребує plotly. Ти можеш встановити його, виконавши pip install plotly.

Проміжки між порціями

Візуалізація може показувати проміжки між виконанням порцій. Усі завдання, надіслані на справжній QPU, проходять певну класичну попередню обробку, як-от переведення в імпульси, які можна надіслати на обладнання. Оскільки цю попередню обробку можна виконувати паралельно, сервіс виймає з черги й запускає кілька завдань одночасно. Проте QPU може обробляти лише одну одиницю роботи за раз. Тому, якщо завдання генерує кілька порцій, його порції можуть чергуватися з порціями завдань інших користувачів, і кожній порції, можливо, доведеться чекати своєї черги на QPU. Ось чому між порціями можуть з'являтися проміжки.

from qiskit_ibm_runtime.visualization import draw_chunk_timings

fig = draw_chunk_timings(result.timing, names="Executor job")

# Alternatively, call the `draw()` method of the ChunkTiming object
# fig = result.timing.draw()

# Uncomment the following line to display the figure
# fig.show(renderer="notebook")

# Save to a file
# fig.write_html("chunk_timings.html")

Наступні кроки​

Рекомендації