Debugging at tracing
Ang function na build() ay nagta-transform ng annotated na circuit tungo sa pares ng template circuit at samplex. Sa isang masalimuot na circuit, maaaring mahirap malaman kung aling box sa orihinal na circuit ang tumutugma sa aling mga barrier sa template, o aling mga node sa samplex DAG.
Nagbibigay ang Samplomatic ng dalawang magkatuwang na tracing tool:
-
Mga barrier label: Ang bawat barrier sa template circuit ay may label na nag-e-encode ng pinagmulan nito. Palagi itong naroroon, itinakda man o hindi ang
debug=True. -
Trace info sa mga samplex node: Kapag tinawag ang
build()na maydebug=True, ang bawat samplex node ay may dalangTraceInfoobject na nag-uugnay pabalik sa box o mga box na lumikha nito.
Pinakakapaki-pakinabang ang parehong tool kapag ang mga box ay may Tag annotation, na nag-aattach ng ref string sa box. Bilang alternatibo, ina-attach din ang mga reference ng InjectNoise.
Mga tag box
Ang isang Tag annotation ay nag-aattach ng ref string sa isang box. Lumalabas ang ref sa mga barrier label at sa trace information sa mga samplex node.
Ang sumusunod na halimbawang circuit ay may tatlong box: dalawang naka-tag na CX box sa magkahiwalay na qubit pair, at isang walang tag na right-dressed na measurement box. Ang ikalawang CX box ay may dala ring InjectNoise annotation — parehong ang tag ref at ang noise ref nito ay lalabas sa mga barrier label. Ang dalawang CX box ay sumasaklaw sa magkaibang qubit, na nagbibigay-daan sa samplex optimizer na pagsamahin ang kanilang mga propagation node sa mga shared node. Ipinapakita ito sa seksyon ng trace information.
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")
Mga barrier label sa template circuit
Ang pagtawag sa build() sa circuit sa itaas ay gumagawa ng template na ang mga barrier ay may mga identifying label. Ang bawat label ay may anyong {side}{scope}@{key=value&...}:
-
Panig:
L= hangganan ng kaliwang dressing,M= hangganan ng nilalaman ng panloob na box,R= hangganan ng kanang dressing. -
Scope: Isang integer index (o listahang pinaghihiwalay ng underscore para sa mga nested na box) na nagpapakilala sa pagitan ng maraming box sa parehong antas ng nesting.
-
Mga annotation: Isang listahang nagsisimula sa
@at pinaghihiwalay ng&ng mga pares nakey=valuena hinango mula sa mga annotation ng box. Angtagkey ay galing sa isangTagannotation at anginject_noiseay galing sa isangInjectNoiseannotation.
Ang mga barrier mula sa mga walang tag na box ay may side at scope lamang (halimbawa, L2), na walang @ suffix.
template, samplex = build(circuit)
template.draw("mpl", fold=100)
Maaari ring i-extract ang mga barrier label sa programmatic na paraan:
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']
Trace information sa mga samplex node
Ang pagpasa ng debug=True sa build() ay nag-aattach ng trace information sa bawat samplex node. Ang attribute na trace_info ng bawat node ay isang TraceInfo object na ang trace_refs dictionary ay nagmamapa ng mga annotation key (halimbawa, "tag" at "inject_noise") sa mga set ng ref string. Ang mga node na walang katumbas na box annotation ay may trace_info=None.
Kapag tinawag ang draw() sa isang samplex na binuo sa debug mode, ang pag-hover sa anumang node sa interactive graph ay naglalantad ng trace_refs nito sa loob ng hover tooltip.
template, samplex = build(circuit, debug=True)
samplex.draw()
Maaari ring siyasatin ang trace information sa programmatic na paraan. Pansinin na may ilang node na may dalang mga reference mula sa parehong 'cx_ab' at 'cx_cd'. Pinagsama ng samplex optimizer ang kanilang mga parallel propagation node dahil ang dalawang box ay sumasaklaw sa magkahiwalay na qubit at may common predecessor mula sa emission ng right-dressed na measurement box.
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
Para hanapin ang lahat ng node na nagmula sa isang partikular na box, mag-filter ayon sa "tag" key:
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
Ang samplex DAG
Ang samplex na ibinabalik ng build() ay isang directed acyclic graph (DAG) kung saan ang mga edge ay nagsasaad ng register dependency. Ibig sabihin, ang edge mula node A papuntang node B ay nangangahulugang kailangang maghintay ang B hanggang makapag-act ang A sa mga shared virtual register bago payagang mag-act ang B. Hindi ito temporal na pagkakasunod-sunod tulad ng DAG ng isang quantum circuit. Sa halip, ang graph ay dumadaloy mula sa mga node na responsable sa pagbuo ng mga randomization patungo sa mga node na responsable sa pag-synthesize ng mga ito bilang mga output.
May tatlong uri ng node, bawat isa ay may natatanging visual style sa interactive plot:
| Hugis | Kulay | Uri | Papel |
|---|---|---|---|
| Star | Red | SamplingNode | Gumagawa ng bagong virtual register mula sa distribution o input |
| Circle | Green | EvaluationNode | Nagta-transform, nagsasama, o nagpapalaganap ng mga virtual register |
| Bowtie | Blue / Purple | CollectionNode | Nagbabasa ng mga register at nagsusulat sa mga output ng sample() |
Ang execution ay nagpapatuloy sa tatlong yugto:
-
Una munang tumatakbo ang lahat ng sampling node (nang magkakasabay).
-
Susunod na tumatakbo ang mga evaluation node, sa topological order (magkakasabay sa loob ng bawat henerasyon).
-
Huling tumatakbo ang mga collection node (nang magkakasabay).
Ipinapakita ito ng graphviz layout. Ang mga sampling node ay nasa itaas, ang mga collection node ay nasa ibaba.
Mga hover tooltip
Ang bawat node sa interactive visualization ay nagpapakita ng tooltip kapag nag-hover ka rito. Ang tooltip ay naglalaman ng sumusunod na impormasyon:
-
Pangalan ng class ng node at ang integer graph index nito.
-
Mga register manifest, kung aling mga register ang ginagawa, binabasa, sinusulatan, at inaalis ng node. Sinasabi nito sa iyo kung paano dumadaloy ang data sa pagitan ng mga node.
-
Mga detalyeng partikular sa node, kabilang ang uri ng distribution para sa mga sampling node, ang operand para sa mga multiplication node, ang mga template parameter index para sa mga collection node, at iba pa.
-
Trace refs (kapag
debug=Truelamang), na mga annotation key at ref string na nag-uugnay sa node pabalik sa mga pinagmulan nitong box.
Ang pag-click sa plot at pagkatapos ay pag-hover sa mga indibidwal na node ang pinakamabilis na paraan para maunawaan kung ano ang ginagawa ng isang node nang hindi nagbabasa ng source code.
Buod ng samplex
Bago suriin ang visualization, ang print(samplex) ay nagbibigay ng mabilis na text summary ng bilang ng node, mga kinakailangang input, at mga ipinangakong output.
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.
Siyasatin ang mga register
Ang pagpasa ng keep_registers=True sa sample() ay nagpapanatili sa mga intermediate na VirtualRegister object na buhay pa sa pagtatapos ng sampling, at iniimbak ang mga ito sa outputs.metadata["registers"]. Ang bawat register ay isang 2D array (hugis (num_subsystems, num_randomizations), na may posibleng trailing na gate-shape dimension) ng mga virtual group element.
Kapaki-pakinabang ito para i-verify na tama ang pagkakasama-sama ng mga virtual gate, o para siyasatin ang raw na Pauli o unitary sample bago i-synthesize ang mga ito bilang mga rotation angle.
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)
Maaari mong tingnan ang nilalaman ng end-state ng isang partikular na register. Sa sumusunod na halimbawa, ang register ay may uri na PauliRegister. Tingnan ang API documentation(/apidocs/index)__ para sa mga detalye tungkol sa storage format.
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)
Kung paano nagmamapa ang mga samplex node sa mga template parameter
Ang array na outputs["parameter_values"] na ibinabalik ng sample() ay may hugis na (num_randomizations, N), kung saan ang N ay tumutugma sa len(template.parameters). Ang bawat column ay tumutugma sa isang parameter sa template circuit — pinupunan ng i-th na column ang template.parameters[i].
Ang mga collection node na CollectTemplateValues ang ugnayan sa pagitan ng mga virtual register at mga template parameter. Ang bawat ganoong node ay may hawak na index information na nagtatala kung aling mga column ng output array ang sinusulatan nito. Dahil dito, posibleng matunton kung aling mga virtual gate subsystem ang nagpapatakbo sa aling mga template parameter.
Tingnan ang guide na {Samplex inputs and outputs}samplex-io para malaman kung paano i-bind ang mga sampled na parameter value sa template circuit at magpatakbo ng mga experiment.
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]]
Awtomatikong pag-tag
Kapag gumagamit ng generate_boxing_pass_manager(), ang parameter na add_tags ay awtomatikong nagdaragdag ng mga Tag annotation sa lahat ng box. May tatlong mode na available:
-
"unique_instance": Nagtatalaga ng sunud-sunod naref(t0,t1, at iba pa) sa mga box ayon sa pagkakasunod sa circuit. Bawat box ay may natatangingrefanuman ang istruktura nito. -
"unique_box": Kinakalkula ang structural hash ng nilalaman ng bawat box at nagtatalaga ng parehongrefsa lahat ng box na magkapareho ang istruktura. Kapaki-pakinabang ito sa pag-group ng mga box ayon sa uri sa halip na ayon sa posisyon. -
"noise_ref": Kinokopya angrefmula saInjectNoiseannotation ng bawat box at tina-tag lang ang mga box na mayroon nito. Kapaki-pakinabang ito kapag may mga makabuluhang noise ref na.
Ang sumusunod na halimbawa ay nag-a-apply ng "unique_instance" at "unique_box" sa isang circuit na ang dalawang CX box ay magkapareho ang istruktura. Sa "unique_instance", bawat isa ay may natatanging ref, samantalang sa "unique_box", iisa ang ibinabahagi nila.
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
