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

Транспайлер

Функція generate_boxing_pass_manager() — гнучкий і зручний інструмент для побудови qiskit.transpiler.PassManager, який може групувати інструкції схеми в анотовані бокси. Це дає просту альтернативу ручному групуванню та анотуванню схем і корисне для автоматизації робочого процесу, тестування різних стратегій розбиття схеми на бокси тощо.

Цей посібник ілюструє, як використовувати generate_boxing_pass_manager() та його аргументи. Щоб підкреслити ефекти кожного з аргументів функції, наступні розділи здебільшого посилаються на схему нижче.

from qiskit.circuit import Parameter, QuantumCircuit

circuit = QuantumCircuit(4, 7)
circuit.h(1)
circuit.h(2)
circuit.cz(1, 2)
circuit.h(1)
circuit.cx(1, 0)
circuit.cx(2, 3)
circuit.measure(range(1, 4), range(3))
circuit.cx(0, 1)
circuit.cx(1, 2)
circuit.cx(2, 3)
for qubit in range(4):
circuit.rz(Parameter(f"th_{qubit}"), qubit)
circuit.rx(Parameter(f"phi_{qubit}"), qubit)
circuit.rz(Parameter(f"lam_{qubit}"), qubit)
circuit.measure(range(4), range(3, 7))

circuit.draw("mpl", scale=0.8)

../_images/cf27e1ef03e51b2360c90165a4efeaa793da36083bb674774ca06c8181ef63dd.png

Групування операцій у бокси​

Аргумент enable_gates можна встановити в True або False, щоб указати, чи слід групувати двокубітні вентилі в бокси. Так само enable_measures указує, чи слід групувати вимірювання. Наступний фрагмент показує приклад, де групуються і вентилі, і вимірювання.

from samplomatic.transpiler import generate_boxing_pass_manager

boxing_pass_manager = generate_boxing_pass_manager(
enable_gates=True,
enable_measures=True,
)
transpiled_circuit = boxing_pass_manager.run(circuit)
transpiled_circuit.draw("mpl", scale=0.8)

../_images/b323dde8a5412e42359aef1831af92ed3702d2251547c663208dc084a2995276.png

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

Наступний код показує приклад, де enable_gates встановлено в False. Як видно, двокубітні вентилі не групуються в бокси, так само як і однокубітні вентилі, що передують їм.

boxing_pass_manager = generate_boxing_pass_manager(
enable_gates=False,
enable_measures=True,
)
transpiled_circuit = boxing_pass_manager.run(circuit)
transpiled_circuit.draw("mpl", scale=0.8)

../_images/3fc8e660b0c7eff1f5aa2e571d064f91b3eb2738c5990727e026a8ffe1101a1c.png

Вибір способу анотування боксів​

Усі бокси з двокубітними вентилями та вимірюваннями в поверненій схемі мають анотації left-dressed. Зокрема, усі бокси, що містять двокубітні вентилі, анотовано Twirl. Для боксів вимірювань користувачі можуть обирати між Twirl, BasisTranform (з mode, встановленим у "measure") або обома. Наступний код генерує схему, де для всіх боксів виконується twirl, а бокси вимірювань додатково анотовано BasisTranform.

boxing_pass_manager = generate_boxing_pass_manager(
enable_gates=True,
enable_measures=True,
measure_annotations="all",
)
transpiled_circuit = boxing_pass_manager.run(circuit)

Підготовка схеми до ін'єкції шуму​

inject_noise_targets дозволяє вказати, які бокси мають отримати анотацію InjectNoise. Наприклад, наступний код генерує схему, де бокси з двокубітними вентилями мають анотацію InjectNoise, а бокси вимірювань — ні.

boxing_pass_manager = generate_boxing_pass_manager(
enable_gates=True,
enable_measures=True,
inject_noise_targets="gates",
)
transpiled_circuit = boxing_pass_manager.run(circuit)

