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

Як інтерпретувати візуалізації затінених світлових конусів

Пакет надає два способи подивитися на затінений світловий конус:

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

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

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

Налаштування​

Використай ту саму тротеризовану схему Ізинга та спостережувану, що й у посібнику швидкого старту.

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

_needed = {"numpy": "numpy", "qiskit": "qiskit", "qiskit_addon_slc": "qiskit-addon-slc", "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")
import numpy as np
from qiskit import QuantumCircuit
from qiskit.quantum_info import Pauli, PauliLindbladMap, QubitSparsePauliList
from qiskit_addon_slc.bounds import (
compute_backward_bounds,
compute_forward_bounds,
merge_bounds,
)
from qiskit_addon_slc.utils import generate_noise_model_paulis
from samplomatic.transpiler import generate_boxing_pass_manager
from samplomatic.utils import find_unique_box_instructions

def trotter_ising_circuit(num_qubits, num_steps, rx_angle, rzz_angle):
"""Trotterized transverse-field Ising evolution on a 1D chain."""
circuit = QuantumCircuit(num_qubits)
for _ in range(num_steps):
circuit.rx(rx_angle, range(num_qubits))
circuit.barrier()
for start in (0, 1): # even then odd bonds
for i in range(start, num_qubits - 1, 2):
circuit.rzz(rzz_angle, i, i + 1)
circuit.barrier()
return circuit

