Fehlerbehebung und Ablaufverfolgung
Die Funktion build() wandelt eine annotierte Schaltung in eine Vorlagenschaltung und ein Samplex-Paar um. In einem komplexen Schaltkreis kann es schwierig sein zu erkennen, welches Feld im ursprünglichen Schaltkreis welchen Barrieren in der Vorlage oder welchen Knoten im Samplex-DAG entspricht.
Samplomatic bietet zwei sich ergänzende Tools zur Rückverfolgung:
- Barrierenbezeichnungen : Jede Barriere im Vorlagenschaltkreis ist mit einer Bezeichnung versehen, die ihren Ursprung angibt. Diese sind immer vorhanden, unabhängig davon, ob gesetzt
debug=Trueist. - Trace-Informationen zu Samplex-Knoten : Wenn mit aufgerufen
build()wirddebug=True, enthält jeder Samplex-Knoten ein -ObjektTraceInfo, das ihn mit der Box bzw. den Boxen verknüpft, aus denen er entstanden ist.
Beide Werkzeuge sind besonders nützlich, wenn Rahmen mit einer Anmerkung Tag versehen sind, die dem Rahmen eine Zeichenkette ref zuordnet. Alternativ sind auch Quellenangaben InjectNoise beigefügt.
Tag-Felder
Eine Anmerkung Tag fügt einer Box eine Zeichenfolge ref hinzu. Das erscheint dann ref in Barriere-Bezeichnungen und in den Trace-Informationen zu Samplex-Knoten.
Die folgende Beispielschaltung umfasst drei Boxen: zwei mit „CX“ gekennzeichnete Boxen auf disjunkten Qubit-Paaren und eine nicht gekennzeichnete, rechtsgerichtete Messbox. Auch die zweite CX-Box ist mit einer InjectNoise Anmerkung versehen – sowohl ihre tag „ref“ als auch ihr „noise“ ref werden in den Barriere-Bezeichnungen erscheinen. Die beiden CX-Boxen decken unterschiedliche Qubits ab, wodurch der Samplex-Optimierer ihre Ausbreitungsknoten zu gemeinsamen Knoten zusammenführen kann. Dies wird im Abschnitt „Trace-Informationen“ angezeigt.
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")
Barriere-Etiketten in der Vorlagenschaltung
Der Aufruf build() der obigen Schaltung erzeugt eine Vorlage, deren Barrieren mit identifizierenden Bezeichnungen versehen sind. Jede Bezeichnung hat folgende Form {side}{scope}@{key=value&...}:
- Seite :
L= linke Begrenzung,M= innere Begrenzung des Box-Inhalts,R= rechte Begrenzung. - Gültigkeitsbereich : Ein ganzzahliger Index (oder eine durch Unterstriche getrennte Liste bei verschachtelten Boxen), der mehrere Boxen auf derselben Verschachtelungsebene voneinander unterscheidet.
- Anmerkungen : Eine mit „-“
@beginnende, durch „-“&getrennte Liste von Paarenkey=value, die aus den Anmerkungen des Feldes abgeleitet wurden. Der Schlüsseltagstammt aus einer AnmerkungTagundinject_noisestammt aus einer AnmerkungInjectNoise.
Barrieren aus nicht gekennzeichneten Kästchen enthalten nur die Seite und den Umfang (zum Beispiel L2), ohne Suffix @ .
template, samplex = build(circuit)
template.draw("mpl", fold=100)
Die Barriere-Bezeichnungen können auch programmgesteuert extrahiert werden:
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-Informationen zu Samplex-Knoten
Mit „Passing debug=True to“ werden jedem Samplex-Knoten Trace-Informationen build() hinzugefügt. Das Attribut trace_info jedes Knotens ist ein TraceInfo Objekt, dessen Wörterbuch trace_refs Annotationsschlüssel (zum Beispiel und "tag" "inject_noise") auf Mengen von Referenzzeichenfolgen abbildet. Knoten ohne entsprechende Box-Annotation haben trace_info=None.
Wenn in einem für Debugging erstellten Samplex aufgerufen draw() wird, wird beim Bewegen des Mauszeigers über einen beliebigen Knoten im interaktiven Diagramm dessen trace_refs im Tooltip angezeigt.
template, samplex = build(circuit, debug=True)
samplex.draw()Trace-Informationen können auch programmgesteuert überprüft werden. Beachten Sie, dass einige Knoten Verweise sowohl von 'cx_ab' als auch von enthalten 'cx_cd'. Der Samplex-Optimierer hat die parallelen Ausbreitungsknoten zusammengeführt, da die beiden Boxen disjunkte Qubits abdecken und einen gemeinsamen Vorgänger aus der Emission der rechtsgerichteten Messbox teilen.
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
Um alle Knoten zu finden, die aus einer bestimmten Box stammen, filtern Sie nach dem Schlüssel "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
Der Samplex-DAG
Das von zurückgegebene Samplex ist build() ein gerichteter azyklischer Graph (DAG), in dem die Kanten Registerabhängigkeiten darstellen. Das heißt, eine Kante vom Knoten A zum Knoten B bedeutet, dass B warten muss, bis A die gemeinsamen virtuellen Register bearbeitet hat, bevor B selbst aktiv werden darf. Hierbei handelt es sich nicht um eine zeitliche Reihenfolge wie beim DAG eines Quantenschaltkreises. Stattdessen verläuft der Graph von Knoten, die für die Erzeugung von Zufallswerten zuständig sind, zu Knoten, die diese als Ausgabe synthetisieren.
Es gibt drei Knotentypen, die in der interaktiven Darstellung jeweils einen eigenen visuellen Stil aufweisen:
Form | Farbe | Typ | Rolle |
|---|---|---|---|
| Stern | Rot | SamplingNode | Erstellt neue virtuelle Register aus einer Verteilung oder einer Eingabe |
| Kreis | Grün | EvaluationNode | Transformiert, kombiniert oder überträgt virtuelle Register |
| Bowtie | Blau / Violett | CollectionNode | Liest Register aus und schreibt in sample() Ausgänge |
Die Durchführung erfolgt in drei Phasen:
- Alle Abtastknoten werden zuerst (parallel) ausgeführt.
- Als Nächstes werden die Auswertungsknoten in topologischer Reihenfolge ausgeführt (innerhalb jeder Generation parallel).
- Die Erfassungsknoten werden zuletzt (parallel) ausgeführt.
Das Graphviz-Layout spiegelt dies wider. Die Abtastknoten werden oben angezeigt, die Erfassungsknoten unten.
Tooltips beim Darüberfahren mit der Maus
Jeder Knoten in der interaktiven Visualisierung zeigt einen Tooltip an, wenn Sie mit der Maus darüberfahren. Der Tooltip enthält folgende Informationen:
- Name der Knotenklasse und ihr ganzzahliger Graphindex.
- Registrieren Sie Manifeste, in denen die Knoten erfasst werden, die der Knoten instanziiert, aus denen er liest, in die er schreibt und die er entfernt. Diese zeigen Ihnen, wie der Datenfluss zwischen den Knoten verläuft.
- Knotenspezifische Details, darunter der Verteilungstyp für Sampling-Knoten, der Operand für Multiplikationsknoten, die Indizes der Vorlagenparameter für Sammelknoten und so weiter.
- Trace-Referenzen (nur wenn
debug=True), bei denen es sich um Annotationsschlüssel und Referenzzeichenfolgen handelt, die den Knoten wieder mit seinen Ursprungsfeldern verknüpfen.
Wenn man auf das Diagramm klickt und dann mit der Maus über einzelne Knoten fährt, lässt sich am schnellsten nachvollziehen, welche Funktion ein bestimmter Knoten hat, ohne den Quellcode lesen zu müssen.
Samplex – Zusammenfassung
Bevor die Visualisierung näher betrachtet wird, print(samplex) gibt es eine kurze textliche Zusammenfassung der Knotenanzahl, der erforderlichen Eingaben und der versprochenen Ausgaben.
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.
Register prüfen
Durch die Übergabe keep_registers=True an sample() werden die VirtualRegister Zwischenobjekte, die am Ende der Stichprobenentnahme noch aktiv sind, beibehalten und in gespeichert outputs.metadata["registers"]. Jedes Register ist ein Array vom Typ „ 2D “ (mit der Form (num_subsystems, num_randomizations), möglicherweise gefolgt von Dimensionen in Gate-Form) aus Elementen virtueller Gruppen.
Dies ist nützlich, um zu überprüfen, ob virtuelle Gatter korrekt kombiniert wurden, oder um die rohen Pauli- oder unitären Stichproben zu untersuchen, bevor sie zu Rotationswinkeln synthetisiert werden.
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)
Sie können den Inhalt des Endzustands eines bestimmten Registers anzeigen. Im folgenden Beispiel hat das Register den Typ PauliRegister. Weitere Informationen zum Speicherformat finden Sie unter API documentation(/apidocs/index)__.
print("lhs_0:", outputs.metadata["registers"]["lhs_0"])
outputs.metadata["registers"]["lhs_0"].virtual_gateslhs_0: PauliRegister(<4, 3>)
array([[0, 0, 1],
[3, 1, 1],
[3, 0, 0],
[1, 0, 0]], dtype=uint8)
Wie Samplex-Knoten den Vorlagenparametern zugeordnet werden
Das von zurückgegebene Array outputs["parameter_values"] hat sample() die Form (num_randomizations, N), wobei mit übereinstimmt N``len(template.parameters) . Jede Spalte entspricht einem Parameter in der Schaltungsvorlage – in der i-ten Spalte wird der Wert eingetragen template.parameters[i].
Die CollectTemplateValues Sammelknoten bilden die Verbindung zwischen virtuellen Registern und Vorlagenparametern. Jeder dieser Knoten enthält Indexinformationen, die festhalten, in welche Spalten des Ausgabearrays er schreibt. Auf diese Weise lässt sich nachvollziehen, welche virtuellen Gate-Subsysteme welche Vorlagenparameter steuern.
Lesen Sie den Leitfaden „ {Samplex inputs and outputs}samplex-io “, um zu erfahren, wie Sie die erfassten Parameterwerte an die Vorlagenschaltung binden und Experimente durchführen können.
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]]
Automatische Kennzeichnung
Bei Verwendung generate_boxing_pass_manager()von fügt der Parameter add_tags automatisch Tag Anmerkungen zu allen Feldern hinzu. Es stehen drei Modi zur Verfügung:
"unique_instance": Weist den Feldern in der Reihenfolge des Schaltkreises fortlaufende Nummern (ref``t0,t1, usw.) zu. Jede Box erhält eine eindeutige Kennung, unabhängigrefvon ihrer Struktur."unique_box": Berechnet einen strukturellen Hashwert für den Inhalt jeder Box und weist allen strukturell gleichwertigen Boxen denselben Wert zuref. Dies ist nützlich, um Boxen nach Typ statt nach Position zu gruppieren."noise_ref": Kopiert dierefaus der AnmerkungInjectNoisejedes Feldes und kennzeichnet nur Felder, die eine solche Anmerkung enthalten. Dies ist nützlich, wenn bereits aussagekräftige Rauschreferenzen vorliegen.
Im folgenden Beispiel werden "unique_instance" und "unique_box" auf eine Schaltung angewendet, deren beiden CX-Blöcke strukturell äquivalent sind. Bei "unique_instance" erhält jeder ein eigenes ref, während sie sich "unique_box" bei eines teilen.
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