Якщо схема містить два або більше боксів, еквівалентних з точністю до однокубітних вентилів, усі вони анотуються InjectNoise з однаковим ref. Отже, кількість унікальних ref у поверненій схемі дорівнює кількості унікальних боксів.

Значення inject_noise_strategy впливає на анотації InjectNoise. Можливі такі значення:

  • modifier_ref='': Рекомендовано, коли не потрібно змінювати мапи шуму перед вибіркою з них.

  • modifier_ref=ref: Рекомендовано, коли всі мапи шуму потрібно рівномірно масштабувати на один і той самий множник.

  • Унікальне значення modifier_ref: Рекомендовано, коли кожну мапу шуму потрібно масштабувати на різний множник.

Наступний код генерує схему, де бокси з двокубітними вентилями мають анотацію InjectNoise з унікальними значеннями modifier_ref.

boxing_pass_manager = generate_boxing_pass_manager(
enable_gates=True,
enable_measures=True,
inject_noise_targets="gates",
inject_noise_strategy="individual_modification",
)
transpiled_circuit = boxing_pass_manager.run(circuit)

Вибір стратегії twirling​

Boxing pass manager починає з вибору того, які вентилі заплутування та інструкції вимірювання мають з'явитися в одних і тих самих боксах. Після цього бокси, як правило, не матимуть повної ширини — вони, імовірно, включатимуть лише підмножину кубітів, активних у схемі або присутніх у цілі бекенда. У типових конфігураціях boxing pass manager, оскільки twirl виконується для всіх кубітів, на які діє інструкція бокса (навіть тих, на які не діє жодна інструкція всередині бокса), часткова ширина боксів впливає на ефекти twirling щодо підлаштування шуму. Кубіти, що простоюють, не twirl-яться і можуть, наприклад, накопичувати когерентні помилки, поки бокс застосовується. twirling_strategy надає варіанти розширення кубітів, якими володіє кожен бокс, змінюючи таким чином кількість кубітів, що простоюють:

  • "active" (за замовчуванням) не розширює бокси на кубіти, що простоюють.

  • "active_accum" розширює всі бокси на ті кубіти, на які вже діяла попередня інструкція в схемі. Це корисно, якщо ти хочеш якомога довше залишати кубіти на початку схеми в основному стані, не виконуючи їхній twirl, доки вони не стануть активними.

  • "active_circuit" розширює всі бокси на ті кубіти, на які діє будь-яка інструкція в схемі. Це корисно, якщо у тебе структурована схема з повторюваними шарами заплутування і ти хочеш мінімізувати кількість унікальних боксів, бо шум кожного з них потрібно навчати.

  • "all" розширює всі бокси на всі кубіти в усіх квантових регістрах схеми. Це корисно як розширення "active_circuit", коли ти хочеш, щоб twirl завжди виконувався для всіх кубітів у QPU.

У наступному прикладі кожна зі стратегій застосовується до базової схеми, після чого результати малюються.

circuit = QuantumCircuit(5, 2)
circuit.h(range(4))
circuit.cx(0, 1)
circuit.cx(1, 2)
circuit.cx(2, 3)
circuit.cx(0, 1)
circuit.cx(1, 2)
circuit.cx(2, 3)
circuit.barrier()
circuit.h(range(4))
circuit.measure([1, 2], [0, 1])

circuit.draw("mpl", scale=0.6)

../_images/cdf089891dabe1650f1303c8c747aa8cf03cdde4dfc42c772fe2264bd7a49a1b.png

import matplotlib.pyplot as plt

plt.figure(figsize=(9, 6))
for idx, strategy in enumerate(["active", "active_accum", "active_circuit", "all"]):
ax = plt.subplot(2, 2, idx + 1)
pm = generate_boxing_pass_manager(twirling_strategy=strategy, remove_barriers="never")
transpiled_circuit = pm.run(circuit)
fig = transpiled_circuit.draw("mpl", scale=0.8, ax=ax)
ax.set_title(f"twirling_strategy='{strategy}'")

