Saltar al contenido principal
Este proyecto está en fase beta. ¿Encontraste un error? Abre un issue en GitHub. ¿Tienes una pregunta o una idea? Pregunta en Discussions — ¡nos encantaría tu opinión!
¿Cómo es la calidad de la traducción en esta página?

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; después explorará las operaciones básicas de DAG e implementará 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 RZR_Z, dependiendo del resultado de la medición.

Package versions

El código de esta página se desarrolló utilizando los siguientes requisitos. Recomendamos usar estas versiones o más recientes.

qiskit[all]~=2.5.2
# Added by doQumentation — installs the packages this notebook needs if they are missing
import importlib.util

_needed = {"qiskit": "qiskit"}
_missing = [pip for module, pip in _needed.items()
if importlib.util.find_spec(module) is None]
# One at a time, so a package that fails to install does not block the others
for _pip in _missing:
%pip install -q {_pip}
if not _missing:
print("\u2713 All required packages are installed")
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 of the previous code cell

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)

Output of the previous code cell

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 0x12559c410>, 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 0x12559f490>, 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)

Output of the previous code cell

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)

Output of the previous code cell

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)

Output of the previous code cell

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")

Output of the previous code cell

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.

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 necesarios para hacer adyacentes 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_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")

Output of the previous code cell

Siguientes pasos​