Escribir un paso de transpilador personalizado
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
El SDK de Qiskit permite crear pases de transpilación personalizados y ejecutarlos en el objeto PassManager o añadirlos a un StagedPassManager. Aquí demostraremos cómo escribir un pase transpilador, centrándonos en la construcción de un pase que realice el giro de Pauli en las puertas cuánticas ruidosas de un circuito cuántico. Este ejemplo utiliza el DAG, que es el objeto manipulado por el tipo de pase TransformationPass .
Antes de construir un pase, es importante introducir la representación interna de los circuitos cuánticos en Qiskit, el grafo acíclico dirigido (DAG) (ver este tutorial para una visión general). Para seguir estos pasos, instale la biblioteca
graphvizpara las funciones de trazado de DAG.En Qiskit, dentro de las etapas de transpilación, los circuitos se representan utilizando 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 utilizando
qiskit.dagcircuit.DAGCircuitobjetos que se componen de objetosDagNodeindividuales. La ventaja de esta representación sobre una lista pura de puertas (es decir, una lista de redes ) 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 mediante la creación de un circuito simple que prepara un estado Bell y aplica una rotación , 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]) with qc.if_test((cr, 2)): qc.rz(np.pi/2, qr[1]) qc.draw(output='mpl')
Circuito Utilice la función
qiskit.tools.visualization.dag_drawer()para ver el DAG de este circuito. Hay tres tipos de nodos gráficos: nodos 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.visualization import dag_drawer dag = circuit_to_dag(qc) dag_drawer(dag)
DAG
Pasadas del transpilador
Las etapas del transpilador se clasifican como AnalysisPass o como TransformationPass. Las pasadas suelen funcionar con el DAG y el property_set, un objeto similar a un diccionario que sirve para almacenar las propiedades determinadas por las pasadas de análisis. Las etapas de análisis funcionan tanto con el DAG como con su property_set. No pueden modificar el DAG, pero sí pueden modificar el property_set. Esto contrasta con las etapas de transformación, que sí modifican el DAG y pueden leer (pero no escribir) en property_setél. Por ejemplo, las pasadas de transformación convierten un circuito a su ISA o realizan pasadas de enrutamiento para insertar puertas SWAP donde sea necesario.
Crear una PauliTwirl pasada del transpilador
El siguiente ejemplo construye un pase de transpilador que añade giros de Pauli. El giro de Pauli es una estrategia de supresión de errores que aleatoriza la forma en que los qubits experimentan los canales ruidosos, que en este ejemplo suponemos que son puertas de dos qubits (porque son mucho más propensas a errores que las puertas de un solo qubit). Los giros de Pauli no afectan al funcionamiento de las puertas de dos qubits. Se eligen de forma que los aplicados antes de la puerta de dos qubits (a la izquierda) sean contrarrestados por los aplicados después de la puerta de dos qubits (a la derecha). En este sentido, las operaciones de dos qubits son idénticas, pero la forma en que se realizan es diferente. Una ventaja del giro de Pauli es que convierte los errores coherentes en errores estocásticos, que pueden mejorarse promediando más.
Los pases del Transpilador actúan sobre el DAG, por lo que el método importante a anular es .run(), que toma el DAG como entrada. Inicializar pares de Paulis como se muestra preserva la operación de cada puerta de dos qubits. Esto se hace con el método de ayuda build_twirl_set, que recorre cada Pauli de dos qubits (como se obtiene de pauli_basis(2)) y encuentra el otro Pauli que preserva la operación.
A partir del DAG, utiliza el método op_nodes() para devolver todos sus nodos. El DAG también puede utilizarse para recopilar ejecuciones, que son secuencias de nodos que se ejecutan ininterrumpidamente en un qubit. Se pueden recopilar como ejecuciones de un solo qubit con collect_1q_runs, ejecuciones de dos qubits con collect_2q_runs, y ejecuciones de nodos en los que los nombres de las instrucciones están en una namelist con collect_runs. DAGCircuit dispone de numerosos métodos para buscar y recorrer un grafo. Un método comúnmente utilizado es topological_op_nodes, que proporciona los nodos en un orden de dependencia. Otros métodos como bfs_successors se utilizan principalmente para determinar cómo interactúan los nodos con operaciones posteriores en un DAG.
En el ejemplo, queremos sustituir cada nodo, que representa una instrucción, por un subcircuito construido como un mini DAG. Al mini DAG se le ha añadido un registro cuántico de dos qubits. Las operaciones se añaden al mini DAG utilizando apply_operation_back, que coloca el Instruction en la salida del mini DAG (mientras que apply_operation_front lo colocaría en la entrada del mini DAG). A continuación, el nodo se sustituye por el mini DAG utilizando substitute_node_with_dag, y el proceso continúa sobre cada instancia de CXGate y ECRGate en el DAG (correspondientes a las puertas 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, Optionalclass 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 dagUtiliza el PauliTwirl paso del transpilador
El siguiente código utiliza el pase creado anteriormente para transpilear un circuito. Consideremos un circuito simple con cx puertas ecr y.
qc = QuantumCircuit(3)
qc.cx(0, 1)
qc.ecr(1, 2)
qc.ecr(1, 0)
qc.cx(2, 1)
qc.draw("mpl")Output:
Para aplicar el pase personalizado, cree un gestor de pases utilizando el pase PauliTwirl y ejecútelo en 50 circuitos.
pm = PassManager([PauliTwirl()])
twirled_qcs = [pm.run(qc) for _ in range(50)]Cada puerta de dos qubits está ahora intercalada entre dos Paulis.
twirled_qcs[-1].draw("mpl")Output:
Los operadores son los mismos si se utiliza Operator de qiskit.quantum_info :
np.all([Operator(twirled_qc).equiv(qc) for twirled_qc in twirled_qcs])Output:
np.True_
Próximos pasos
- Para aprender a utilizar la función
generate_preset_passmanageren lugar de escribir sus propios pases, empiece por el tema Ajustes por defecto y opciones de configuración de la transpilación. - Prueba la guía de configuración del transpilador Compare.
- Revisa la documentación de la API del transpilador.