plt.tight_layout()

../_images/b810ecc4c65822cf60b3d652792a4089c5f931ad1e8320eccdd89bc50a08ba6d.png

Вказівка, як поводитися з бар'єрами​

За замовчуванням бар'єри видаляються зі схеми після того, як усі вентилі заплутування та інструкції вимірювання було поміщено в бокси згідно з інструкціями, враховуючи їхні розширення через вибір twirling_strategy, але до того, як однокубітні вентилі додаються до бокса безпосередньо праворуч від них, якщо це можливо.

Наприклад, наступна схема містить бар'єр:

circuit_with_barrier = QuantumCircuit(4)
circuit_with_barrier.h(range(4))
circuit_with_barrier.cz(0, 1)
circuit_with_barrier.barrier()
circuit_with_barrier.cz(2, 3)
circuit_with_barrier.measure_all()

circuit_with_barrier.draw("mpl", scale=0.8)

../_images/f2343807597155ab5301205803954b95d29ba0d7370440bd861439fd83d1a193.png

Поведінка за замовчуванням не дозволяє двом вентилям заплутування опинитися в одному боксі, хоча дозволяє однокубітним вентилям поглинутися наступним доступним боксом попри бар'єр.

boxing_pass_manager = generate_boxing_pass_manager()
transpiled_circuit = boxing_pass_manager.run(circuit_with_barrier)
transpiled_circuit.draw("mpl", scale=0.8)

../_images/7e0812ac6b3f2405c765338ab25c2e9179165a7257ef1d996bea99f94cb39ca6.png

Встановлення remove_barriers у 'never' зберігає бар'єри, тож однокубітні вентилі обмежені.

boxing_pass_manager = generate_boxing_pass_manager(remove_barriers="never")

transpiled_circuit = boxing_pass_manager.run(circuit_with_barrier)
transpiled_circuit.draw("mpl", scale=0.8)

../_images/6b57e625509dd8ca55fbd96994398b03ac2fe6f76f21535db9bd042d4c68c987.png

Нарешті, якщо бар'єри видаляються негайно, вентилі заплутування більше не обмежені в тому, щоб опинитися в окремих боксах.

boxing_pass_manager = generate_boxing_pass_manager(remove_barriers="immediately")

transpiled_circuit = boxing_pass_manager.run(circuit_with_barrier)
transpiled_circuit.draw("mpl", scale=0.8)

../_images/9a44c59a05a44a3e8f0ce9b7edd3ccc2896c6a68fb00ed110a7e225f52a7ca73.png

Як працює стратифікація​

Boxing pass manager послідовно виконує два ключові проходи:

  1. GroupGatesIntoBoxes — Стратифікує вентилі заплутування в бокси. Однокубітні вентилі на цьому етапі ігноруються; групуються лише двокубітні вентилі.

  2. AbsorbSingleQubitGates — Поглинає кожен однокубітний вентиль найближчим придатним боксом праворуч від нього.

Під час стратифікації GroupGatesIntoBoxes обходить DAG схеми в топологічному порядку і розміщує кожен двокубітний вентиль у найранішій групі, кубіти якої ще не зайняті. Три види інструкцій діють як роздільники, що скидають поточну групу для своїх кубітів, змушуючи наступні вентилі на цих кубітах потрапляти в нову групу:

  • Бар'єри

  • Наявні інструкції box

  • Measurements

Однокубітні вентилі не є роздільниками — вони не впливають на групування і залишаються для обробки AbsorbSingleQubitGates. Наразі аналіз комутативності не виконується; групування дотримується топологічного порядку схеми як є.

Режим за замовчуванням — "after_stratification", тому що він поєднує дві переваги:

  • Бар'єри обмежують спосіб групування вентилів заплутування (вони діють як роздільники під час стратифікації).

  • Бар'єри видаляються до запуску AbsorbSingleQubitGates, тож однокубітні вентилі все ще можуть проходити через колишні позиції бар'єрів і поглинатися найближчим боксом.

