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

Налагодження та трасування

Функція build() перетворює анотовану схему на пару з шаблонної схеми та samplex. У складній схемі може бути важко зрозуміти, який box в початковій схемі відповідає яким бар’єрам у шаблоні або яким вузлам у DAG samplex.

Samplomatic надає два взаємодоповнювальні інструменти трасування:

  • Мітки бар’єрів: Кожен бар’єр у шаблонній схемі несе мітку, що кодує його походження. Вони завжди присутні незалежно від того, чи встановлено debug=True.

  • Інформація трасування у вузлах samplex: Коли build() викликається з debug=True, кожен вузол samplex несе об’єкт TraceInfo, що пов’язує його з boxом або boxами, які його створили.

Обидва інструменти найкорисніші, коли boxи несуть анотацію Tag, яка прикріплює до box рядок ref. Крім того, також прикріплюються посилання InjectNoise.

Позначення boxів тегами​

Анотація Tag прикріплює до box рядок ref. Потім ref з’являється в мітках бар’єрів та в інформації трасування у вузлах samplex.

Наступна приклад схема має три блоки: два блоки з тегом CX на непересічних парах кубітів і блок вимірювання без тегу з правим «одягом». Другий блок CX також містить анотацію InjectNoise — і його посилання tag, і посилання шуму ref з'являтимуться в мітках бар'єрів. Два блоки CX охоплюють різні кубіти, що дозволяє оптимізатору samplex об'єднати їхні вузли поширення у спільні вузли. Це показано в розділі з інформацією про трасування.

from qiskit.circuit import QuantumCircuit

from samplomatic import InjectNoise, Tag, Twirl, build

circuit = QuantumCircuit(4, 4)

with circuit.box([Twirl(), Tag("cx_ab")]):
circuit.cx(0, 1)

with circuit.box([Twirl(), InjectNoise("cx_noise"), Tag("cx_cd")]):
circuit.cx(2, 3)

with circuit.box([Twirl(), Tag("meas_box")]):
circuit.measure(range(4), range(4))

circuit.draw("mpl")

../_images/f6b6fe7b60a39ba897814008443fa79b66213005462ab09f49ffc89aab1f3668.png

Мітки бар’єрів у шаблонній схемі​

Виклик build() для наведеної вище схеми створює шаблон, бар’єри якого несуть ідентифікаційні мітки. Кожна мітка має вигляд {side}{scope}@{key=value&...}:

  • Сторона: L = межа лівого dressing, M = межа внутрішнього вмісту box, R = межа правого dressing.

  • Область: Цілочисловий індекс (або список, розділений підкресленнями, для вкладених boxів), що розрізняє кілька boxів на одному рівні вкладеності.

  • Анотації: Список пар key=value, що починається з @ і розділений &, отриманий з анотацій box. Ключ tag походить від анотації Tag, а inject_noise — від анотації InjectNoise.

Бар’єри від непозначених тегами boxів несуть лише сторону та область (наприклад, L2), без суфікса @.

template, samplex = build(circuit)
template.draw("mpl", fold=100)

../_images/e533d70a29504005a636aa27306d3ce3b1d2488358ac6a53f0674f2934698ab8.png

Мітки бар’єрів також можна отримати програмно:

barrier_labels = [instr.operation.label for instr in template if instr.operation.name == "barrier"]
barrier_labels
['L0@tag=cx_ab',
'M0@tag=cx_ab',
'R0@tag=cx_ab',
'L1@inject_noise=cx_noise&tag=cx_cd',
'M1@inject_noise=cx_noise&tag=cx_cd',
'R1@inject_noise=cx_noise&tag=cx_cd',
'L2@tag=meas_box',
'M2@tag=meas_box',
'R2@tag=meas_box']

Інформація трасування у вузлах samplex​

Передача debug=True до build() прикріплює інформацію трасування до кожного вузла samplex. Атрибут trace_info кожного вузла — це об’єкт TraceInfo, чий словник trace_refs зіставляє ключі анотацій (наприклад, "tag" і "inject_noise") із множинами рядків ref. Вузли без відповідної анотації box мають trace_info=None.

Коли draw() викликається для samplex, побудованого в режимі налагодження, наведення курсора на будь-який вузол в інтерактивному графі показує його trace_refs у підказці.

template, samplex = build(circuit, debug=True)
samplex.draw()

Інформацію трасування також можна переглядати програмно. Зверни увагу, що деякі вузли несуть посилання і від 'cx_ab', і від 'cx_cd'. Оптимізатор samplex об’єднав їхні паралельні вузли поширення, бо ці два boxи покривають непересічні кубіти та мають спільного попередника від емісії box вимірювання з правим dressing.

