Scrivi il tuo transpiler pass
Versioni dei pacchetti
Il codice di questa pagina è stato sviluppato usando i seguenti requisiti. Si consiglia di usare queste versioni o versioni più recenti.
qiskit[all]~=2.5.2
Qiskit SDK ti permette di creare pass di transpilazione personalizzati ed eseguirli nell'oggetto PassManager o di aggiungerli a uno StagedPassManager. Qui dimostreremo come scrivere un pass del Transpiler, concentrandoci sulla creazione di un pass che esegue il Pauli twirling sui Gate quantistici rumorosi in un Circuit quantistico. Questo esempio usa il DAG, che è l'oggetto manipolato dal tipo di pass TransformationPass.
# Added by doQumentation — installs the packages this notebook needs if they are missing
import importlib.util
_needed = {"numpy": "numpy", "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")
Approfondimento: rappresentazione DAG
Prima di creare un pass, è importante introdurre la rappresentazione interna dei circuiti quantistici in Qiskit, il grafo aciclico diretto (DAG) (consulta questo tutorial per una panoramica). Per seguire questi passaggi, installa la libreria graphviz per le funzioni di disegno del DAG.
In Qiskit, all'interno delle fasi di transpilazione, i circuiti sono rappresentati usando un DAG. In generale, un DAG è composto da vertici (detti anche "nodi") e da archi diretti che collegano coppie di vertici in un particolare orientamento. Questa rappresentazione viene memorizzata usando oggetti qiskit.dagcircuit.DAGCircuit composti da singoli oggetti DagNode. Il vantaggio di questa rappresentazione rispetto a un semplice elenco di Gate (cioè una netlist) è che il flusso di informazioni tra le operazioni è esplicito, rendendo più facile prendere decisioni di trasformazione.
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')
Usa la funzione qiskit.tools.visualization.dag_drawer() per visualizzare il DAG di questo circuito. Esistono tre tipi di nodi del grafo: nodi Qubit/clbit (verdi), nodi operazione (blu) e nodi di output (rossi). Ogni arco 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)

Transpiler pass
I transpiler pass vengono classificati come AnalysisPass o TransformationPass. I pass in generale lavorano con il DAG e il property_set, un oggetto simile a un dizionario per memorizzare le proprietà determinate dagli analysis pass. Gli analysis pass lavorano sia con il DAG sia con il suo property_set: non possono modificare il DAG, ma possono modificare il property_set. Questo si contrappone ai transformation pass, che modificano il DAG e possono leggere (ma non scrivere) il property_set. Ad esempio, i transformation pass traducono un circuito nella sua ISA o eseguono routing pass per inserire gate SWAP dove necessario.
Crea un transpiler pass PauliTwirl
L'esempio seguente costruisce un transpiler pass che aggiunge Pauli twirl. Il Pauli twirling è una strategia di soppressione degli errori che randomizza il modo in cui i qubit attraversano i canali rumorosi, che in questo esempio assumiamo essere gate a due qubit (poiché sono molto più soggetti a errori rispetto ai gate a singolo qubit). I Pauli twirl non modificano l'operazione dei gate a due qubit: vengono scelti in modo che quelli applicati prima del gate a due qubit (a sinistra) siano neutralizzati da quelli applicati dopo (a destra). In questo senso, le operazioni a due qubit sono identiche, ma il modo in cui vengono eseguite è diverso. Uno dei vantaggi del Pauli twirling è che converte gli errori coerenti in errori stocastici, che possono essere ridotti mediando su un numero maggiore di campioni.
I transpiler pass agiscono sul DAG, quindi il metodo importante da sovrascrivere è .run(), che riceve il DAG come input. Inizializzare le coppie di Pauli come mostrato preserva l'operazione di ogni gate a due qubit. Questo viene fatto con il metodo helper build_twirl_set, che scorre ogni Pauli a due qubit (ottenuto da pauli_basis(2)) e trova l'altro Pauli che preserva l'operazione.
Dal DAG, usa il metodo op_nodes() per restituire tutti i suoi nodi. Il DAG può essere usato anche per raccogliere run, ovvero sequenze di nodi eseguite senza interruzioni su un qubit. Questi possono essere raccolti come run a singolo qubit con collect_1q_runs, run a due qubit con collect_2q_runs, e run di nodi i cui nomi di istruzione sono in una namelist con collect_runs. Il DAGCircuit dispone di molti metodi per la ricerca e l'attraversamento di un grafo. Uno dei metodi più usati è topological_op_nodes, che fornisce i nodi in ordine di dipendenza. Altri metodi come bfs_successors vengono usati principalmente per determinare come i nodi interagiscono con le operazioni successive in un DAG.
Nell'esempio, vogliamo sostituire ogni nodo, che rappresenta un'istruzione, con un sottocircuito costruito come un mini DAG. Al mini DAG viene aggiunto un registro quantistico a due qubit. Le operazioni vengono aggiunte al mini DAG tramite apply_operation_back, che posiziona l'Instruction sull'output del mini DAG (mentre apply_operation_front la posizionerebbe sull'input). Il nodo viene poi sostituito dal mini DAG tramite substitute_node_with_dag, e il processo continua per ogni istanza di CXGate e ECRGate nel DAG (corrispondenti ai gate di base a due qubit nei backend 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 il transpiler pass PauliTwirl
Il codice seguente usa il pass creato sopra per transpilare un circuito. Considera un semplice circuito con gate cx ed ecr.
qc = QuantumCircuit(3)
qc.cx(0, 1)
qc.ecr(1, 2)
qc.ecr(1, 0)
qc.cx(2, 1)
qc.draw("mpl")
Per applicare il pass personalizzato, costruisci un pass manager usando il pass PauliTwirl ed eseguilo su 50 circuiti.
pm = PassManager([PauliTwirl()])
twirled_qcs = [pm.run(qc) for _ in range(50)]
Ogni gate a due qubit è ora racchiuso tra due Pauli.
twirled_qcs[-1].draw("mpl")
Gli operatori sono gli stessi se si usa Operator da qiskit.quantum_info:
np.all([Operator(twirled_qc).equiv(qc) for twirled_qc in twirled_qcs])
np.True_
Passi successivi
- Per imparare a usare la funzione
generate_preset_passmanagerinvece di scrivere i propri pass, inizia con l'argomento Impostazioni predefinite e opzioni di configurazione della transpilazione. - Prova la guida Confronta le impostazioni del transpiler.
- Consulta la documentazione API del transpiler.