Налагодження та трасування
Функція 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")
Мітки бар’єрів у шаблонній схемі
Виклик 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)
Мітки бар’єрів також можна отримати програмно:
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() |
Виконання відбувається в три фази:
-
Спочатку виконуються всі вузли вибірки (паралельно).
-
Далі виконуються вузли обчислення в топологічному порядку (паралельно в межах кожного покоління).
-
Вузли збору виконуються останніми (паралельно).
Розкладка 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