Натомість:

  • "never" зберігає бар'єри протягом усього часу, що затискає однокубітні вентилі між бар'єром і боксом, перешкоджаючи їх поглинанню та залишаючи в схемі обертання без twirl.

  • "immediately" видаляє бар'єри до стратифікації, тож вони не впливають на групування.

  • "finally" корисний, коли ти хочеш мати бар'єри у вихідній схемі (наприклад, для візуалізації), але однокубітні вентилі між бар'єром і боксом не поглинаються.

Обгортання ISA-схем у блоки за допомогою бар'єрів​

Схеми з повторюваною структурою — такі як тротеризовані симуляції гамільтоніанів, варіаційні анзаци та послідовності підсилення помилок — зазвичай будують програмно, а потім транспілюють у ISA-форму. Додавати блоки вручну до транспільованої схеми незручно, тому природний робочий процес такий:

  1. Побудуй логічну схему, вставивши бар'єри на межах шарів, які мають стати межами блоків.

  2. Транспілюй у ISA-схеми. Транспілятор Qiskit зберігає бар'єри під час маршрутизації та оптимізації.

  3. Виконай generate_boxing_pass_manager() на ISA-схемі.

Бар'єри потрібні, коли кінець одного шару не охоплює всі кубіти. Інакше жадібний алгоритм помістить вентилі наступного шару — на цих «вільних» кубітах — у ту саму групу. Як конкретний приклад розглянемо дворокову симуляцію Гейзенберга ZZ на кільці з 5 кубітів. Кожен крок Троттера має три шари зв'язків: парні зв'язки (0,1), (2,3), непарні зв'язки (1,2), (3,4) та замикальний зв'язок (4,0). Після шару непарних зв'язків кубіти 0 і 4 обидва вільні. Без бар'єра замикальний зв'язок (4,0) буде жадібно згруповано зі зв'язками наступного кроку Троттера, що використовують ці вільні кубіти, і вентилі з різних кроків опиняться в одному блоці.

from qiskit.circuit import Parameter, QuantumCircuit

n = 5 # 5-qubit ring: bonds (0,1), (1,2), (2,3), (3,4), (4,0)
dt = Parameter("dt")
heisenberg = QuantumCircuit(n)

for step in range(2):
# Layer 1: even bonds (0,1) and (2,3)
for idx in range(0, n - 1, 2):
heisenberg.rzz(dt, idx, idx + 1)
heisenberg.barrier()
# Layer 2: odd bonds (1,2) and (3,4) -- qubits 0 and 4 are now free
for idx in range(1, n - 1, 2):
heisenberg.rzz(dt, idx, idx + 1)
heisenberg.barrier()
# Layer 3: wrap bond (4,0) -- both qubits were free after layer 2
heisenberg.rzz(dt, n - 1, 0)
if step < 1:
heisenberg.barrier() # separates the two Trotter steps

heisenberg.measure_all()

heisenberg.draw("mpl", scale=0.7)

../_images/172fa55c9f02964fef32fc045a50b86971126be728602b3f1e4b2437df2580b7.png

Наведений нижче код транспілює схему в ISA-форму, припускаючи, що базовий пристрій має CX як рідний вентиль заплутування. Зауваж, що бар'єри зберігаються після транспіляції та залишаються видимими у вихідній схемі.

from qiskit.transpiler import generate_preset_pass_manager

preset_pm = generate_preset_pass_manager(
basis_gates=["rz", "sx", "cx"],
coupling_map=[[0, 1], [1, 2], [2, 3], [3, 4], [4, 0]],
optimization_level=1,
)
isa_circuit = preset_pm.run(heisenberg)
isa_circuit.draw("mpl", scale=0.5, fold=200)