num_qubits = 10
circuit = trotter_ising_circuit(
num_qubits, num_steps=4, rx_angle=np.pi / 16, rzz_angle=-np.pi / 3
)
observable = Pauli("I" * num_qubits).compose("Z", [num_qubits // 2])

boxing_pass = generate_boxing_pass_manager(
inject_noise_targets="all",
inject_noise_strategy="individual_modification",
inject_noise_site="after",
twirling_strategy="active",
remove_barriers="never",
)
boxed_circuit = boxing_pass.run(circuit)
noise_model_paulis = generate_noise_model_paulis(
find_unique_box_instructions(boxed_circuit)
)

forward_bounds = compute_forward_bounds(
boxed_circuit, noise_model_paulis, observable
)
backward_bounds = compute_backward_bounds(boxed_circuit, noise_model_paulis)

rng = np.random.default_rng(42)
noise_rates = {
layer: PauliLindbladMap.from_components(
rng.random(len(terms)) * 5e-3,
QubitSparsePauliList.from_sparse_list(
terms.to_sparse_list(), terms.num_qubits
),
)
for layer, terms in noise_model_paulis.items()
}
merged_bounds = merge_bounds(
boxed_circuit, forward_bounds, backward_bounds, noise_rates
)
print(f"observable: Z on qubit {num_qubits // 2}")
Optimal spacetime partitioning not implemented! Just partitioning list of noisy boxes.
observable: Z on qubit 5

draw_shaded_lightcone​

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

from qiskit_addon_slc.visualization import draw_shaded_lightcone

draw_shaded_lightcone(
boxed_circuit, merged_bounds, noise_model_paulis, fold=-1, scale=0.5
)

Output of the previous code cell

Важливі зауваження​

1. Кожне число — це сума за членами похибки зі спільним носієм. Без фільтра межі кожного члена похибки, що діє на той самий кубіт або на ту саму пару кубітів, додаються в одне значення: усі три типи Паулі на кубіті та всі дев'ять на парі. Це має два наслідки. Значення може перевищувати 2.0, хоча межа жодного окремого члена не може (бокс з міткою 4.5 — це кілька членів, що складаються, а не порушена межа). Рисунок не може підказати, чи велике значення походить від одного типу Паулі, чи від кількох співмірних; наступний розділ показує, як їх розділити.

2. Колірна шкала починається з 2.0 і зростає разом з даними. Шкала йде від 0 до запису "max_bound" у метаданих накладеної схеми, який є більшим із 2.0 та найбільшого показаного значення. Нефільтровані суми часто перевищують 2.0, тож шкала розтягується, щоб їх вмістити, і той самий колір може означати різні значення на різних рисунках. Коли всі значення малі, шкала залишається на 2.0, і все виглядає однорідно темним (це шкала відображає абсолютний розмір, а не помилку рендерингу).

Розділення типів Паулі​

Використовуй pauli_filter, щоб бачити по одному типу. Ціле число вибирає вагу Паулі; рядок вибирає певний Паулі, зіставлений з кожним членом шуму, зведеним до власного неодиничного носія. Тож "X" вибирає кожну одно-кубітну похибку XX незалежно від того, на який кубіт вона діє, а "XX" вибирає похибки ваги 2 (мітка повної ширини на кшталт "XII" не збігається ні з чим).

from qiskit_addon_slc.visualization import accumulate_filtered_bounds

box_id = next(iter(merged_bounds))
print(f"single-qubit supports of box {box_id!r}:\n")
print(f"{'filter':>8} bounds per qubit")
for pauli_filter in (None, 1, "X", "Y", "Z"):
filtered = accumulate_filtered_bounds(
boxed_circuit, merged_bounds, noise_model_paulis, pauli_filter
)
per_qubit = {
support[0]: round(float(bound), 3)
for support, bound in sorted(filtered[box_id].items())
if len(support) == 1
}
print(f"{pauli_filter!s:>8} {per_qubit}")
single-qubit supports of box 'm0':

filter bounds per qubit
None {0: 0.0, 1: 0.0, 2: 0.0, 3: 0.312, 4: 1.355, 5: 5.111, 6: 1.506, 7: 0.124, 8: 0.019, 9: 0.0}
1 {0: 0.0, 1: 0.0, 2: 0.0, 3: 0.312, 4: 1.355, 5: 5.111, 6: 1.506, 7: 0.124, 8: 0.019, 9: 0.0}
X {0: 0.0, 1: 0.0, 2: 0.0, 3: 0.153, 4: 0.577, 5: 2.0, 6: 0.702, 7: 0.056, 8: 0.01, 9: 0.0}
Y {0: 0.0, 1: 0.0, 2: 0.0, 3: 0.148, 4: 0.607, 5: 2.0, 6: 0.631, 7: 0.057, 8: 0.01, 9: 0.0}
Z {0: 0.0, 1: 0.0, 2: 0.0, 3: 0.011, 4: 0.172, 5: 1.111, 6: 0.172, 7: 0.011, 8: 0.0, 9: 0.0}

Нефільтрований рядок — це сума трьох типів Паулі під ним. Фільтрування — єдиний спосіб дізнатися, чи велике нефільтроване значення походить від одного домінантного типу похибки, чи від кількох співмірних. Це важливо, бо пом'якшення діє на кожен член, а не на кожен вузол.

Передавання того самого фільтра до draw_shaded_lightcone записує його в заголовок рисунка, тож відфільтрований рисунок не можна сплутати з нефільтрованим:

draw_shaded_lightcone(
boxed_circuit,
merged_bounds,
noise_model_paulis,
pauli_filter="X",
fold=-1,
scale=0.5,
)

Output of the previous code cell

Цей рисунок виглядає зовсім інакше, ніж нефільтрований вище, і не лише тому, що показує менше значень. Кожен заповнювач на цих рисунках позначає один носій: окремий кубіт або пару кубітів. Без фільтра бокс показує заповнювач на кожному кубіті та на кожній парі сусідніх кубітів, тож розтягується на кілька стовпців. З "X" залишаються лише одно-кубітні носії, і кожен бокс звужується до одного стовпця.

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

animate_shaded_lightcone​

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

Функції потрібен CouplingMap, який вона розкладає на сітці, вбудовуючи граф пристрою, тому вона працює і зі зв'язністю heavy-hex, і з квадратною зв'язністю.

Примітка: У цій відрендереній документації анімація статична. Повзунок і кнопка відтворення працюють лише тоді, коли ноутбук запущено інтерактивно, наприклад у Jupyter.

from qiskit import transpile
from qiskit.transpiler import CouplingMap
from qiskit_addon_slc.visualization import animate_shaded_lightcone

# A small square-lattice device stands in for a real backend here.
coupling_map = CouplingMap.from_grid(3, 4)

# Lay the chain onto a connected path of physical qubits, snaking through the grid, so the
# transpiler inserts no SWAPs: a SWAP-laden circuit is far more expensive to bound and harder to
# read.
initial_layout = [0, 1, 2, 3, 7, 6, 5, 4, 8, 9]
device_circuit = transpile(
circuit,
coupling_map=coupling_map,
initial_layout=initial_layout,
optimization_level=0,
)
boxed_device_circuit = boxing_pass.run(device_circuit)
device_noise_paulis = generate_noise_model_paulis(
find_unique_box_instructions(boxed_device_circuit),
coupling_map=coupling_map,
circuit=boxed_device_circuit,
)
# The same observable, on the physical qubit the middle of the chain was laid onto.
device_observable = Pauli("I" * device_circuit.num_qubits).compose(
"Z", [initial_layout[num_qubits // 2]]
)
device_bounds = compute_forward_bounds(
boxed_device_circuit, device_noise_paulis, device_observable
)

animate_shaded_lightcone(boxed_device_circuit, device_bounds, coupling_map)

Output of the previous code cell

Як читати анімацію​

Кожен кубіт — це кругова діаграма, один сектор на тип Паулі. Маркер кубіта поділений на три сектори для його помилок XX, YY і ZZ, кожен затінений за власною межею. Це фільтрація з попереднього розділу, вбудована в рисунок (pauli_filter для розрізнення типів не потрібен).

Ребра несуть межі двокубітних помилок. Лінія між двома зв'язаними кубітами затінена за межею для помилок ваги 2, що діють на цю пару.

Світло-сірий означає відсутність даних, а не малу межу. Сектор або ребро малюється світло-сірим, коли в шумовій моделі блока немає для нього члена помилки, наприклад для кубітів, на які блок не діє. Він малюється сірим, а не темним, щоб його не можна було сплутати з нульовою межею. Блоки за межами ліміту max_num_boxes — інша річ: вони мають тривіальну межу 2.0, тож малюються на яскравому кінці шкали.

Повзунок крокує по блоках, а не рівномірно в часі. Є один кадр на кожен шумовий блок у порядку схеми (або у зворотному порядку з reverse=True, що простежує світловий конус назад від спостережуваної). Перегляд у зворотному порядку зазвичай інтуїтивніший: конус росте назовні від вимірюваних кубітів. Кожен кадр підписаний ідентифікатором свого блока — тим самим ключем, під яким словник меж зберігає цей блок, тож повзунок також можна використати, щоб перейти до конкретного блока й подивитися його числа.

Примітка: Розташування кубітів відновлюється з графа карти зв'язності шляхом його вбудовування в квадратну ґратку. Карта зв'язності, яка не вбудовується (бо вона незв'язна або занадто щільно зв'язана для сітки), викликає ValueError, а не малює оманливу картину.

Що використовувати​

draw_shaded_lightconeanimate_shaded_lightcone
геометріясхемакарта зв'язності QPU
найкраще для1D-ланцюжківбудь-якої зв'язності
типи Паулісумуються, якщо не відфільтрованізавжди розділені, як сектори діаграми
часусі шари одразуодин шар на кадр
результатрисунок matplotlibрисунок plotly

Для 1D-ланцюжка статичний рисунок легше сприймати з одного погляду, і його можна відтворити в статті. За межами 1D анімація — єдиний з двох варіантів, що вірно показує просторову структуру.