Skip to main content
IBM Quantum Platform

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 graphviz fü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.DAGCircuit Objekten gespeichert, die sich aus einzelnen DagNode Objekten 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 RZR_Z 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 zur Vorbereitung eines Bell-Zustands und zur Anwendung einer R_Z-Drehung in Abhängigkeit vom Messergebnis.
    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)
    Die DAG der Schaltung besteht aus Knoten, die durch gerichtete Kanten verbunden sind. Es handelt sich um eine visuelle Darstellung von Qubits oder klassischen Bits, den Operationen und der Art und Weise, wie Daten fließen.
    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, Optional
class 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 dag

Verwenden 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:

Output of the previous code cell

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:

Output of the previous code cell

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

Empfehlungen
War diese Seite hilfreich?
Melden Sie auf GitHub einen Fehler, einen Tippfehler oder fordern Sie Inhalte an.