Saltar al contenido principal

Escribe un pase de transpilador personalizado

Versiones de paquetes

El código de esta página fue desarrollado con los siguientes requisitos. Recomendamos usar estas versiones o versiones más recientes.

qiskit[all]~=2.3.0

El SDK de Qiskit te permite crear pases de transpilación personalizados y ejecutarlos en el objeto PassManager o agregarlos a un StagedPassManager. Aquí demostraremos cómo escribir un pase de transpilador, con énfasis en construir un pase que aplique Pauli twirling sobre las compuertas cuánticas ruidosas de un circuito cuántico. Este ejemplo usa el DAG, que es el objeto manipulado por el tipo de pase TransformationPass.

# Added by doQumentation — required packages for this notebook
!pip install -q numpy qiskit

Contexto: representación DAG

Antes de construir un pase, es importante presentar la representación interna de los circuitos cuánticos en Qiskit: el grafo acíclico dirigido (DAG) (consulta este tutorial para obtener una introducción). Para seguir estos pasos, instala la biblioteca graphviz para las funciones de visualización del DAG.

En Qiskit, dentro de las etapas de transpilación, los circuitos se representan mediante un DAG. En general, un DAG está compuesto por vértices (también llamados "nodos") y aristas dirigidas que conectan pares de vértices en una orientación específica. Esta representación se almacena usando objetos qiskit.dagcircuit.DAGCircuit compuestos de objetos individuales DagNode. La ventaja de esta representación frente a una lista pura de compuertas (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.

Este ejemplo ilustra el DAG creando un circuito sencillo que prepara un estado de Bell y aplica una rotación RZR_Z dependiendo del resultado de la medición.

  from qiskit import QuantumRegister, ClassicalRegister, QuantumCircuit
import numpy as np

qr = QuantumRegister(3, 'qr')
cr = ClassicalRegister(3, 'cr')
qc = QuantumCircuit(qr, cr)

qc.h(qr[0])
qc.cx(qr[0], qr[1])
qc.measure(qr[0], cr[0])
qc.rz(np.pi/2, qr[1]).c_if(cr, 2)
qc.draw(output='mpl')

Circuito que prepara un estado de Bell y aplica una rotación R_Z dependiendo del resultado de la medición.

Usa la función qiskit.tools.visualization.dag_drawer() para visualizar el DAG de este circuito. Hay tres tipos de nodos en el grafo: nodos de qubit/clbit (verde), nodos de operación (azul) y nodos de salida (rojo). Cada arista indica el flujo de datos (o dependencia) entre dos nodos.

from qiskit.converters import circuit_to_dag
from qiskit.tools.visualization import dag_drawer

dag = circuit_to_dag(qc)
dag_drawer(dag)

El DAG del circuito está formado por nodos conectados mediante aristas direccionales. Es una forma visual de representar los qubits o bits clásicos, las operaciones y el flujo de datos.

Pases de transpilador

Los pases de transpilador se clasifican como AnalysisPass o TransformationPass. En general, los pases trabajan con el DAG y el property_set, un objeto similar a un diccionario para almacenar propiedades determinadas por los pases de análisis. Los pases de análisis trabajan tanto con el DAG como con su property_set: no pueden modificar el DAG, pero sí pueden modificar el property_set. Esto contrasta con los pases de transformación, que sí modifican el DAG y pueden leer (pero no escribir en) el property_set. Por ejemplo, los pases de transformación traducen un circuito a su ISA o realizan pases de enrutamiento para insertar compuertas SWAP donde sea necesario.

Crea un pase de transpilador PauliTwirl

El siguiente ejemplo construye un pase de transpilador que agrega Pauli twirls. El Pauli twirling es una estrategia de supresión de errores que aleatoriza cómo los qubits experimentan los canales ruidosos, que en este ejemplo asumimos que son las compuertas de dos qubits (ya que son mucho más propensas a errores que las compuertas de un solo qubit). Los Pauli twirls no afectan la operación de las compuertas de dos qubits. Se eligen de manera que los aplicados antes de la compuerta de dos qubits (a la izquierda) sean contrarrestados por los aplicados después (a la derecha). En este sentido, las operaciones de dos qubits son idénticas, pero la forma en que se ejecutan es diferente. Una ventaja del Pauli twirling es que convierte los errores coherentes en errores estocásticos, que pueden reducirse promediando más ejecuciones.

Los pases de transpilador actúan sobre el DAG, por lo que el método importante a sobrescribir es .run(), que recibe el DAG como entrada. Inicializar pares de Paulis como se muestra preserva la operación de cada compuerta de dos qubits. Esto se hace con el método auxiliar build_twirl_set, que recorre cada Pauli de dos qubits (obtenido de pauli_basis(2)) y encuentra el otro Pauli que preserva la operación.

Desde el DAG, usa el método op_nodes() para obtener todos sus nodos. El DAG también puede usarse para recopilar ejecuciones, que son secuencias de nodos que se ejecutan sin interrupciones en un qubit. Estas pueden recopilarse como ejecuciones de un qubit con collect_1q_runs, ejecuciones de dos qubits con collect_2q_runs, y ejecuciones de nodos cuyos nombres de instrucción estén en una lista de nombres con collect_runs. El DAGCircuit tiene muchos métodos para buscar y recorrer un grafo. Uno de los más utilizados es topological_op_nodes, que proporciona los nodos en orden de dependencia. Otros métodos como bfs_successors se usan principalmente para determinar cómo los nodos interactúan con las operaciones posteriores en un DAG.

En el ejemplo, queremos reemplazar cada nodo, que representa una instrucción, con un subcircuito construido como un mini DAG. El mini DAG tiene un registro cuántico de dos qubits agregado. Las operaciones se agregan al mini DAG usando apply_operation_back, que coloca la Instruction en la salida del mini DAG (mientras que apply_operation_front la colocaría en la entrada). Luego el nodo es sustituido por el mini DAG usando substitute_node_with_dag, y el proceso continúa sobre cada instancia de CXGate y ECRGate en el DAG (correspondientes a las compuertas base de dos qubits en los backends de IBM®).

from qiskit.dagcircuit import DAGCircuit
from qiskit.circuit import QuantumCircuit, QuantumRegister, Gate
from qiskit.circuit.library import CXGate, ECRGate
from qiskit.transpiler import PassManager
from qiskit.transpiler.basepasses import TransformationPass
from qiskit.quantum_info import Operator, pauli_basis

import numpy as np

from typing import Iterable, Optional
class PauliTwirl(TransformationPass):
"""Add Pauli twirls to two-qubit gates."""

def __init__(
self,
gates_to_twirl: Optional[Iterable[Gate]] = None,
):
"""
Args:
gates_to_twirl: Names of gates to twirl. The default behavior is to twirl all
two-qubit basis gates, `cx` and `ecr` for IBM backends.
"""
if gates_to_twirl is None:
gates_to_twirl = [CXGate(), ECRGate()]
self.gates_to_twirl = gates_to_twirl
self.build_twirl_set()
super().__init__()

def build_twirl_set(self):
"""
Build a set of Paulis to twirl for each gate and store internally as .twirl_set.
"""
self.twirl_set = {}

# iterate through gates to be twirled
for twirl_gate in self.gates_to_twirl:
twirl_list = []

# iterate through Paulis on left of gate to twirl
for pauli_left in pauli_basis(2):
# iterate through Paulis on right of gate to twirl
for pauli_right in pauli_basis(2):
# save pairs that produce identical operation as gate to twirl
if (Operator(pauli_left) @ Operator(twirl_gate)).equiv(
Operator(twirl_gate) @ pauli_right
):
twirl_list.append((pauli_left, pauli_right))

self.twirl_set[twirl_gate.name] = twirl_list

def run(
self,
dag: DAGCircuit,
) -> DAGCircuit:
# collect all nodes in DAG and proceed if it is to be twirled
twirling_gate_classes = tuple(
gate.base_class for gate in self.gates_to_twirl
)
for node in dag.op_nodes():
if not isinstance(node.op, twirling_gate_classes):
continue

# random integer to select Pauli twirl pair
pauli_index = np.random.randint(
0, len(self.twirl_set[node.op.name])
)
twirl_pair = self.twirl_set[node.op.name][pauli_index]

# instantiate mini_dag and attach quantum register
mini_dag = DAGCircuit()
register = QuantumRegister(2)
mini_dag.add_qreg(register)

# apply left Pauli, gate to twirl, and right Pauli to empty mini-DAG
mini_dag.apply_operation_back(
twirl_pair[0].to_instruction(), [register[0], register[1]]
)
mini_dag.apply_operation_back(node.op, [register[0], register[1]])
mini_dag.apply_operation_back(
twirl_pair[1].to_instruction(), [register[0], register[1]]
)

# substitute gate to twirl node with twirling mini-DAG
dag.substitute_node_with_dag(node, mini_dag)

return dag

Usa el pase de transpilador PauliTwirl

El siguiente código usa el pase creado anteriormente para transpilar un circuito. Considera un circuito sencillo con compuertas cx y ecr.

qc = QuantumCircuit(3)
qc.cx(0, 1)
qc.ecr(1, 2)
qc.ecr(1, 0)
qc.cx(2, 1)
qc.draw("mpl")

Salida de la celda de código anterior

Para aplicar el pase personalizado, construye un gestor de pases usando el pase PauliTwirl y ejecútalo sobre 50 circuitos.

pm = PassManager([PauliTwirl()])
twirled_qcs = [pm.run(qc) for _ in range(50)]

Cada compuerta de dos qubits queda ahora flanqueada por dos Paulis.

twirled_qcs[-1].draw("mpl")

Salida de la celda de código anterior

Los operadores son los mismos si se usa Operator de qiskit.quantum_info:

np.all([Operator(twirled_qc).equiv(qc) for twirled_qc in twirled_qcs])
np.True_

Próximos pasos

Recomendaciones