Einen benutzerdefinierten Transpiler-Pass schreiben
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
Mit dem Qiskit SDK können Sie benutzerdefinierte Transpilationspässe erstellen und sie im PassManager Objekt ausführen oder einem StagedPassManager hinzufügen. Hier wird demonstriert, wie man einen Transpiler-Pass schreibt, wobei der Schwerpunkt auf der Erstellung eines Passes liegt, der Pauli-Twirling an den verrauschten Quantengattern in einer Quantenschaltung durchführt. In diesem Beispiel wird die DAG verwendet, das Objekt, das durch den Durchgangstyp TransformationPass bearbeitet wird.
Bevor Sie einen Durchlauf bauen, ist es wichtig, die interne Darstellung von Quantenschaltungen in Qiskit, den gerichteten azyklischen Graphen (DAG), vorzustellen (siehe dieses Tutorial für einen Überblick). Um diese Schritte auszuführen, installieren Sie die Bibliothek
graphvizfür die DAG-Darstellungsfunktionen.In Qiskit werden Schaltungen innerhalb der Transpilierungsphasen mithilfe eines DAG dargestellt. Im Allgemeinen besteht ein DAG aus Knoten (auch als „Nodes“ bezeichnet) und gerichteten Kanten, die Paare von Knoten in einer bestimmten Ausrichtung verbinden. Diese Darstellung wird mithilfe von
qiskit.dagcircuit.DAGCircuitObjekten gespeichert, die sich aus einzelnenDagNodeObjekten zusammensetzen. 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 die Entscheidungsfindung bei der Transformation erleichtert.In diesem Beispiel wird die DAG durch eine einfache Schaltung veranschaulicht, die einen Bell-Zustand vorbereitet und je nach Messergebnis eine Drehung vornimmt.
from qiskit import QuantumRegister, ClassicalRegister, QuantumCircuit import numpy as np qr = QuantumRegister(3, 'qr') cr = ClassicalRegister(3, 'cr') qc = QuantumCircuit(qr, cr) qc.h(qr[0]) qc.cx(qr[0], qr[1]) qc.measure(qr[0], cr[0]) with qc.if_test((cr, 2)): qc.rz(np.pi/2, qr[1]) qc.draw(output='mpl')
Schaltung Verwenden Sie die Funktion
qiskit.tools.visualization.dag_drawer(), um die DAG dieser Schaltung anzuzeigen. Es gibt drei Arten von Graphknoten: Qubit/Clbit-Knoten (grün), Operationsknoten (blau) und Ausgabeknoten (rot). Jede Kante zeigt den Datenfluss (oder die Abhängigkeit) zwischen zwei Knoten an.from qiskit.converters import circuit_to_dag from qiskit.visualization import dag_drawer dag = circuit_to_dag(qc) dag_drawer(dag)
DAG
Transpiler-Durchläufe
Transpiler-Durchläufe werden entweder als oder AnalysisPass als klassifiziert TransformationPass. Pässe arbeiten im Allgemeinen mit dem DAG und dem zusammen property_set, einem Wörterbuch-ähnlichen Objekt zum Speichern von Eigenschaften, die durch Analyse-Pässe bestimmt werden. Analyse-Passes funktionieren sowohl mit dem DAG als auch mit dessen property_set. Sie können die DAG nicht ändern, aber sie können die property_set. Im Gegensatz dazu verändern Transformationsdurchläufe den DAG und können darauf zugreifen (jedoch keine Änderungen vornehmen) property_set. Beispielsweise wandeln Transformationsdurchläufe eine Schaltung in ihre ISA um oder führen Routing-Durchläufe durch, um an den erforderlichen Stellen SWAP-Gatter einzufügen.
Erstellen Sie einen PauliTwirl Transpiler-Durchlauf
Das folgende Beispiel konstruiert einen Transpiler-Durchgang, der Pauli-Wirbel hinzufügt. Pauli-Twirling ist eine Strategie zur Fehlerunterdrückung, die zufällig bestimmt, wie die Qubits verrauschte Kanäle wahrnehmen. In diesem Beispiel nehmen wir an, dass es sich dabei um Zwei-Qubit-Gatter handelt (weil diese viel fehleranfälliger sind als Ein-Qubit-Gatter). Die Pauli-Wirbel haben keinen Einfluss auf die Funktion der Zwei-Qubit-Tore. Sie sind so gewählt, dass diejenigen, die vor dem Zwei-Qubit-Gatter (links) angewendet werden, durch diejenigen, die nach dem Zwei-Qubit-Gatter (rechts) angewendet werden, ausgeglichen werden. In diesem Sinne sind die Zwei-Qubit-Operationen identisch, aber die Art und Weise, wie sie durchgeführt werden, ist unterschiedlich. Ein Vorteil des Pauli-Twirlings ist, dass es kohärente Fehler in stochastische Fehler umwandelt, die durch eine stärkere Mittelwertbildung verbessert werden können.
Die Transpiler-Passagen wirken auf die DAG, so dass die wichtigste Methode, die überschrieben werden muss, .run() ist, die die DAG als Eingabe erhält. Bei der Initialisierung von Paulis-Paaren bleibt die Funktion jedes Zwei-Qubit-Gatters erhalten. Dies geschieht mit der Hilfsmethode build_twirl_set, die jedes Zwei-Qubit-Pauli (wie von pauli_basis(2) erhalten) durchgeht und das andere Pauli findet, das die Operation bewahrt.
Verwenden Sie in der DAG die Methode op_nodes() , um alle ihre Knoten zurückzugeben. Die DAG kann auch verwendet werden, um Läufe zu sammeln, d. h. Sequenzen von Knoten, die ohne Unterbrechung auf einem Qubit ablaufen. Diese können als Ein-Qubit-Läufe mit collect_1q_runs, Zwei-Qubit-Läufe mit collect_2q_runs und Läufe von Knoten, bei denen die Befehlsnamen in einer Namensliste stehen, mit collect_runs gesammelt werden. DAGCircuit verfügt über zahlreiche Methoden zum Suchen und Durchlaufen eines Graphen. Eine häufig verwendete Methode ist topological_op_nodes, die die Knoten in einer Abhängigkeitsordnung bereitstellt. Andere Methoden wie bfs_successors werden in erster Linie verwendet, um zu bestimmen, wie Knoten mit nachfolgenden Operationen auf einer DAG interagieren.
In unserem Beispiel wollen wir jeden Knoten, der eine Anweisung darstellt, durch einen Teilschaltkreis ersetzen, der als Mini-DAG aufgebaut ist. Der Mini-DAG wurde um ein Zwei-Qubit-Quantenregister erweitert. Die Operationen werden dem Mini-DAG durch die Verwendung von apply_operation_back hinzugefügt, wodurch Instruction auf dem Ausgang des Mini-DAG platziert wird (während apply_operation_front auf dem Eingang des Mini-DAG platziert würde). Der Knoten wird dann durch die Mini-DAG ersetzt, indem substitute_node_with_dag verwendet wird, und der Prozess wird über jede Instanz von CXGate und ECRGate in der DAG fortgesetzt (entsprechend den Zwei-Qubit-Basisgattern auf IBM® Backends).
from qiskit.dagcircuit import DAGCircuit
from qiskit.circuit import QuantumCircuit, QuantumRegister, Gate
from qiskit.circuit.library import CXGate, ECRGate
from qiskit.transpiler import PassManager
from qiskit.transpiler.basepasses import TransformationPass
from qiskit.quantum_info import Operator, pauli_basis
import numpy as np
from typing import Iterable, Optionalclass PauliTwirl(TransformationPass):
"""Add Pauli twirls to two-qubit gates."""
def __init__(
self,
gates_to_twirl: Optional[Iterable[Gate]] = None,
):
"""
Args:
gates_to_twirl: Names of gates to twirl. The default behavior is to twirl all
two-qubit basis gates, `cx` and `ecr` for IBM backends.
"""
if gates_to_twirl is None:
gates_to_twirl = [CXGate(), ECRGate()]
self.gates_to_twirl = gates_to_twirl
self.build_twirl_set()
super().__init__()
def build_twirl_set(self):
"""
Build a set of Paulis to twirl for each gate and store internally as .twirl_set.
"""
self.twirl_set = {}
# iterate through gates to be twirled
for twirl_gate in self.gates_to_twirl:
twirl_list = []
# iterate through Paulis on left of gate to twirl
for pauli_left in pauli_basis(2):
# iterate through Paulis on right of gate to twirl
for pauli_right in pauli_basis(2):
# save pairs that produce identical operation as gate to twirl
if (Operator(pauli_left) @ Operator(twirl_gate)).equiv(
Operator(twirl_gate) @ pauli_right
):
twirl_list.append((pauli_left, pauli_right))
self.twirl_set[twirl_gate.name] = twirl_list
def run(
self,
dag: DAGCircuit,
) -> DAGCircuit:
# collect all nodes in DAG and proceed if it is to be twirled
twirling_gate_classes = tuple(
gate.base_class for gate in self.gates_to_twirl
)
for node in dag.op_nodes():
if not isinstance(node.op, twirling_gate_classes):
continue
# random integer to select Pauli twirl pair
pauli_index = np.random.randint(
0, len(self.twirl_set[node.op.name])
)
twirl_pair = self.twirl_set[node.op.name][pauli_index]
# instantiate mini_dag and attach quantum register
mini_dag = DAGCircuit()
register = QuantumRegister(2)
mini_dag.add_qreg(register)
# apply left Pauli, gate to twirl, and right Pauli to empty mini-DAG
mini_dag.apply_operation_back(
twirl_pair[0].to_instruction(), [register[0], register[1]]
)
mini_dag.apply_operation_back(node.op, [register[0], register[1]])
mini_dag.apply_operation_back(
twirl_pair[1].to_instruction(), [register[0], register[1]]
)
# substitute gate to twirl node with twirling mini-DAG
dag.substitute_node_with_dag(node, mini_dag)
return dagVerwenden Sie den PauliTwirl Transpiler-Durchlauf
Der folgende Code verwendet den oben erstellten Pass, um eine Schaltung zu transpilieren. Betrachten Sie eine einfache Schaltung mit cx und ecr Gattern.
qc = QuantumCircuit(3)
qc.cx(0, 1)
qc.ecr(1, 2)
qc.ecr(1, 0)
qc.cx(2, 1)
qc.draw("mpl")Output:
Um den benutzerdefinierten Durchgang anzuwenden, erstellen Sie einen Durchgangsmanager mit dem Durchgang PauliTwirl und lassen Sie ihn auf 50 Stromkreisen laufen.
pm = PassManager([PauliTwirl()])
twirled_qcs = [pm.run(qc) for _ in range(50)]Jedes Zwei-Qubit-Gate ist nun zwischen zwei Paulis eingebettet.
twirled_qcs[-1].draw("mpl")Output:
Die Operatoren sind dieselben, wenn Operator von qiskit.quantum_info verwendet wird:
np.all([Operator(twirled_qc).equiv(qc) for twirled_qc in twirled_qcs])Output:
np.True_
Nächste Schritte
- Wenn Sie wissen möchten, wie Sie die Funktion
generate_preset_passmanagerverwenden können, anstatt Ihre eigenen Pässe zu schreiben, beginnen Sie mit dem Thema Transpilationsstandardeinstellungen und Konfigurationsoptionen. - Probieren Sie die Anleitung zum Vergleichen der Transpiler-Einstellungen aus.
- Lesen Sie die Dokumentation zur Transpiler-API.