Debugging dan tracing
Fungsi build() mengubah circuit beranotasi menjadi pasangan template circuit dan samplex. Pada circuit yang kompleks, sulit untuk tahu box mana di circuit asli yang sesuai dengan barrier mana di template, atau node mana di DAG samplex.
Samplomatic menyediakan dua alat tracing yang saling melengkapi:
-
Label barrier: Setiap barrier di template circuit membawa label yang mengodekan asalnya. Label ini selalu ada, terlepas dari apakah
debug=Truediatur atau tidak. -
Info trace pada node samplex: Ketika
build()dipanggil dengandebug=True, setiap node samplex membawa objekTraceInfoyang menautkannya kembali ke box atau beberapa box yang menghasilkannya.
Kedua alat ini paling berguna ketika box membawa anotasi Tag, yang menempelkan string ref ke box. Selain itu, referensi InjectNoise juga ikut ditempelkan.
Beri tag pada box
Anotasi Tag menempelkan string ref ke sebuah box. ref itu lalu muncul di label barrier dan di informasi trace pada node samplex.
Circuit contoh berikut punya tiga box: dua box CX bertag pada pasangan qubit yang saling lepas, dan satu box pengukuran right-dressed tanpa tag. Box CX kedua juga membawa anotasi InjectNoise — baik ref tag-nya maupun ref noise-nya akan muncul di label barrier. Kedua box CX mencakup qubit yang berbeda, sehingga optimizer samplex bisa menggabungkan node propagasi keduanya menjadi node bersama. Hal ini ditunjukkan di bagian informasi trace.
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")
Label barrier di template circuit
Memanggil build() pada circuit di atas menghasilkan template yang barrier-nya membawa label pengenal. Setiap label berbentuk {side}{scope}@{key=value&...}:
-
Side:
L= batas left-dressing,M= batas isi dalam box,R= batas right-dressing. -
Scope: Indeks bilangan bulat (atau daftar yang dipisahkan underscore untuk box bersarang) yang membedakan beberapa box pada level penyarangan yang sama.
-
Anotasi: Daftar pasangan
key=valueberawalan@dan dipisahkan&yang diturunkan dari anotasi box. Keytagberasal dari anotasiTagdaninject_noiseberasal dari anotasiInjectNoise.
Barrier dari box tanpa tag hanya membawa side dan scope (misalnya, L2), tanpa akhiran @.
template, samplex = build(circuit)
template.draw("mpl", fold=100)
Label barrier juga bisa diekstrak secara programatik:
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']
Informasi trace pada node samplex
Memberikan debug=True ke build() menempelkan informasi trace ke setiap node samplex. Atribut trace_info tiap node adalah objek TraceInfo yang dictionary trace_refs-nya memetakan key anotasi (misalnya, "tag" dan "inject_noise") ke himpunan string ref. Node tanpa anotasi box yang sesuai memiliki trace_info=None.
Ketika draw() dipanggil pada samplex hasil build debug, mengarahkan kursor ke node mana pun di grafik interaktif akan menampilkan trace_refs-nya di dalam tooltip hover.
template, samplex = build(circuit, debug=True)
samplex.draw()
Informasi trace juga bisa diperiksa secara programatik. Perhatikan bahwa beberapa node membawa referensi dari 'cx_ab' maupun 'cx_cd'. Optimizer samplex menggabungkan node propagasi paralel keduanya karena kedua box mencakup qubit yang saling lepas dan berbagi predecessor yang sama dari emisi box pengukuran right-dressed.
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
Untuk menemukan semua node yang berasal dari box tertentu, filter berdasarkan key "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 yang dikembalikan oleh build() adalah directed acyclic graph (DAG) tempat edge menyatakan dependensi register. Artinya, edge dari node A ke node B berarti B harus menunggu A selesai bekerja pada register virtual bersama sebelum B boleh bekerja. Ini bukan urutan temporal seperti DAG pada circuit kuantum. Sebaliknya, grafik mengalir dari node yang bertugas menghasilkan randomisasi ke node yang bertugas mensintesisnya sebagai output.
Ada tiga tipe node, masing-masing dengan gaya visual berbeda di plot interaktif:
| Bentuk | Warna | Tipe | Peran |
|---|---|---|---|
| Bintang | Merah | SamplingNode | Membuat register virtual baru dari distribusi atau input |
| Lingkaran | Hijau | EvaluationNode | Mentransformasi, menggabungkan, atau mempropagasi register virtual |
| Dasi kupu-kupu | Biru / Ungu | CollectionNode | Membaca register dan menulis ke output sample() |
Eksekusi berjalan dalam tiga fase:
-
Semua node sampling berjalan lebih dulu (secara paralel).
-
Node evaluation berjalan berikutnya, dalam urutan topologis (paralel di dalam setiap generasi).
-
Node collection berjalan terakhir (secara paralel).
Layout graphviz mencerminkan hal ini. Node sampling muncul di atas, node collection di bawah.
Tooltip hover
Setiap node di visualisasi interaktif menampilkan tooltip saat kamu mengarahkan kursor ke atasnya. Tooltip berisi informasi berikut:
-
Nama kelas node dan indeks grafiknya yang berupa bilangan bulat.
-
Manifest register, yaitu register mana yang dibuat, dibaca, ditulis, dan dihapus oleh node. Ini memberi tahu bagaimana data mengalir antar node.
-
Detail khusus node, termasuk tipe distribusi untuk node sampling, operand untuk node perkalian, indeks parameter template untuk node collection, dan seterusnya.
-
Trace ref (hanya saat
debug=True), yaitu key anotasi dan string ref yang menautkan node kembali ke box asalnya.
Mengklik plot lalu mengarahkan kursor ke node satu per satu adalah cara tercepat untuk memahami apa yang dilakukan sebuah node tanpa membaca source code.
Ringkasan samplex
Sebelum memeriksa visualisasi, print(samplex) memberi ringkasan teks singkat tentang jumlah node, input yang dibutuhkan, dan output yang dijanjikan.
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.
Periksa register
Memberikan keep_registers=True ke sample() mempertahankan objek VirtualRegister perantara yang masih aktif di akhir sampling, dan menyimpannya di outputs.metadata["registers"]. Setiap register adalah array 2D (bentuk (num_subsystems, num_randomizations), dengan kemungkinan dimensi gate-shape tambahan di belakang) berisi elemen grup virtual.
Ini berguna untuk memverifikasi bahwa gate virtual digabungkan dengan benar, atau untuk memeriksa sampel Pauli atau unitary mentah sebelum disintesis menjadi sudut rotasi.
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)
Kamu bisa melihat isi keadaan akhir dari register tertentu. Pada contoh berikut, register bertipe PauliRegister. Lihat API documentation(/apidocs/index)__ untuk detail tentang format penyimpanan.
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)
Bagaimana node samplex dipetakan ke parameter template
Array outputs["parameter_values"] yang dikembalikan oleh sample() berbentuk (num_randomizations, N), dengan N sama dengan len(template.parameters). Setiap kolom sesuai dengan satu parameter di template circuit — kolom ke-i mengisi template.parameters[i].
Node collection CollectTemplateValues adalah penghubung antara register virtual dan parameter template. Setiap node seperti itu menyimpan informasi indeks yang mencatat kolom mana dari array output yang ditulisnya. Ini memungkinkan kita menelusuri subsistem gate virtual mana yang menggerakkan parameter template mana.
Lihat panduan {Samplex inputs and outputs}samplex-io untuk mempelajari cara mengikat nilai parameter hasil sampling ke template circuit dan menjalankan eksperimen.
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]]
Tagging otomatis
Saat memakai generate_boxing_pass_manager(), parameter add_tags secara otomatis menambahkan anotasi Tag ke semua box. Tersedia tiga mode:
-
"unique_instance": Memberikanrefberurutan (t0,t1, dan seterusnya) ke box sesuai urutan circuit. Setiap box mendapatrefyang berbeda, apa pun strukturnya. -
"unique_box": Menghitung hash struktural dari isi setiap box dan memberikanrefyang sama ke semua box yang strukturnya setara. Ini berguna untuk mengelompokkan box berdasarkan tipe, bukan posisi. -
"noise_ref": Menyalinrefdari anotasiInjectNoisemilik setiap box dan hanya menandai box yang punya anotasi tersebut. Ini berguna kalau noise ref yang bermakna sudah ada.
Contoh berikut menerapkan "unique_instance" dan "unique_box" ke sebuah Circuit yang kedua box CX-nya setara secara struktural. Dengan "unique_instance", masing-masing mendapat ref yang berbeda, sedangkan dengan "unique_box", keduanya berbagi satu.
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
