Skip to main content
IBM Quantum Platform

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=True ist.
  • Trace-Informationen zu Samplex-Knoten : Wenn mit aufgerufen build() wird debug=True, enthält jeder Samplex-Knoten ein -Objekt TraceInfo , 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")
../_images/f6b6fe7b60a39ba897814008443fa79b66213005462ab09f49ffc89aab1f3668.png

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 Paaren key=value , die aus den Anmerkungen des Feldes abgeleitet wurden. Der Schlüssel tag stammt aus einer Anmerkung Tag und inject_noise stammt aus einer Anmerkung InjectNoise .

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)
../_images/e533d70a29504005a636aa27306d3ce3b1d2488358ac6a53f0674f2934698ab8.png

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
SternRotSamplingNodeErstellt neue virtuelle Register aus einer Verteilung oder einer Eingabe
KreisGrünEvaluationNodeTransformiert, kombiniert oder überträgt virtuelle Register
BowtieBlau / ViolettCollectionNodeLiest Register aus und schreibt in sample() Ausgänge

Die Durchführung erfolgt in drei Phasen:

  1. Alle Abtastknoten werden zuerst (parallel) ausgeführt.
  2. Als Nächstes werden die Auswertungsknoten in topologischer Reihenfolge ausgeführt (innerhalb jeder Generation parallel).
  3. 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_gates
lhs_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ängig ref von ihrer Struktur.
  • "unique_box": Berechnet einen strukturellen Hashwert für den Inhalt jeder Box und weist allen strukturell gleichwertigen Boxen denselben Wert zu ref . Dies ist nützlich, um Boxen nach Typ statt nach Position zu gruppieren.
  • "noise_ref": Kopiert die ref aus der Anmerkung InjectNoise jedes 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
War diese Seite hilfreich?
Melden Sie auf GitHub einen Fehler, einen Tippfehler oder fordern Sie Inhalte an.