Vai al contenuto principale

Scrivi un transpiler pass personalizzato

Versioni dei pacchetti

Il codice in questa pagina è stato sviluppato con i seguenti requisiti. Ti consigliamo di usare queste versioni o versioni più recenti.

qiskit[all]~=2.3.0

L'SDK di Qiskit ti permette di creare transpilation pass personalizzati ed eseguirli nell'oggetto PassManager oppure aggiungerli a un StagedPassManager. Qui mostreremo come scrivere un transpiler pass, concentrandoci sulla costruzione di un pass che esegue il Pauli twirling sui gate quantistici rumorosi in un circuito quantistico. Questo esempio utilizza il DAG, ovvero l'oggetto manipolato dal tipo di pass TransformationPass.

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

Informazioni di base: rappresentazione DAG

Prima di costruire un pass, è importante introdurre la rappresentazione interna dei circuiti quantistici in Qiskit: il grafo aciclico diretto (DAG) (vedi questo tutorial per una panoramica). Per seguire questi passaggi, installa la libreria graphviz necessaria per le funzioni di visualizzazione del DAG.

In Qiskit, all'interno delle fasi di transpilazione, i circuiti sono rappresentati tramite un DAG. In generale, un DAG è composto da vertici (detti anche "nodi") e da archi diretti che collegano coppie di vertici in una direzione specifica. Questa rappresentazione viene memorizzata tramite 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ù semplice prendere decisioni di trasformazione.

Questo esempio illustra il DAG creando un semplice circuito che prepara uno stato di Bell e applica una rotazione RZR_Z in base al risultato della misura.

  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 che prepara uno stato di Bell e applica una rotazione R_Z in base al risultato della misura.

Usa la funzione qiskit.tools.visualization.dag_drawer() per visualizzare il DAG di questo circuito. Esistono tre tipi di nodi nel 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.tools.visualization import dag_drawer

dag = circuit_to_dag(qc)
dag_drawer(dag)

Il DAG del circuito è composto da nodi collegati da archi direzionali. È un modo visivo per rappresentare qubit o bit classici, le operazioni e il flusso dei dati.

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 aumentando il numero di medie.

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 che si eseguono ininterrotti 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. Il mini DAG ha 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")

Output della cella di codice precedente

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")

Output della cella di codice precedente

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​

Raccomandazioni