for node in samplex.graph.nodes():
if node.trace_info is not None:
tags = node.trace_info.trace_refs.get("tag", set())
merged = " ← merged" if len(tags) > 1 else ""
print(f"{type(node).__name__:40s} tags={tags}{merged}")
TwirlSamplingNode tags={'meas_box'}
InjectNoiseNode tags={'cx_cd'}
CollectZ2ToOutputNode tags={'cx_cd'}
TwirlSamplingNode tags={'cx_cd'}
CombineRegistersNode tags={'cx_cd'}
CollectTemplateValues tags={'cx_cd'}
TwirlSamplingNode tags={'cx_ab'}
CombineRegistersNode tags={'cx_ab', 'cx_cd'} ← merged
CollectTemplateValues tags={'cx_ab', 'cx_cd'} ← merged

Щоб знайти всі вузли, що походять від певного box, відфільтруй за ключем "tag":

tag_ref = "cx_ab"
matching_nodes = [
node
for node in samplex.graph.nodes()
if node.trace_info is not None and tag_ref in node.trace_info.trace_refs.get("tag", set())
]

print(f"Nodes originating from box '{tag_ref}':")
for node in matching_nodes:
print(f" {type(node).__name__}")
Nodes originating from box 'cx_ab':
TwirlSamplingNode
CombineRegistersNode
CollectTemplateValues

DAG samplex​

Samplex, який повертає build(), — це напрямлений ациклічний граф (DAG), де ребра позначають залежність від регістрів. Тобто ребро від вузла A до вузла B означає, що B має дочекатися, поки A подіє на спільні віртуальні регістри, перш ніж B зможе діяти. Це не часове впорядкування, як у DAG квантової схеми. Натомість граф тече від вузлів, відповідальних за генерацію рандомізацій, до вузлів, відповідальних за їхній синтез як виходів.

Є три типи вузлів, кожен із власним візуальним стилем в інтерактивному графіку:

ФормаКолірТипРоль
ЗіркаЧервонийSamplingNodeСтворює нові віртуальні регістри з розподілу або вхідних даних
КолоЗеленийEvaluationNodeПеретворює, комбінує або поширює віртуальні регістри
МетеликСиній / ФіолетовийCollectionNodeЧитає регістри та записує у виходи sample()

Виконання відбувається в три фази:

  1. Спочатку виконуються всі вузли вибірки (паралельно).

  2. Далі виконуються вузли обчислення в топологічному порядку (паралельно в межах кожного покоління).

  3. Вузли збору виконуються останніми (паралельно).

Розкладка graphviz це відображає. Вузли вибірки з’являються вгорі, вузли збору — внизу.

Підказки при наведенні​

Кожен вузол в інтерактивній візуалізації показує підказку, коли ти наводиш на нього курсор. Підказка містить таку інформацію:

  • Назва класу вузла та його цілочисловий індекс у графі.

  • Маніфести регістрів, тобто які регістри вузол створює, читає, у які записує та які видаляє. Вони показують, як дані течуть між вузлами.

  • Деталі, специфічні для вузла, зокрема тип розподілу для вузлів вибірки, операнд для вузлів множення, індекси параметрів шаблону для вузлів збору тощо.

  • Trace refs (лише коли debug=True) — це ключі анотацій і рядки ref, що пов’язують вузол із boxами, з яких він походить.

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

Підсумок samplex​

Перед оглядом візуалізації print(samplex) дає короткий текстовий підсумок кількості вузлів, потрібних входів і обіцяних виходів.

template, samplex = build(circuit)
print(samplex)
Samplex(<17 nodes>)
Inputs:
- 'pauli_lindblad_maps.cx_noise' <PauliLindbladMap>: A PauliLindblad map acting on 2
qubits, with 'num_terms_cx_noise' terms.

Outputs:
* 'measurement_flips.c' <bool['num_randomizations', 1, 4]>: Bit-flip corrections for
measurement twirling.
* 'parameter_values' <float32['num_randomizations', 24]>: Parameter values valid for an
associated template circuit.
* 'pauli_signs' <bool['num_randomizations', 1]>: Signs from sampled Pauli Lindblad
maps, where boolean values represent the parity of the number of non-trivial factors in the
sampled error that arise from negative rates. In other words, in order to implement basic
PEC, the sign used to correct expectation values should be ``(-1)**bool_value``. The order
matches the iteration order of boxes in the original circuit with noise injection
annotations.

Огляд регістрів​

