Zum Hauptinhalt springen
Dieses Projekt befindet sich in der Beta-Phase. Einen Fehler gefunden? GitHub Issue erstellen. Frage oder Idee? In Discussions fragen — wir freuen uns über dein Feedback!
Wie ist die Übersetzungsqualität auf dieser Seite?

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.

Diese Anleitung demonstriert, wie du mit DAGs arbeitest und diese zur Entwicklung eigener Transpiler-Passes nutzt. Sie 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 präpariert und eine RZR_Z-Rotation anwendet, abhängig vom Messergebnis.

Package versions

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

qiskit[all]~=2.5.2
# Added by doQumentation — installs the packages this notebook needs if they are missing
import importlib.util

_needed = {"qiskit": "qiskit"}
_missing = [pip for module, pip in _needed.items()
if importlib.util.find_spec(module) is None]
# One at a time, so a package that fails to install does not block the others
for _pip in _missing:
%pip install -q {_pip}
if not _missing:
print("\u2713 All required packages are installed")
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 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)
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 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 werden 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, deren Coupling-Map-Einschränkungen nicht erfüllt sind, wird ein geeigneter Swap-Pfad ermittelt, und es werden die erforderlichen Swaps eingefügt, um die beteiligten Qubits benachbart zu machen.

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 du erhältst als Ausgabe einen neuen, transformierten Schaltkreis.

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