Écrire une passe de transpilation personnalisée
Le code de cette page a été développé en tenant compte des exigences suivantes. Nous recommandons d'utiliser ces versions ou des versions plus récentes.
qiskit[all]~=2.5.1
Qiskit SDK vous permet de créer des passes de transpilation personnalisées et de les exécuter dans l'objet PassManager ou de les ajouter à un objet StagedPassManager. Nous allons montrer ici comment écrire une passe de transpilation, en nous concentrant sur la construction d'une passe qui effectue un tourbillon de Pauli sur les portes quantiques bruyantes d'un circuit quantique. Cet exemple utilise le DAG, qui est l'objet manipulé par le type de passe TransformationPass .
Avant de construire une passe, il est important de présenter la représentation interne des circuits quantiques dans Qiskit, le graphe acyclique dirigé (DAG) (voir ce tutoriel pour une vue d'ensemble). Pour suivre ces étapes, installez la bibliothèque
graphvizpour les fonctions de traçage DAG.Dans Qiskit, au cours des étapes de transpilation, les circuits sont représentés à l'aide d'un DAG. En général, un DAG est composé de sommets (également appelés « nœuds ») et d'arêtes orientées qui relient des paires de sommets dans une orientation particulière. Cette représentation est stockée à l'aide
qiskit.dagcircuit.DAGCircuitd'objets composés d'objets individuelsDagNode. L'avantage de cette représentation par rapport à une simple liste de portes (c'est-à-dire une liste d'interconnexions ) est que le flux d'informations entre les opérations est explicite, ce qui facilite la prise de décisions en matière de transformation.Cet exemple illustre le DAG en créant un circuit simple qui prépare un état Bell et applique une rotation , en fonction du résultat de la mesure.
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')
Circuit Utilisez la fonction
qiskit.tools.visualization.dag_drawer()pour visualiser le DAG de ce circuit. Il existe trois types de nœuds de graphe : les nœuds de qubit/clbit (vert), les nœuds d'opération (bleu) et les nœuds de sortie (rouge). Chaque arête indique le flux de données (ou la dépendance) entre deux nœuds.from qiskit.converters import circuit_to_dag from qiskit.visualization import dag_drawer dag = circuit_to_dag(qc) dag_drawer(dag)
DAG
Passages du transpilateur
Les passes du transpileur sont classées soit comme AnalysisPass soit comme TransformationPass. Les passes fonctionnent généralement avec le DAG et le property_set, un objet de type dictionnaire servant à stocker les propriétés déterminées par les passes d'analyse. Les passes d'analyse fonctionnent aussi bien avec le DAG qu'avec son property_set. Ils ne peuvent pas modifier le DAG, mais peuvent modifier le property_set. Cela contraste avec les passes de transformation, qui modifient le DAG et peuvent lire (mais pas écrire) dans property_setcelui-ci. Par exemple, les passes de transformation convertissent un circuit en son architecture d'instructions (ISA) ou effectuent des passes de routage pour insérer des portes SWAP là où cela est nécessaire.
Créer une PauliTwirl passage de transpileur
L'exemple suivant construit une passe de transpilateur qui ajoute des tourbillons de Pauli. Le tournoiement de Pauli est une stratégie de suppression des erreurs qui randomise la manière dont les qubits sont confrontés aux canaux bruyants, que nous supposons être des portes à deux qubits dans cet exemple (parce qu'elles sont beaucoup plus sujettes aux erreurs que les portes à un seul qubit). Les tourbillons de Pauli n'affectent pas le fonctionnement des portes à deux qubits. Ils sont choisis de telle sorte que ceux qui sont appliqués avant la porte à deux qubits (à gauche) sont contrés par ceux qui sont appliqués après la porte à deux qubits (à droite). En ce sens, les opérations à deux qubits sont identiques, mais la manière dont elles sont effectuées est différente. L'un des avantages du tourbillon de Pauli est qu'il transforme les erreurs cohérentes en erreurs stochastiques, qui peuvent être améliorées en calculant davantage de moyennes.
Les passes Transpiler agissent sur le DAG, donc la méthode importante à surcharger est .run(), qui prend le DAG en entrée. L'initialisation des paires de Paulis comme indiqué préserve le fonctionnement de chaque porte à deux qubits. Pour ce faire, on utilise la méthode d'aide build_twirl_set, qui passe en revue chaque Pauli à deux qubits (tel qu'obtenu à partir de pauli_basis(2)) et trouve l'autre Pauli qui préserve l'opération.
À partir du DAG, utilisez la méthode op_nodes() pour retourner tous ses nœuds. Le DAG peut également être utilisé pour collecter des runs, qui sont des séquences de nœuds qui s'exécutent de manière ininterrompue sur un qubit. Elles peuvent être rassemblées sous forme d'exécutions de qubits simples avec collect_1q_runs, d'exécutions de qubits doubles avec collect_2q_runs, et d'exécutions de nœuds dont les noms d'instructions figurent dans une liste de noms avec collect_runs. Le site DAGCircuit propose de nombreuses méthodes pour rechercher et parcourir un graphe. Une méthode couramment utilisée est topological_op_nodes, qui fournit les nœuds dans un ordre de dépendance. D'autres méthodes, telles que bfs_successors , sont utilisées principalement pour déterminer comment les nœuds interagissent avec les opérations ultérieures sur un DAG.
Dans l'exemple, nous voulons remplacer chaque nœud, représentant une instruction, par un sous-circuit construit comme un mini DAG. Un registre quantique à deux qubits est ajouté au mini DAG. Les opérations sont ajoutées au mini DAG en utilisant apply_operation_back, qui place Instruction sur la sortie du mini DAG (alors que apply_operation_front le placerait sur l'entrée du mini DAG). Le nœud est ensuite remplacé par le mini DAG en utilisant substitute_node_with_dag, et le processus se poursuit pour chaque instance de CXGate et ECRGate dans le DAG (correspondant aux portes de base à deux qubits sur les backends 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 dagUtilisez le PauliTwirl passage du transpiler
Le code suivant utilise le passe créé ci-dessus pour transcompiler un circuit. Considérons un circuit simple avec des portes cx``ecr et.
qc = QuantumCircuit(3)
qc.cx(0, 1)
qc.ecr(1, 2)
qc.ecr(1, 0)
qc.cx(2, 1)
qc.draw("mpl")Output:
Pour appliquer le laissez-passer personnalisé, construisez un gestionnaire de laissez-passer en utilisant le laissez-passer PauliTwirl et faites-le fonctionner sur 50 circuits.
pm = PassManager([PauliTwirl()])
twirled_qcs = [pm.run(qc) for _ in range(50)]Chaque porte à deux qubits est maintenant prise en sandwich entre deux Paulis.
twirled_qcs[-1].draw("mpl")Output:
Les opérateurs sont les mêmes si l'on utilise Operator de qiskit.quantum_info :
np.all([Operator(twirled_qc).equiv(qc) for twirled_qc in twirled_qcs])Output:
np.True_
Etapes suivantes
- Pour apprendre à utiliser la fonction
generate_preset_passmanagerau lieu d'écrire vos propres passes, commencez par la rubrique Paramètres par défaut et options de configuration de la transpilation. - Consultez le guide Comparer les paramètres du transpiler.
- Consultez la documentation relative à l'API du transcompilateur.