Scrivere un passaggio personalizzato per il transpilatore
Il codice di questa pagina è stato sviluppato in base ai seguenti requisiti. Si consiglia di utilizzare queste versioni o versioni più recenti.
qiskit[all]~=2.5.1
L'SDK di Qiskit consente di creare passaggi di transpilazione personalizzati e di eseguirli nell'oggetto PassManager o di aggiungerli a StagedPassManager. Qui dimostreremo come scrivere un passaggio di transpiler, concentrandoci sulla costruzione di un passaggio che esegua il Pauli twirling sulle porte quantistiche rumorose di un circuito quantistico. Questo esempio utilizza il DAG, che è l'oggetto manipolato dal tipo di passaggio TransformationPass .
Prima di costruire un passaggio, è importante introdurre la rappresentazione interna dei circuiti quantistici in Qiskit, il grafo aciclico diretto (DAG) (si veda questo tutorial per una panoramica). Per seguire questi passaggi, installare la libreria
graphvizper le funzioni di plottaggio DAG.In Qiskit, nelle fasi di transpilazione, i circuiti sono rappresentati utilizzando un DAG. In generale, un DAG è composto da vertici (noti anche come "nodi") e spigoli orientati che collegano coppie di vertici in un orientamento particolare. Questa rappresentazione è memorizzata utilizzando
qiskit.dagcircuit.DAGCircuitoggetti composti da singoliDagNodeoggetti. Il vantaggio di questa rappresentazione rispetto a un semplice elenco di porte (ovvero una netlist ) è che il flusso di informazioni tra le operazioni è esplicito, rendendo più facile prendere decisioni di trasformazione.Questo esempio illustra il DAG creando un semplice circuito che prepara uno stato Bell e applica una rotazione , a seconda del risultato della misurazione.
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 Utilizzare la funzione
qiskit.tools.visualization.dag_drawer()per visualizzare il DAG di questo circuito. Ci sono tre tipi di nodi del grafo: nodi qubit/clbit (verdi), nodi di operazione (blu) e nodi di uscita (rossi). Ogni bordo indica il flusso di dati (o la dipendenza) tra due nodi.from qiskit.converters import circuit_to_dag from qiskit.visualization import dag_drawer dag = circuit_to_dag(qc) dag_drawer(dag)
DAG
Passaggi del transpiler
I passaggi del transpiler vengono classificati come AnalysisPass o come TransformationPass. I passaggi, in generale, interagiscono con il DAG e con property_set, un oggetto simile a un dizionario utilizzato per memorizzare le proprietà determinate dai passaggi di analisi. I passaggi di analisi funzionano sia con il DAG che con il suo property_set. Non possono modificare il DAG, ma possono modificare il property_set. Ciò è in contrasto con i passaggi di trasformazione, che modificano il DAG e possono leggere (ma non scrivere) property_set. Ad esempio, le fasi di trasformazione traducono un circuito nella sua ISA oppure eseguono fasi di instradamento per inserire porte SWAP dove necessario.
Creare un passaggio PauliTwirl transpiler
L'esempio seguente costruisce un passaggio di transpiler che aggiunge le torsioni di Pauli. Il Pauli twirling è una strategia di soppressione degli errori che randomizza il modo in cui i qubit sperimentano i canali rumorosi, che in questo esempio assumiamo essere porte a due qubit (perché sono molto più soggette a errori rispetto alle porte a singolo qubit). I vortici di Pauli non influenzano il funzionamento delle porte a due qubit. Sono scelti in modo tale che quelli applicati prima del gate a due equbit (a sinistra) siano contrastati da quelli applicati dopo il gate a due equbit (a destra). In questo senso, le operazioni a due qubit sono identiche, ma il modo in cui vengono eseguite è diverso. Un vantaggio della rotazione di Pauli è che trasforma gli errori coerenti in errori stocastici, che possono essere migliorati con una maggiore media.
I passaggi di Transpiler agiscono sul DAG, quindi il metodo importante da sovrascrivere è .run(), che prende il DAG come input. L'inizializzazione delle coppie di Paulis, come mostrato, preserva il funzionamento di ogni porta a due qubit. Questo viene fatto con il metodo di aiuto build_twirl_set, che passa attraverso ogni Pauli a due qubit (come ottenuto da pauli_basis(2)) e trova l'altro Pauli che preserva l'operazione.
Dal DAG, utilizzare il metodo op_nodes() per restituire tutti i suoi nodi. Il DAG può essere utilizzato anche per raccogliere le corse, che sono sequenze di nodi che funzionano ininterrottamente su un qubit. Queste possono essere raccolte come corse a singolo qubit con collect_1q_runs, corse a due qubit con collect_2q_runs, e corse di nodi in cui i nomi delle istruzioni sono in una namelist con collect_runs. Il sito DAGCircuit dispone di molti metodi per cercare e attraversare un grafo. Un metodo comunemente utilizzato è topological_op_nodes, che fornisce i nodi in un ordine di dipendenza. Altri metodi, come bfs_successors , sono utilizzati principalmente per determinare come i nodi interagiscono con le operazioni successive su un DAG.
Nell'esempio, vogliamo sostituire ogni nodo, che rappresenta un'istruzione, con un sottocircuito costruito come un mini DAG. Al mini DAG è stato aggiunto un registro quantistico a due qubit. Le operazioni vengono aggiunte al mini DAG utilizzando apply_operation_back, che posiziona Instruction sull'uscita del mini DAG (mentre apply_operation_front lo posizionerebbe sull'ingresso del mini DAG). Il nodo viene quindi sostituito dal mini DAG utilizzando substitute_node_with_dag, e il processo continua per ogni istanza di CXGate e ECRGate nel DAG (corrispondenti alle porte di base a due qubit sui backend di 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 dagUtilizza il passaggio PauliTwirl transpiler
Il codice seguente utilizza il pass creato sopra per transpilare un circuito. Consideriamo un circuito semplice con cx porte ecr e.
qc = QuantumCircuit(3)
qc.cx(0, 1)
qc.ecr(1, 2)
qc.ecr(1, 0)
qc.cx(2, 1)
qc.draw("mpl")Output:
Per applicare il pass personalizzato, costruire un pass manager utilizzando il pass PauliTwirl ed eseguirlo su 50 circuiti.
pm = PassManager([PauliTwirl()])
twirled_qcs = [pm.run(qc) for _ in range(50)]Ogni porta a due qubit è ora inserita tra due Paulis.
twirled_qcs[-1].draw("mpl")Output:
Gli operatori sono gli stessi se si utilizza Operator da qiskit.quantum_info :
np.all([Operator(twirled_qc).equiv(qc) for twirled_qc in twirled_qcs])Output:
np.True_
Passi successivi
- Per imparare a usare la funzione
generate_preset_passmanagerinvece di scrivere i propri passaggi, iniziare con l'argomento Impostazioni predefinite e opzioni di configurazione di Transpilation. - Prova la guida Confronta le impostazioni del transpiler.
- Consulta la documentazione dell'API del transpiler.