Skip to main content
IBM Quantum Platform

Travailler avec les DAG dans les passes du transpilateur

Dans Qiskit, lors 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 dirigées qui relient des paires de sommets dans une orientation particulière. Cette représentation est stockée à l'aide d'objets qiskit.dagcircuit.DAGCircuit composés d'objets individuels DagNode . L'avantage de cette représentation par rapport à une simple liste de portes (c'est-à-dire une liste de réseaux) 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.

Ce guide montre comment travailler avec les DAG et les utiliser pour écrire des passes de transpilation personnalisées. Il commencera par la construction d'un circuit simple et l'examen de sa représentation DAG, puis explorera les opérations DAG de base et mettra en œuvre une passe BasicMapper personnalisée.


Construisez un circuit et examinez son DAG

L'extrait de code ci-dessous 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.

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

Output of the previous code cell

Dans le DAG, il y a trois types de nœuds : les nœuds d'entrée 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. Utilisez la fonction qiskit.tools.visualization.dag_drawer () pour visualiser le DAG de ce circuit. (Installez la bibliothèque Graphviz pour exécuter cette opération)

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

Output:

Output of the previous code cell

Opérations DAG de base

Les exemples de code ci-dessous illustrent des opérations courantes avec les DAG, notamment l'accès aux nœuds, l'ajout d'opérations et la substitution de sous-circuits. Ces opérations constituent la base de l'élaboration des passes de transpondeur.


Obtenir tous les nœuds d'opération dans le DAG

La méthode op_nodes() renvoie une liste itérable d'objets DAGOpNode dans le circuit :

dag.op_nodes()

Output:

[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 0x7fcbf0f00510>, 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>))]

Chaque nœud est une instance de la classe 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)

Output:

node name: if_else
op: Instruction(name='if_else', num_qubits=1, num_clbits=3, params=[<qiskit.circuit.quantumcircuit.QuantumCircuit object at 0x7fcbf0fefbd0>, 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)

Ajouter une opération à l'arrière

Une opération est ajoutée à la fin du circuit DAGCircuit à l'aide de la méthode apply_operation_back() . Cette opération ajoute la porte spécifiée pour agir sur les qubits donnés après toutes les opérations existantes dans le circuit.

from qiskit.circuit.library import HGate

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

Output:

Output of the previous code cell

Ajouter une opération à l'avant

Une opération est ajoutée au début du circuit DAGC à l'aide de la méthode apply_operation_front() . Cette opération insère la porte spécifiée avant toutes les opérations existantes dans le circuit, ce qui en fait la première opération exécutée.

from qiskit.circuit.library import CCXGate

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

Output:

Output of the previous code cell

Remplacer un nœud par un sous-circuit

Un nœud représentant une opération spécifique dans le DAGCircuit est remplacé par un sous-circuit. Tout d'abord, un nouveau sous-DAG est construit avec la séquence de portes souhaitée, puis le nœud cible est remplacé par ce sous-DAG à l'aide de substitute_node_with_dag(), en préservant les connexions avec le reste du circuit.

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:

Output of the previous code cell

Une fois toutes les transformations terminées, le DAG peut être reconverti en un objet ordinaire QuantumCircuit . C'est ainsi que fonctionne le pipeline de transposition. Un circuit est pris, traité sous forme de DAG, et un circuit transformé est produit en sortie.

from qiskit.converters import dag_to_circuit

new_circ = dag_to_circuit(dag)
circuit_drawer(new_circ, output="mpl")

Output:

Output of the previous code cell

Mettre en œuvre une passe d' BasicMapper

La structure DAG peut être exploitée pour l'écriture de passes de transposition. Dans l'exemple ci-dessous, une passe BasicMapper est mise en œuvre pour faire correspondre un circuit arbitraire à un dispositif dont la connectivité des qubits est restreinte. Pour plus d'informations, consultez le guide sur l' écriture de passes de transpilation personnalisées.

La passe est définie comme une TransformationPass, ce qui signifie qu'elle modifie le circuit. Pour ce faire, il parcourt le DAG couche par couche, en vérifiant si chaque instruction satisfait aux contraintes imposées par la carte de couplage de l'appareil. Si une violation est détectée, un chemin de permutation est déterminé et les portes SWAP nécessaires sont insérées en conséquence.

Lors de la création d'une passe de transpondeur, la première décision consiste à choisir si la passe doit hériter de TransformationPass ou de AnalysisPass. Les passes de transformation sont conçues pour modifier le circuit, tandis que les passes d'analyse ne visent qu'à extraire des informations qui seront utilisées par les passes suivantes. La fonctionnalité principale est ensuite mise en œuvre dans la méthode run(dag) . Enfin, le laissez-passer doit être enregistré dans le module qiskit.transpiler.passes .

Dans cette passe spécifique, le DAG est parcouru couche par couche (où chaque couche contient des opérations qui agissent sur des ensembles disjoints de qubits et peuvent donc être exécutées indépendamment). Pour chaque opération, si les contraintes de la carte de couplage ne sont pas respectées, un chemin de permutation approprié est identifié et les permutations nécessaires sont insérées pour mettre les qubits concernés en contiguïté.

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

La passe peut maintenant être testée sur un petit circuit d'exemple. Un gestionnaire de laissez-passer est construit avec le laissez-passer nouvellement défini. Le circuit de l'exemple est alors fourni à ce gestionnaire de passage et un nouveau circuit transformé est obtenu en sortie.

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:

Output of the previous code cell

Etapes suivantes

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