Ladění a trasování
Funkce build() převede anotovaný obvod na dvojici šablonového obvodu a samplexu. Ve složitém obvodu může být těžké poznat, který box v původním obvodu odpovídá kterým bariérám v šabloně nebo kterým uzlům v DAG samplexu.
Samplomatic poskytuje dva doplňující se nástroje pro trasování:
-
Popisky bariér: Každá bariéra v šablonovém obvodu nese popisek, který kóduje její původ. Jsou vždy přítomny bez ohledu na to, zda je nastaveno
debug=True. -
Trasovací informace na uzlech samplexu: Když je
build()zavoláno sdebug=True, každý uzel samplexu nese objektTraceInfo, který ho propojuje s boxem nebo boxy, které ho vytvořily.
Oba nástroje jsou nejužitečnější, když boxy nesou anotaci Tag, která k boxu připojí řetězec ref. Alternativně se připojují i reference InjectNoise.
Označení boxů
Anotace Tag připojí k boxu řetězec ref. Tento ref se pak objeví v popiscích bariér a v trasovacích informacích na uzlech samplexu.
Následující ukázkový obvod má tři boxy: dva označené boxy CX na disjunktních dvojicích qubitů a neoznačený box měření s pravým dressingem. Druhý box CX nese také anotaci InjectNoise — jeho reference tag i reference šumu ref se objeví v popiscích bariér. Oba boxy CX pokrývají různé qubity, což optimalizátoru samplexu umožňuje sloučit jejich propagační uzly do sdílených uzlů. To je ukázáno v sekci o trasovacích informacích.
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")
Popisky bariér v šablonovém obvodu
Zavolání build() na obvodu výše vytvoří šablonu, jejíž bariéry nesou identifikační popisky. Každý popisek má tvar {side}{scope}@{key=value&...}:
-
Strana (Side):
L= hranice levého dressingu,M= hranice vnitřního obsahu boxu,R= hranice pravého dressingu. -
Rozsah (Scope): Celočíselný index (nebo seznam oddělený podtržítky pro vnořené boxy), který odlišuje více boxů na téže úrovni vnoření.
-
Anotace: Seznam dvojic
key=values prefixem@a oddělovačem&, odvozený z anotací boxu. Klíčtagpochází z anotaceTagainject_noisez anotaceInjectNoise.
Bariéry z neoznačených boxů nesou jen stranu a rozsah (například L2), bez přípony @.
template, samplex = build(circuit)
template.draw("mpl", fold=100)
Popisky bariér lze také získat programově:
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']
Trasovací informace na uzlech samplexu
Předání debug=True do build() připojí trasovací informace ke každému uzlu samplexu. Atribut trace_info každého uzlu je objekt TraceInfo, jehož slovník trace_refs mapuje klíče anotací (například "tag" a "inject_noise") na množiny řetězců ref. Uzly bez odpovídající anotace boxu mají trace_info=None.
Když je draw() zavoláno na samplexu sestaveném v režimu ladění, najetí na libovolný uzel v interaktivním grafu zobrazí jeho trace_refs v tooltipu.
template, samplex = build(circuit, debug=True)
samplex.draw()
Trasovací informace lze také zkoumat programově. Všimni si, že některé uzly nesou reference z 'cx_ab' i 'cx_cd'. Optimalizátor samplexu sloučil jejich paralelní propagační uzly, protože oba boxy pokrývají disjunktní qubity a sdílejí společného předchůdce z emise boxu měření s pravým dressingem.
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
Chceš-li najít všechny uzly pocházející z konkrétního boxu, filtruj podle klíče "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 samplexu
Samplex vrácený funkcí build() je orientovaný acyklický graf (DAG), kde hrany označují závislost registrů. To znamená, že hrana z uzlu A do uzlu B značí, že B musí počkat, až A zapůsobí na sdílené virtuální registry, než smí B působit sám. Nejde o časové uspořádání jako u DAG kvantového obvodu. Graf spíše teče od uzlů odpovědných za generování randomizací k uzlům odpovědným za jejich syntézu na výstupy.
Existují tři typy uzlů, z nichž každý má v interaktivním grafu odlišný vizuální styl:
| Tvar | Barva | Typ | Role |
|---|---|---|---|
| Hvězda | Červená | SamplingNode | Vytváří nové virtuální registry z rozdělení nebo vstupu |
| Kruh | Zelená | EvaluationNode | Transformuje, kombinuje nebo propaguje virtuální registry |
| Motýlek | Modrá / Fialová | CollectionNode | Čte registry a zapisuje do výstupů sample() |
Provádění probíhá ve třech fázích:
-
Nejprve se spustí všechny uzly vzorkování (paralelně).
-
Poté se spustí uzly vyhodnocení v topologickém pořadí (paralelně v rámci každé generace).
-
Nakonec se spustí uzly sběru (paralelně).
Rozložení graphviz to odráží. Uzly vzorkování jsou nahoře, uzly sběru dole.
Tooltipy při najetí myší
Každý uzel v interaktivní vizualizaci zobrazí po najetí myší tooltip. Tooltip obsahuje následující informace:
-
Název třídy uzlu a jeho celočíselný index v grafu.
-
Manifesty registrů, tedy které registry uzel vytváří, čte, zapisuje do nich a odstraňuje. Říkají ti, jak data proudí mezi uzly.
-
Podrobnosti specifické pro uzel, včetně typu rozdělení u uzlů vzorkování, operandu u uzlů násobení, indexů parametrů šablony u uzlů sběru a tak dále.
-
Trace refs (pouze s
debug=True), tedy klíče anotací a řetězce ref, které uzel propojují s původními boxy.
Kliknutí na graf a následné najíždění na jednotlivé uzly je nejrychlejší způsob, jak pochopit, co daný uzel dělá, aniž bys četl zdrojový kód.
Souhrn samplexu
Před prozkoumáním vizualizace poskytne print(samplex) rychlý textový souhrn počtu uzlů, požadovaných vstupů a slíbených výstupů.
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.
Zkoumání registrů
Předání keep_registers=True do sample() zachová mezilehlé objekty VirtualRegister, které jsou živé na konci vzorkování, a uloží je do outputs.metadata["registers"]. Každý registr je 2D pole (tvar (num_subsystems, num_randomizations), s možnými koncovými rozměry tvaru hradla) prvků virtuální grupy.
To je užitečné pro ověření, že virtuální hradla byla správně zkombinována, nebo pro zkoumání surových Pauliových či unitárních vzorků dříve, než jsou syntetizovány do úhlů rotace.
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)
Můžeš si prohlédnout obsah koncového stavu konkrétního registru. V následujícím příkladu má registr typ PauliRegister. Podrobnosti o formátu uložení najdeš v 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)
Jak se uzly samplexu mapují na parametry šablony
Pole outputs["parameter_values"] vrácené funkcí sample() má tvar (num_randomizations, N), kde N odpovídá len(template.parameters). Každý sloupec odpovídá jednomu parametru v šablonovém obvodu — i-tý sloupec vyplňuje template.parameters[i].
Uzly sběru CollectTemplateValues jsou spojnicí mezi virtuálními registry a parametry šablony. Každý takový uzel drží informace o indexech, které zaznamenávají, do kterých sloupců výstupního pole zapisuje. Díky tomu lze vysledovat, které podsystémy virtuálních hradel řídí které parametry šablony.
V průvodci {Samplex inputs and outputs}samplex-io se dozvíš, jak navázat vzorkované hodnoty parametrů na šablonový obvod a spouštět experimenty.
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]]
Automatické označování
Při použití generate_boxing_pass_manager() parametr add_tags automaticky přidá anotace Tag ke všem boxům. K dispozici jsou tři režimy:
-
"unique_instance": Přiřadí boxům v pořadí obvodu sekvenčníref(t0,t1a tak dále). Každý box dostane odlišnýrefbez ohledu na svou strukturu. -
"unique_box": Spočítá strukturální hash obsahu každého boxu a všem strukturně ekvivalentním boxům přiřadí stejnýref. To je užitečné pro seskupování boxů podle typu místo podle pozice. -
"noise_ref": Zkopírujerefz anotaceInjectNoisekaždého boxu a označí pouze boxy, které ji mají. To je užitečné, pokud smysluplné noise refs již existují.
Následující příklad použije "unique_instance" a "unique_box" na obvod, jehož dva boxy CX jsou strukturně ekvivalentní. S "unique_instance" dostane každý odlišný ref, zatímco s "unique_box" sdílejí jeden.
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