Передача keep_registers=True до sample() зберігає проміжні об’єкти VirtualRegister, що є активними наприкінці вибірки, і зберігає їх у outputs.metadata["registers"]. Кожен регістр — це двовимірний масив (форма (num_subsystems, num_randomizations), з можливими додатковими вимірами форми гейта) віртуальних елементів групи.

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

from qiskit.quantum_info import PauliLindbladMap

outputs = samplex.sample(
{"pauli_lindblad_maps.cx_noise": PauliLindbladMap.identity(2)},
num_randomizations=3,
keep_registers=True,
)
for name, reg in outputs.metadata["registers"].items():
print(f"{name}: type={reg.TYPE.value}, shape={reg.virtual_gates.shape}")
lhs_0: type=pauli, shape=(4, 3)
rhs_0: type=pauli, shape=(4, 3)
lhs_3: type=pauli, shape=(2, 3)
rhs_3: type=pauli, shape=(2, 3)
inject_noise_1: type=pauli, shape=(2, 3)
sign_1: type=z2, shape=(1, 3)
lhs_7: type=pauli, shape=(2, 3)
rhs_7: type=pauli, shape=(2, 3)
meas_prop_6: type=pauli, shape=(4, 3)
collect_10: type=u2, shape=(2, 3, 2, 2)
meas_prop_z2a_6: type=z2, shape=(4, 3)
collect_9: type=u2, shape=(4, 3, 2, 2)
collect_5: type=u2, shape=(2, 3, 2, 2)

Ти можеш переглянути вміст кінцевого стану певного регістра. У наступному прикладі регістр має тип PauliRegister. Докладніше про формат зберігання див. API documentation(/apidocs/index)__.

print("lhs_0:", outputs.metadata["registers"]["lhs_0"])
outputs.metadata["registers"]["lhs_0"].virtual_gates
lhs_0: PauliRegister(<4, 3>)
array([[0, 0, 1],
[3, 1, 1],
[3, 0, 0],
[1, 0, 0]], dtype=uint8)

Як вузли samplex відображаються на параметри шаблону​

Масив outputs["parameter_values"], який повертає sample(), має форму (num_randomizations, N), де N збігається з len(template.parameters). Кожен стовпець відповідає одному параметру в шаблонній схемі — i-й стовпець заповнює template.parameters[i].

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

Див. посібник {Samplex inputs and outputs}samplex-io, щоб дізнатися, як прив’язати вибрані значення параметрів до шаблонної схеми та запускати експерименти.

from samplomatic.samplex.nodes import CollectTemplateValues

template, samplex = build(circuit, debug=True)
for node in samplex.graph.nodes():
if isinstance(node, CollectTemplateValues):
tags = node.trace_info.trace_refs.get("tag", set()) if node.trace_info else set()
print(f"tags={tags} → template param indices: {node.template_idxs.tolist()}")
tags={'cx_cd'} → template param indices: [[6, 7, 8], [9, 10, 11]]
tags={'cx_ab', 'cx_cd'} → template param indices: [[12, 13, 14], [15, 16, 17], [18, 19, 20], [21, 22, 23]]
tags=set() → template param indices: [[0, 1, 2], [3, 4, 5]]

Автоматичне тегування​

Під час використання generate_boxing_pass_manager() параметр add_tags автоматично додає анотації Tag до всіх boxів. Доступні три режими:

  • "unique_instance": Призначає послідовні ref (t0, t1 і так далі) боксам у порядку схеми. Кожен бокс отримує окремий ref незалежно від своєї структури.

  • "unique_box": Обчислює структурний хеш вмісту кожного бокса і призначає однаковий ref усім структурно еквівалентним бокcам. Це корисно для групування боксів за типом, а не за позицією.

  • "noise_ref": Копіює ref з анотації InjectNoise кожного бокса і позначає лише ті бокси, які її мають. Це корисно, коли змістовні noise ref уже існують.

У наведеному нижче прикладі "unique_instance" і "unique_box" застосовуються до схеми, дві CX-бокси якої структурно еквівалентні. З "unique_instance" кожен отримує окремий ref, а з "unique_box" вони мають спільний.

from samplomatic.transpiler import generate_boxing_pass_manager

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

# unique_instance: every box gets a distinct tag ref
pm = generate_boxing_pass_manager(add_tags="unique_instance")
boxed = pm.run(base_circuit)
template, _ = build(boxed)

print("unique_instance barrier labels:")
for instr in template:
if instr.operation.name == "barrier" and instr.operation.label:
print(f" {instr.operation.label}")
unique_instance barrier labels:
L0@tag=t0
M0@tag=t0
R0@tag=t0
L1@tag=t1
M1@tag=t1
R1@tag=t1
L2@tag=t2
M2@tag=t2
R2@tag=t2