Zum Hauptinhalt springen

Mit DAGs in Transpiler-Passes arbeiten

In Qiskit werden Schaltkreise innerhalb der Transpiler-Stufen mittels eines DAG (Directed Acyclic Graph) dargestellt. Im Allgemeinen besteht ein DAG aus Knoten (auch "Vertices" genannt) und gerichteten Kanten, die Knotenpaare in einer bestimmten Orientierung verbinden. Diese Darstellung wird in qiskit.dagcircuit.DAGCircuit-Objekten gespeichert, die aus einzelnen DagNode-Objekten zusammengesetzt sind. Der Vorteil dieser Darstellung gegenüber einer reinen Liste von Gates (d.h. einer Netzliste) besteht darin, dass der Informationsfluss zwischen Operationen explizit ist, was Transformationsentscheidungen erleichtert.

Dieser Leitfaden demonstriert, wie du mit DAGs arbeitest und diese zur Entwicklung eigener Transpiler-Passes nutzt. Er beginnt mit dem Aufbau eines einfachen Schaltkreises und der Untersuchung seiner DAG-Darstellung, erkundet dann grundlegende DAG-Operationen und implementiert einen benutzerdefinierten BasicMapper-Pass.

Einen Schaltkreis erstellen und seinen DAG untersuchen

Das folgende Code-Beispiel veranschaulicht den DAG durch die Erstellung eines einfachen Schaltkreises, der einen Bell-Zustand vorbereitet und eine RZR_Z-Rotation anwendet, abhängig vom Messergebnis.

Paketversionen

Der Code auf dieser Seite wurde mit den folgenden Anforderungen entwickelt. Wir empfehlen die Verwendung dieser oder neuerer Versionen.

qiskit[all]~=2.3.0
# Added by doQumentation — required packages for this notebook
!pip install -q qiskit
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 of the previous code cell

Im DAG gibt es drei Arten von Graphknoten: Qubit-/Clbit-Eingabeknoten (grün), Operationsknoten (blau) und Ausgabeknoten (rot). Jede Kante zeigt den Datenfluss (oder die Abhängigkeit) zwischen zwei Knoten an. Verwende die Funktion qiskit.tools.visualization.dag_drawer(), um den DAG dieses Schaltkreises zu visualisieren. (Installiere dazu die Graphviz-Bibliothek.)

# Convert to DAG
dag = circuit_to_dag(circ)
dag_drawer(dag)

Output of the previous code cell

Grundlegende DAG-Operationen

Die folgenden Code-Beispiele demonstrieren gängige Operationen mit DAGs, einschließlich des Zugriffs auf Knoten, des Hinzufügens von Operationen und des Ersetzens von Teilschaltkreisen. Diese Operationen bilden die Grundlage für den Aufbau von Transpiler-Passes.

Alle Operationsknoten im DAG abrufen

Die Methode op_nodes() gibt eine iterierbare Liste von DAGOpNode-Objekten im Schaltkreis zurück:

dag.op_nodes()
[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 0x7f912f47db10>, 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)
node name: if_else
op: Instruction(name='if_else', num_qubits=1, num_clbits=3, params=[<qiskit.circuit.quantumcircuit.QuantumCircuit object at 0x7f912f4ceed0>, 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 am Ende hinzufügen

Eine Operation wird mit der Methode apply_operation_back() am Ende des DAGCircuit hinzugefügt. Dadurch wird das angegebene Gate so angefügt, dass es nach allen vorhandenen Operationen im Schaltkreis auf die angegebenen Qubits wirkt.

from qiskit.circuit.library import HGate

dag.apply_operation_back(HGate(), qargs=[q[0]])
dag_drawer(dag)

Output of the previous code cell

Eine Operation am Anfang hinzufügen

Eine Operation wird mit der Methode apply_operation_front() am Anfang des DAGCircuit hinzugefügt. Dadurch wird das angegebene Gate vor allen vorhandenen Operationen im Schaltkreis eingefügt, sodass es effektiv als erste Operation ausgeführt wird.

from qiskit.circuit.library import CCXGate

dag.apply_operation_front(CCXGate(), qargs=[q[0], q[1], q[2]])
dag_drawer(dag)

Output of the previous code cell

Einen Knoten durch einen Teilschaltkreis ersetzen

Ein Knoten, der eine bestimmte Operation im DAGCircuit repräsentiert, wird durch einen Teilschaltkreis ersetzt. Zunächst wird ein neuer Sub-DAG mit der gewünschten Abfolge von Gates erstellt, dann wird der Zielknoten mit substitute_node_with_dag() durch diesen Sub-DAG ersetzt, wobei die Verbindungen zum Rest des Schaltkreises 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 of the previous code cell

Nachdem alle Transformationen abgeschlossen sind, kann der DAG wieder in ein reguläres QuantumCircuit-Objekt umgewandelt werden. So funktioniert die Transpiler-Pipeline: Ein Schaltkreis wird entgegengenommen, 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 of the previous code cell

Einen BasicMapper-Pass implementieren

Die DAG-Struktur kann zum Schreiben von Transpiler-Passes genutzt werden. Im folgenden Beispiel wird ein BasicMapper-Pass implementiert, der einen beliebigen Schaltkreis auf ein Gerät mit eingeschränkter Qubit-Konnektivität abbildet. Weitere Hinweise findest du in der Anleitung zum Schreiben benutzerdefinierter Transpiler-Passes.

Der Pass ist als TransformationPass definiert, das heißt, er modifiziert den Schaltkreis. Er durchläuft den DAG Schicht für Schicht und prüft, ob jede Instruktion die Einschränkungen der Coupling Map des Geräts erfüllt. Wird ein Verstoß erkannt, wird ein Swap-Pfad bestimmt und die erforderlichen SWAP-Gates entsprechend eingefügt.

Beim Erstellen eines Transpiler-Passes muss zunächst entschieden werden, ob der Pass von TransformationPass oder AnalysisPass erben soll. Transformations-Passes sind dafür konzipiert, den Schaltkreis zu modifizieren, während Analyse-Passes nur Informationen für nachfolgende Passes extrahieren. Die Hauptfunktionalität wird dann in der run(dag)-Methode implementiert. Abschließend sollte der Pass im qiskit.transpiler.passes-Modul registriert werden.

In diesem speziellen Pass wird der 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). Für jede Operation wird, falls die Coupling-Map-Einschränkungen nicht erfüllt sind, ein geeigneter Swap-Pfad ermittelt und die erforderlichen Swaps 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_dag

Der Pass kann nun an einem kleinen Beispielschaltkreis getestet werden. Ein Pass-Manager wird mit dem neu definierten Pass erstellt. Der Beispielschaltkreis wird diesem Pass-Manager übergeben, und ein neuer, transformierter Schaltkreis wird als Ausgabe erhalten.

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 of the previous code cell

Nächste Schritte

Empfehlungen