Trabajar con DAGs en pases del transpilador
En Qiskit, dentro de las etapas de transpilación, los circuitos se representan usando un DAG. En general, un DAG está compuesto por vértices (también conocidos como "nodos") y aristas dirigidas que conectan pares de vértices en una orientación particular. Esta representación se almacena utilizando objetos qiskit.dagcircuit.DAGCircuit que están compuestos por objetos DagNode individuales. La ventaja de esta representación frente a una lista pura de puertas (es decir, un netlist) es que el flujo de información entre operaciones es explícito, lo que facilita la toma de decisiones de transformación.
Esta guía demuestra cómo trabajar con DAGs y usarlos para escribir pases del transpilador personalizados. Comenzará construyendo un circuito simple y examinando su representación DAG, luego explorará operaciones básicas de DAG e implementarás un pase BasicMapper personalizado.
Construir un circuito y examinar su DAG
El siguiente fragmento de código ilustra el DAG creando un circuito simple que prepara un estado de Bell y aplica una rotación , dependiendo del resultado de la medición.
Versiones de paquetes
El código de esta página se desarrolló utilizando los siguientes requisitos. Recomendamos usar estas versiones o más recientes.
qiskit[all]~=2.3.0
# Added by doQumentation — required packages for this notebook
!pip install -q qiskit
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")
En el DAG, hay tres tipos de nodos de grafo: nodos de entrada de qubit/clbit (verdes), nodos de operación (azules) y nodos de salida (rojos). Cada arista indica el flujo de datos (o dependencia) entre dos nodos. Usa la función qiskit.tools.visualization.dag_drawer() para ver el DAG de este circuito. (Instala la biblioteca Graphviz para ejecutar esto).
# Convert to DAG
dag = circuit_to_dag(circ)
dag_drawer(dag)

Operaciones básicas con DAGs
Los ejemplos de código a continuación demuestran operaciones comunes con DAGs, incluyendo el acceso a nodos, la adición de operaciones y la sustitución de subcircuitos. Estas operaciones forman la base para construir pases del transpilador.
Obtener todos los nodos de operación en el DAG
El método op_nodes() devuelve una lista iterable de objetos DAGOpNode en el circuito:
dag.op_nodes()
[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 0x7f912f47db10>, 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>))]
Cada nodo es una instancia de la clase 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)
node name: if_else
op: Instruction(name='if_else', num_qubits=1, num_clbits=3, params=[<qiskit.circuit.quantumcircuit.QuantumCircuit object at 0x7f912f4ceed0>, 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)
Añadir una operación al final
Se añade una operación al final del DAGCircuit usando el método apply_operation_back(). Esto añade la puerta especificada para que actúe sobre los qubits dados después de todas las operaciones existentes en el circuito.
from qiskit.circuit.library import HGate
dag.apply_operation_back(HGate(), qargs=[q[0]])
dag_drawer(dag)

Añadir una operación al principio
Se añade una operación al principio del DAGCircuit usando el método apply_operation_front(). Esto inserta la puerta especificada antes de todas las operaciones existentes en el circuito, convirtiéndola efectivamente en la primera operación ejecutada.
from qiskit.circuit.library import CCXGate
dag.apply_operation_front(CCXGate(), qargs=[q[0], q[1], q[2]])
dag_drawer(dag)

Sustituir un nodo con un subcircuito
Un nodo que representa una operación específica en el DAGCircuit es reemplazado por un subcircuito. Primero, se construye un nuevo sub-DAG con la secuencia deseada de puertas, luego el nodo objetivo es sustituido por este sub-DAG usando substitute_node_with_dag(), preservando las conexiones con el resto del circuito.
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)

Después de completar todas las transformaciones, el DAG puede convertirse nuevamente en un objeto QuantumCircuit regular. Así es como opera la canalización del transpilador. Se toma un circuito, se procesa en forma de DAG y se produce un circuito transformado como salida.
from qiskit.converters import dag_to_circuit
new_circ = dag_to_circuit(dag)
circuit_drawer(new_circ, output="mpl")
Implementar un pase BasicMapper
La estructura del DAG se puede aprovechar para escribir pases del transpilador. En el siguiente ejemplo, se implementa un pase BasicMapper para asignar un circuito arbitrario a un dispositivo con conectividad de qubits restringida. Para obtener orientación adicional, consulta la guía sobre cómo escribir pases del transpilador personalizados.
El pase se define como un TransformationPass, lo que significa que modifica el circuito. Lo hace recorriendo el DAG capa por capa, verificando si cada instrucción satisface las restricciones impuestas por el mapa de acoplamiento del dispositivo. Si se detecta una violación, se determina una ruta de intercambio y se insertan las puertas SWAP necesarias correspondientemente.
Al crear un pase del transpilador, la primera decisión implica seleccionar si el pase debe heredar de TransformationPass o AnalysisPass. Los pases de transformación están diseñados para modificar el circuito, mientras que los pases de análisis están destinados solo a extraer información para su uso por pases subsiguientes. La funcionalidad principal se implementa luego en el método run(dag). Finalmente, el pase debe registrarse dentro del módulo qiskit.transpiler.passes.
En este pase específico, el DAG se recorre capa por capa (donde cada capa contiene operaciones que actúan sobre conjuntos disjuntos de qubits y, por lo tanto, pueden ejecutarse de forma independiente). Para cada operación, si no se cumplen las restricciones del mapa de acoplamiento, se identifica una ruta de intercambio adecuada y se insertan los intercambios requeridos para llevar los qubits involucrados a la adyacencia.
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
El pase ahora puede probarse en un circuito de ejemplo pequeño. Se construye un gestor de pases con el nuevo pase definido incluido. El circuito de ejemplo se proporciona luego a este gestor de pases, y se obtiene un nuevo circuito transformado como salida.
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")
Siguientes pasos
- Revisa la guía sobre cómo crear un pase del transpilador personalizado
- Aprende a crear y transpilar contra backends personalizados
- Prueba la guía Comparar configuraciones del transpilador.
- Revisa la documentación de la API de DAG Circuit.