Skip to main content
IBM Quantum Platform

Écrire une passe de transpilation personnalisée

  • Le code de cette page a été développé en tenant compte des exigences suivantes. Nous recommandons d'utiliser ces versions ou des versions plus récentes.

    qiskit[all]~=2.5.1
    

Qiskit SDK vous permet de créer des passes de transpilation personnalisées et de les exécuter dans l'objet PassManager ou de les ajouter à un objet StagedPassManager. Nous allons montrer ici comment écrire une passe de transpilation, en nous concentrant sur la construction d'une passe qui effectue un tourbillon de Pauli sur les portes quantiques bruyantes d'un circuit quantique. Cet exemple utilise le DAG, qui est l'objet manipulé par le type de passe TransformationPass .

  • Avant de construire une passe, il est important de présenter la représentation interne des circuits quantiques dans Qiskit, le graphe acyclique dirigé (DAG) (voir ce tutoriel pour une vue d'ensemble). Pour suivre ces étapes, installez la bibliothèque graphviz pour les fonctions de traçage DAG.

    Dans Qiskit, au cours des étapes de transpilation, les circuits sont représentés à l'aide d'un DAG. En général, un DAG est composé de sommets (également appelés « nœuds ») et d'arêtes orientées qui relient des paires de sommets dans une orientation particulière. Cette représentation est stockée à l'aide qiskit.dagcircuit.DAGCircuit d'objets composés d'objets individuels DagNode . L'avantage de cette représentation par rapport à une simple liste de portes (c'est-à-dire une liste d'interconnexions ) est que le flux d'informations entre les opérations est explicite, ce qui facilite la prise de décisions en matière de transformation.

    Cet exemple illustre le DAG en créant un circuit simple qui prépare un état Bell et applique une rotation RZR_Z, en fonction du résultat de la mesure.

    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')
    
    Circuit préparant un état de cloche et appliquant une rotation R_Z en fonction du résultat de la mesure.
    Circuit

    Utilisez la fonction qiskit.tools.visualization.dag_drawer() pour visualiser le DAG de ce circuit. Il existe trois types de nœuds de graphe : les nœuds de qubit/clbit (vert), les nœuds d'opération (bleu) et les nœuds de sortie (rouge). Chaque arête indique le flux de données (ou la dépendance) entre deux nœuds.

    from qiskit.converters import circuit_to_dag
    from qiskit.visualization import dag_drawer
    
    dag = circuit_to_dag(qc)
    dag_drawer(dag)
    Le DAG du circuit est constitué de nœuds reliés par des arêtes directionnelles. Il s'agit d'un moyen visuel de représenter les qubits ou les bits classiques, les opérations et la manière dont les données circulent.
    DAG

Passages du transpilateur

Les passes du transpileur sont classées soit comme AnalysisPass soit comme TransformationPass. Les passes fonctionnent généralement avec le DAG et le property_set, un objet de type dictionnaire servant à stocker les propriétés déterminées par les passes d'analyse. Les passes d'analyse fonctionnent aussi bien avec le DAG qu'avec son property_set. Ils ne peuvent pas modifier le DAG, mais peuvent modifier le property_set. Cela contraste avec les passes de transformation, qui modifient le DAG et peuvent lire (mais pas écrire) dans property_setcelui-ci. Par exemple, les passes de transformation convertissent un circuit en son architecture d'instructions (ISA) ou effectuent des passes de routage pour insérer des portes SWAP là où cela est nécessaire.


Créer une PauliTwirl passage de transpileur

L'exemple suivant construit une passe de transpilateur qui ajoute des tourbillons de Pauli. Le tournoiement de Pauli est une stratégie de suppression des erreurs qui randomise la manière dont les qubits sont confrontés aux canaux bruyants, que nous supposons être des portes à deux qubits dans cet exemple (parce qu'elles sont beaucoup plus sujettes aux erreurs que les portes à un seul qubit). Les tourbillons de Pauli n'affectent pas le fonctionnement des portes à deux qubits. Ils sont choisis de telle sorte que ceux qui sont appliqués avant la porte à deux qubits (à gauche) sont contrés par ceux qui sont appliqués après la porte à deux qubits (à droite). En ce sens, les opérations à deux qubits sont identiques, mais la manière dont elles sont effectuées est différente. L'un des avantages du tourbillon de Pauli est qu'il transforme les erreurs cohérentes en erreurs stochastiques, qui peuvent être améliorées en calculant davantage de moyennes.

Les passes Transpiler agissent sur le DAG, donc la méthode importante à surcharger est .run(), qui prend le DAG en entrée. L'initialisation des paires de Paulis comme indiqué préserve le fonctionnement de chaque porte à deux qubits. Pour ce faire, on utilise la méthode d'aide build_twirl_set, qui passe en revue chaque Pauli à deux qubits (tel qu'obtenu à partir de pauli_basis(2)) et trouve l'autre Pauli qui préserve l'opération.

À partir du DAG, utilisez la méthode op_nodes() pour retourner tous ses nœuds. Le DAG peut également être utilisé pour collecter des runs, qui sont des séquences de nœuds qui s'exécutent de manière ininterrompue sur un qubit. Elles peuvent être rassemblées sous forme d'exécutions de qubits simples avec collect_1q_runs, d'exécutions de qubits doubles avec collect_2q_runs, et d'exécutions de nœuds dont les noms d'instructions figurent dans une liste de noms avec collect_runs. Le site DAGCircuit propose de nombreuses méthodes pour rechercher et parcourir un graphe. Une méthode couramment utilisée est topological_op_nodes, qui fournit les nœuds dans un ordre de dépendance. D'autres méthodes, telles que bfs_successors , sont utilisées principalement pour déterminer comment les nœuds interagissent avec les opérations ultérieures sur un DAG.

Dans l'exemple, nous voulons remplacer chaque nœud, représentant une instruction, par un sous-circuit construit comme un mini DAG. Un registre quantique à deux qubits est ajouté au mini DAG. Les opérations sont ajoutées au mini DAG en utilisant apply_operation_back, qui place Instruction sur la sortie du mini DAG (alors que apply_operation_front le placerait sur l'entrée du mini DAG). Le nœud est ensuite remplacé par le mini DAG en utilisant substitute_node_with_dag, et le processus se poursuit pour chaque instance de CXGate et ECRGate dans le DAG (correspondant aux portes de base à deux qubits sur les backends IBM® ).

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

Utilisez le PauliTwirl passage du transpiler

Le code suivant utilise le passe créé ci-dessus pour transcompiler un circuit. Considérons un circuit simple avec des portes cx``ecr et.

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

Pour appliquer le laissez-passer personnalisé, construisez un gestionnaire de laissez-passer en utilisant le laissez-passer PauliTwirl et faites-le fonctionner sur 50 circuits.

pm = PassManager([PauliTwirl()])
twirled_qcs = [pm.run(qc) for _ in range(50)]

Chaque porte à deux qubits est maintenant prise en sandwich entre deux Paulis.

twirled_qcs[-1].draw("mpl")

Output:

Output of the previous code cell

Les opérateurs sont les mêmes si l'on utilise Operator de qiskit.quantum_info :

np.all([Operator(twirled_qc).equiv(qc) for twirled_qc in twirled_qcs])

Output:

np.True_

Etapes suivantes

Recommandations
Cette page a-t-elle été utile ?
Signaler un bogue, une coquille ou proposer du contenu sur GitHub.