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 , 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:
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:
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:
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:
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:
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:
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_dagLa 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:
Etapes suivantes
- Consultez le guide sur la création d'un passage de transcompilation personnalisé
- Découvrez comment créer et transcompiler pour des backends personnalisés
- Consultez le guide Comparer les paramètres du transpiler.
- Consultez la documentation relative à l'API DAG Circuit.