../_images/e9a3fbd6ab1a91051c5cd0273d92ecebc6ae8ebbc370c30bc55daee1fd28bab7.png

Далі застосуй менеджер проходів обгортання в блоки до ISA-схеми. Кожен із трьох шарів зв'язків стає окремим блоком на кожному кроці Троттера.

boxing_pm = generate_boxing_pass_manager()
boxed_isa = boxing_pm.run(isa_circuit)
boxed_isa.draw("mpl", scale=0.5, fold=100)

../_images/b59e3c047264b3340d9af0f1ca74ad9f7d4c1705d4a5176e441346db9b678213.png

Додавання менеджера проходів Samplomatic до попередньо налаштованих менеджерів проходів Qiskit​

Наведений нижче код показує, як додати generate_boxing_pass_manager() до попередньо налаштованого менеджера проходів Qiskit. Транспільована схема є ISA-схемою і містить блоки.

from qiskit.transpiler import generate_preset_pass_manager

preset_pass_manager = generate_preset_pass_manager(
basis_gates=["rz", "sx", "cx"],
coupling_map=[[0, 1], [1, 2]],
optimization_level=0,
)
boxing_pass_manager = generate_boxing_pass_manager()

# Run the boxing pass manager after the scheduling stage
preset_pass_manager.post_scheduling = boxing_pass_manager

circuit = QuantumCircuit(3)
circuit.h(0)
circuit.cx(0, 1)
circuit.cx(0, 2)
circuit.measure_all()

transpiled_circuit = preset_pass_manager.run(circuit)
transpiled_circuit.draw("mpl", scale=0.8)

../_images/fe943b034588a5354a87abcf51b8d805c36f4f2b30a73a39c5886bce9e156b04.png

Цей підхід також природно поєднується з робочим процесом ISA-обгортання на основі бар'єрів. Менеджер проходів обгортання виконується як крок після планування, коли бар'єри з початкової логічної схеми вже пережили транспіляцію, тож вони скеровують стратифікацію, як і очікується.

preset_pm = generate_preset_pass_manager(
basis_gates=["rz", "sx", "cx"],
coupling_map=[[0, 1], [1, 2], [2, 3], [3, 4], [4, 0]],
optimization_level=1,
)
preset_pm.post_scheduling = generate_boxing_pass_manager()

boxed_heisenberg = preset_pm.run(heisenberg)
boxed_heisenberg.draw("mpl", scale=0.5, fold=100)

../_images/4905bf1df63cc83fa581ed006a93beb709be504a4f59fc7f663f4efde62da175.png

Перегляд і зміна анотацій​

Після автоматичного обгортання в блоки ти можеш захотіти переглянути анотації на окремих блоках або змінити їх. Наприклад, щоб додати ін'єкцію шуму, видалити анотації або змінити групу твірлінгу. Для цього Samplomatic надає набір службових функцій у samplomatic.utils. Усі вони повертають нову схему, не змінюючи оригінал.

extend_annotations() додає одну або кілька анотацій до кожного блока в схемі.

from samplomatic import InjectNoise, Twirl
from samplomatic.utils import extend_annotations

boxed = generate_boxing_pass_manager().run(circuit_with_barrier)

# Add InjectNoise to every box
with_noise = extend_annotations(boxed, InjectNoise("my_noise_ref"))
with_noise.draw("mpl", scale=0.8)

../_images/7e0812ac6b3f2405c765338ab25c2e9179165a7257ef1d996bea99f94cb39ca6.png

filter_annotations() залишає лише анотації вказаних типів, видаляючи решту. replace_annotations() застосовує функцію до кожної анотації; ця функція повертає список анотацій для підстановки замість неї (поверни [], щоб видалити, список з одного елемента, щоб замінити, або список з кількох елементів, щоб розширити).

from samplomatic.utils import filter_annotations, replace_annotations

# Keep only Twirl annotations, drop everything else
twirl_only = filter_annotations(with_noise, Twirl)

