Trabajar con DAG en pasadas del transpilador
En Qiskit, dentro de las etapas de transpilación, los circuitos se representan mediante un DAG. En general, un DAG se compone de vértices (también conocidos como "nodos") y aristas dirigidas que conectan pares de vértices en una orientación determinada. Esta representación se almacena mediante objetos qiskit.dagcircuit.DAGCircuit que se componen de objetos DagNode individuales. La ventaja de esta representación frente a una lista 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 muestra cómo trabajar con DAGs y utilizarlos para escribir pases de transpilador personalizados. Comenzará construyendo un circuito sencillo y examinando su representación DAG, después explorará las operaciones DAG básicas e implementará un pase personalizado BasicMapper .
Construye un circuito y examina su DAG
El siguiente fragmento de código ilustra el DAG mediante la creación de un circuito sencillo que prepara un estado Bell y aplica una rotación , en función del resultado de la medición.
El código de esta página se ha desarrollado teniendo en cuenta los siguientes requisitos. Recomendamos utilizar estas versiones o versiones más recientes.
qiskit[all]~=2.5.0
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:
En el DAG, hay tres tipos de nodos gráficos: nodos de entrada 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. Utilice la función qiskit.tools.visualization.dag_drawer () para ver el DAG de este circuito. (Instale la biblioteca Graphviz para ejecutarlo)
# Convert to DAG
dag = circuit_to_dag(circ)
dag_drawer(dag)Output:
Operaciones básicas de DAG
Los siguientes ejemplos de código muestran operaciones comunes con DAGs, incluyendo el acceso a nodos, operaciones de adición y sustitución de subcircuitos. Estas operaciones constituyen la base para construir pases de transpilador.
Obtener todos los nodos operativos en el DAG
El método op_nodes() devuelve una lista iterable de objetos DAGOpNode en el circuito:
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 0x7f703fe0bd50>, 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)Output:
node name: if_else
op: Instruction(name='if_else', num_qubits=1, num_clbits=3, params=[<qiskit.circuit.quantumcircuit.QuantumCircuit object at 0x7f703facdb10>, 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 a la parte posterior
Se añade una operación al final del DAGCircuit utilizando el método apply_operation_back() . Esto añade la puerta especificada para actuar 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)Output:
Añadir una operación al frente
Se añade una operación al principio del DAGCircuit utilizando el método apply_operation_front() . Esto inserta la puerta especificada antes de todas las operaciones existentes en el circuito, convirtiéndola 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)Output:
Sustituir un nodo por un subcircuito
Un nodo que representa una operación específica en el DAGCircuit se sustituye por un subcircuito. En primer lugar, se construye un nuevo sub-DAG con la secuencia de puertas deseada y, a continuación, se sustituye el nodo objetivo por este sub-DAG utilizando 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)Output:
Una vez completadas todas las transformaciones, el DAG puede convertirse de nuevo en un objeto normal de QuantumCircuit . Así funciona el transpilador. Se toma un circuito, se procesa en forma de DAG y se obtiene como salida un circuito transformado.
from qiskit.converters import dag_to_circuit
new_circ = dag_to_circuit(dag)
circuit_drawer(new_circ, output="mpl")Output:
Implementar un pase « BasicMapper »
La estructura DAG puede aprovecharse para escribir pases de transpilador. En el siguiente ejemplo, se implementa un pase BasicMapper para mapear un circuito arbitrario en un dispositivo con conectividad qubit restringida. Para más información, consulte la guía sobre cómo escribir pases de transpilador personalizados.
El pase se define como TransformationPass, lo que significa que modifica el circuito. Para ello, recorre el DAG capa por capa, comprobando si cada instrucción cumple las restricciones impuestas por el mapa de acoplamiento del dispositivo. Si se detecta una infracción, se determina una ruta de intercambio y se insertan en consecuencia las puertas SWAP necesarias.
Al crear un pase de transpilador, la primera decisión consiste en seleccionar si el pase debe heredar de TransformationPass o de AnalysisPass. Los pases de transformación están diseñados para modificar el circuito, mientras que los pases de análisis sólo pretenden extraer información para utilizarla en pases posteriores. La funcionalidad principal se implementa en el método run(dag) . Por último, el pase debe registrarse en el módulo qiskit.transpiler.passes .
En este paso específico, el DAG se recorre capa por capa (donde cada capa contiene operaciones que actúan sobre conjuntos disjuntos de qubits y, por tanto, pueden ejecutarse independientemente). 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 necesarios para poner en adyacencia los qubits implicados.
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_dagEl pase puede probarse ahora en un pequeño circuito de ejemplo. Se construye un gestor de pases con el pase recién definido incluido. A continuación, se proporciona el circuito de ejemplo a este gestor de paso y se obtiene como salida un nuevo circuito transformado.
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:
Próximos pasos
- Consulte la guía sobre cómo crear una pasada de transpilador personalizada
- Descubre cómo crear y transpilar utilizando backends personalizados
- Prueba la guía de configuración del transpilador Compare.
- Revisa la documentación de la API del circuito DAG.