Arbeiten mit DAGs in Transpiler-Durchläufen
In Qiskit werden die Schaltkreise in den Umsetzungsphasen durch ein DAG dargestellt. Im Allgemeinen besteht ein DAG aus Knoten (auch "Nodes" genannt) und gerichteten Kanten, die Paare von Knoten in einer bestimmten Ausrichtung verbinden. Diese Darstellung wird über qiskit.dagcircuit.DAGCircuit Objekte gespeichert, die aus einzelnen DagNode Objekten bestehen. Der Vorteil dieser Darstellung gegenüber einer reinen Liste von Gattern (d. h. einer Netzliste) besteht darin, dass der Informationsfluss zwischen den Operationen explizit ist, was es einfacher macht, Transformationsentscheidungen zu treffen.
Dieser Leitfaden zeigt, wie man mit DAGs arbeitet und sie zum Schreiben von benutzerdefinierten Transpilerübergängen verwendet. Er beginnt mit dem Aufbau einer einfachen Schaltung und der Untersuchung ihrer DAG-Darstellung, erforscht dann grundlegende DAG-Operationen und implementiert einen benutzerdefinierten BasicMapper -Durchlauf.
Eine Schaltung aufbauen und ihren DAG untersuchen
Der folgende Codeschnipsel veranschaulicht die DAG, indem er eine einfache Schaltung erstellt, die einen Bell-Zustand vorbereitet und je nach Messergebnis eine Drehung anwendet.
Der Code auf dieser Seite wurde unter Berücksichtigung der folgenden Anforderungen entwickelt. Wir empfehlen die Verwendung dieser oder neuerer Versionen.
qiskit[all]~=2.5.2
from qiskit import QuantumRegister, ClassicalRegister, QuantumCircuit
from qiskit.converters import circuit_to_dag
from qiskit.visualization import circuit_drawer
from qiskit.visualization.dag_visualization import dag_drawer
# Create circuit
q = QuantumRegister(3, "q")
c = ClassicalRegister(3, "c")
circ = QuantumCircuit(q, c)
circ.h(q[0])
circ.cx(q[0], q[1])
circ.measure(q[0], c[0])
# Qiskit 2.0 uses if_test instead of c_if
with circ.if_test((c, 2)):
circ.rz(0.5, q[1])
circuit_drawer(circ, output="mpl")Output:
In der DAG gibt es drei Arten von Graphknoten: Qubit/Clbit-Eingangsknoten (grün), Operationsknoten (blau) und Ausgangsknoten (rot). Jede Kante zeigt den Datenfluss (oder die Abhängigkeit) zwischen zwei Knoten an. Verwenden Sie die Funktion qiskit.tools.visualization.dag_drawer (), um die DAG dieser Schaltung anzuzeigen. (Installieren Sie die Graphviz-Bibliothek, um dies auszuführen.)
# Convert to DAG
dag = circuit_to_dag(circ)
dag_drawer(dag)Output:
Grundlegende DAG-Operationen
Die folgenden Code-Beispiele demonstrieren gängige Operationen mit DAGs, einschließlich des Zugriffs auf Knoten, Hinzufügen von Operationen und Ersetzen von Teilschaltungen. Diese Operationen bilden die Grundlage für die Erstellung von Transpilerpässen.
Alle Betriebsknoten im DAG abrufen
Die Methode op_nodes() gibt eine iterierbare Liste der DAGOpNode Objekte im Stromkreis zurück:
dag.op_nodes()Output:
[DAGOpNode(op=Instruction(name='h', num_qubits=1, num_clbits=0, params=[]), qargs=(<Qubit register=(3, "q"), index=0>,), cargs=()),
DAGOpNode(op=Instruction(name='cx', num_qubits=2, num_clbits=0, params=[]), qargs=(<Qubit register=(3, "q"), index=0>, <Qubit register=(3, "q"), index=1>), cargs=()),
DAGOpNode(op=Instruction(name='measure', num_qubits=1, num_clbits=1, params=[]), qargs=(<Qubit register=(3, "q"), index=0>,), cargs=(<Clbit register=(3, "c"), index=0>,)),
DAGOpNode(op=Instruction(name='if_else', num_qubits=1, num_clbits=3, params=[<qiskit.circuit.quantumcircuit.QuantumCircuit object at 0x12559c410>, None]), qargs=(<Qubit register=(3, "q"), index=1>,), cargs=(<Clbit register=(3, "c"), index=0>, <Clbit register=(3, "c"), index=1>, <Clbit register=(3, "c"), index=2>))]
Jeder Knoten ist eine Instanz der Klasse DAGOpNode :
node = dag.op_nodes()[3]
print("node name:", node.name)
print("op:", node.op)
print("qargs:", node.qargs)
print("cargs:", node.cargs)
print("condition:", node.op.condition)Output:
node name: if_else
op: Instruction(name='if_else', num_qubits=1, num_clbits=3, params=[<qiskit.circuit.quantumcircuit.QuantumCircuit object at 0x12559f490>, None])
qargs: (<Qubit register=(3, "q"), index=1>,)
cargs: (<Clbit register=(3, "c"), index=0>, <Clbit register=(3, "c"), index=1>, <Clbit register=(3, "c"), index=2>)
condition: (ClassicalRegister(3, 'c'), 2)
Eine Operation hinzufügen
Am Ende des DAGCircuit wird mit der Methode apply_operation_back() ein Vorgang hinzugefügt. Damit wird das angegebene Gatter an die angegebenen Qubits angehängt, um nach allen bestehenden Operationen in der Schaltung zu wirken.
from qiskit.circuit.library import HGate
dag.apply_operation_back(HGate(), qargs=[q[0]])
dag_drawer(dag)Output:
Eine Operation an den Anfang hinzufügen
Am Anfang des DAGCircuit wird mit der Methode apply_operation_front() eine Operation hinzugefügt. Dadurch wird das angegebene Gatter vor allen bestehenden Operationen in der Schaltung eingefügt, so dass es effektiv die erste ausgeführte Operation ist.
from qiskit.circuit.library import CCXGate
dag.apply_operation_front(CCXGate(), qargs=[q[0], q[1], q[2]])
dag_drawer(dag)Output:
Ersetzen Sie einen Knoten durch eine Teilschaltung
Ein Knoten, der eine bestimmte Operation im DAGCircuit repräsentiert, wird durch einen Subcircuit ersetzt. Zunächst wird eine neue Unter-DAG mit der gewünschten Folge von Gattern konstruiert, dann wird der Zielknoten mit Hilfe von substitute_node_with_dag() durch diese Unter-DAG ersetzt, wobei die Verbindungen zum Rest der Schaltung erhalten bleiben.
from qiskit.dagcircuit import DAGCircuit
from qiskit.circuit.library import CHGate, U2Gate, CXGate
# Build sub-DAG
mini_dag = DAGCircuit()
p = QuantumRegister(2, "p")
mini_dag.add_qreg(p)
mini_dag.apply_operation_back(CHGate(), qargs=[p[1], p[0]])
mini_dag.apply_operation_back(U2Gate(0.1, 0.2), qargs=[p[1]])
# Replace CX with mini_dag
cx_node = dag.op_nodes(op=CXGate).pop()
dag.substitute_node_with_dag(cx_node, mini_dag, wires=[p[0], p[1]])
dag_drawer(dag)Output:
Nachdem alle Transformationen abgeschlossen sind, kann die DAG wieder in ein reguläres QuantumCircuit Objekt umgewandelt werden. So funktioniert die Transpiler-Pipeline. Ein Schaltkreis wird genommen, in DAG-Form verarbeitet und ein transformierter Schaltkreis wird als Ausgabe erzeugt.
from qiskit.converters import dag_to_circuit
new_circ = dag_to_circuit(dag)
circuit_drawer(new_circ, output="mpl")Output:
BasicMapper -Pass implementieren
Die DAG-Struktur kann für das Schreiben von Transpilerübergängen genutzt werden. Im folgenden Beispiel wird ein BasicMapper Durchgang implementiert, um eine beliebige Schaltung auf ein Gerät mit eingeschränkter Qubit-Konnektivität abzubilden. Weitere Anleitungen finden Sie im Leitfaden zum Schreiben benutzerdefinierter Transpilerübergänge.
Der Pass ist als TransformationPass definiert, was bedeutet, dass er den Stromkreis verändert. Dazu wird die DAG Schicht für Schicht durchlaufen und geprüft, ob jede Anweisung die von der Kopplungskarte des Geräts auferlegten Beschränkungen erfüllt. Wird ein Verstoß festgestellt, wird ein Swap-Pfad ermittelt und die erforderlichen SWAP-Gates werden entsprechend eingefügt.
Bei der Erstellung eines Transpiler-Passes muss zunächst entschieden werden, ob der Pass von TransformationPass oder AnalysisPass erben soll. Transformationsdurchgänge dienen dazu, die Schaltung zu verändern, während Analysedurchgänge nur dazu dienen, Informationen zu extrahieren, die von nachfolgenden Durchgängen verwendet werden können. Die Hauptfunktionalität wird dann in der Methode run(dag) implementiert. Schließlich sollte der Pass im Modul qiskit.transpiler.passes registriert werden.
In diesem speziellen Durchgang wird die DAG Schicht für Schicht durchlaufen (wobei jede Schicht Operationen enthält, die auf disjunkte Mengen von Qubits wirken und daher unabhängig voneinander ausgeführt werden können). Wenn die Bedingungen der Kopplungskarte nicht erfüllt sind, wird für jede Operation ein geeigneter Tauschpfad ermittelt, und die erforderlichen Tauschvorgänge werden eingefügt, um die beteiligten Qubits in Nachbarschaft zu bringen.
from qiskit.transpiler.basepasses import TransformationPass
from qiskit.transpiler import Layout
from qiskit.circuit.library import SwapGate
class BasicSwap(TransformationPass):
def __init__(self, coupling_map, initial_layout=None):
super().__init__()
self.coupling_map = coupling_map
self.initial_layout = initial_layout
def run(self, dag):
new_dag = DAGCircuit()
for qreg in dag.qregs.values():
new_dag.add_qreg(qreg)
for creg in dag.cregs.values():
new_dag.add_creg(creg)
if self.initial_layout is None:
self.initial_layout = Layout.generate_trivial_layout(
*dag.qregs.values()
)
current_layout = self.initial_layout.copy()
for layer in dag.serial_layers():
subdag = layer["graph"]
for gate in subdag.two_qubit_ops():
q0, q1 = gate.qargs
p0 = current_layout[q0]
p1 = current_layout[q1]
if self.coupling_map.distance(p0, p1) != 1:
path = self.coupling_map.shortest_undirected_path(p0, p1)
for i in range(len(path) - 2):
wire1, wire2 = path[i], path[i + 1]
qubit1 = current_layout[wire1]
qubit2 = current_layout[wire2]
new_dag.apply_operation_back(
SwapGate(), qargs=[qubit1, qubit2]
)
current_layout.swap(wire1, wire2)
new_dag.compose(
subdag, qubits=current_layout.reorder_bits(new_dag.qubits)
)
return new_dagDer Durchgang kann nun an einer kleinen Beispielschaltung getestet werden. Es wird ein Passmanager erstellt, der den neu definierten Pass enthält. Die Beispielschaltung wird dann an diesen Passmanager weitergegeben, und als Ergebnis erhält man eine neue, transformierte Schaltung.
from qiskit.transpiler import CouplingMap, PassManager
from qiskit import QuantumRegister, QuantumCircuit
q = QuantumRegister(7, "q")
in_circ = QuantumCircuit(q)
in_circ.h(q[0])
in_circ.cx(q[0], q[4])
in_circ.cx(q[2], q[3])
in_circ.cx(q[6], q[1])
in_circ.cx(q[5], q[0])
in_circ.rz(0.1, q[2])
in_circ.cx(q[5], q[0])
coupling = [[0, 1], [1, 2], [2, 3], [3, 4], [4, 5], [5, 6]]
coupling_map = CouplingMap(couplinglist=coupling)
pm = PassManager()
pm.append(BasicSwap(coupling_map))
out_circ = pm.run(in_circ)
in_circ.draw(output="mpl")
out_circ.draw(output="mpl")Output:
Nächste Schritte
- Lesen Sie die Anleitung zum Erstellen eines benutzerdefinierten Transpiler-Durchlaufs
- Erfahren Sie, wie Sie Projekte erstellen und für benutzerdefinierte Backends transpilieren
- Probieren Sie die Anleitung zum Vergleichen der Transpiler-Einstellungen aus.
- Lesen Sie die Dokumentation zur DAG Circuit API.