# Replace the twirling group on every Twirl annotation
updated = replace_annotations(
twirl_only, lambda a: [Twirl(group="local_c1")] if isinstance(a, Twirl) else [a]
)

Універсальна функція map_annotations() приймає будь-яку функцію, що відображає список анотацій у новий список, надаючи тобі повний контроль над перетвореннями для кожного блока. strip_annotations() — це зручна обгортка, що видаляє всі анотації з кожного блока.

Для масових змін, як-от додавання InjectNoise до всіх блоків із вентилями, найлаконічніший варіант — параметр inject_noise_targets у generate_boxing_pass_manager(). Ці утиліти корисні для вибіркових правок окремих блоків, яких не охоплюють параметри менеджера проходів.

Побудова схеми​

Кожен менеджер проходів, створений generate_boxing_pass_manager(), повертає схеми, які build() може успішно перетворити на пару шаблон/samplex. Наприклад, наведений нижче код викликає функцію build() на схемі, створеній менеджером проходів обгортання.

from samplomatic import build

boxing_pass_manager = generate_boxing_pass_manager(
enable_gates=True,
enable_measures=True,
)
transpiled_circuit = boxing_pass_manager.run(circuit)

template, samplex = build(transpiled_circuit)

Щоб будь-яку транспільовану схему можна було успішно побудувати, менеджери проходів уміють додавати додаткові блоки, коли це потрібно. Наприклад, розглянемо схему нижче, що закінчується невимірюваним кубітом 0.

circuit_with_unmeasured_qubit = QuantumCircuit(4, 3)
circuit_with_unmeasured_qubit.cz(0, 1)
circuit_with_unmeasured_qubit.cz(2, 3)
for qubit in range(4):
circuit_with_unmeasured_qubit.rz(Parameter(f"th_{qubit}"), qubit)
circuit_with_unmeasured_qubit.rx(Parameter(f"phi_{qubit}"), qubit)
circuit_with_unmeasured_qubit.rz(Parameter(f"lam_{qubit}"), qubit)
circuit_with_unmeasured_qubit.measure(range(1, 4), range(3))

circuit_with_unmeasured_qubit.draw("mpl", scale=0.8)

../_images/0452779799470d2deb4d32ce19f5aa28dfe8055a064e67c0e8e5684389845b0f.png

Обгортання вентилів і вимірювань лівооснащеними блоками призвело б до схеми з неzібраними віртуальними вентилями на кубіті 0, а виклик build() на такій схемі призвів би до помилки. Щоб цього уникнути, менеджери проходів, які повертає generate_boxing_pass_manager(), можуть додавати праворуч-оснащені блоки, що виконують роль збирачів. Наприклад, у наведеному нижче коді кубіт 0 завершується праворуч-оснащеним блоком, який підбирає незібраний віртуальний вентиль. Однокубітні вентилі, що діють на кубіт 0, також поміщено всередину цього блока, щоб мінімізувати глибину отриманої схеми.

boxing_pass_manager = generate_boxing_pass_manager(
enable_gates=True,
enable_measures=True,
)
transpiled_circuit = boxing_pass_manager.run(circuit_with_unmeasured_qubit)
transpiled_circuit.draw("mpl", scale=0.8)

../_images/711ee3c014edf6f70ede5dfa849494286e96870b3bd70a2a89f687d83cc5bfc1.png

В іншому прикладі додається праворуч-оснащений блок, щоб зібрати віртуальні вентилі, які інакше залишилися б незібраними через необгорнуті вимірювання.

boxing_pass_manager = generate_boxing_pass_manager(
enable_gates=True,
enable_measures=False,
)
transpiled_circuit = boxing_pass_manager.run(circuit_with_unmeasured_qubit)
transpiled_circuit.draw("mpl", scale=0.8)

../_images/d59d0c0af3c47f474a964a455533b99e09a65364454ec04ced7581e6afac73ab.png