Skip to main content
IBM Quantum Platform

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 graphviz per 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.DAGCircuit oggetti composti da singoli DagNode oggetti. 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 RZR_Z, 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 che prepara uno stato di Bell e applica una rotazione R_Z in base al risultato della misurazione.
    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)
    Il DAG del circuito è costituito da nodi collegati da bordi direzionali. È un modo visivo per rappresentare i qubit o i bit classici, le operazioni e il modo in cui i dati fluiscono.
    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, 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

Utilizza 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:

Output of the previous code cell

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:

Output of the previous code cell

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

Suggerimenti
Questa pagina è stata utile?
Segnala un bug, un errore di battitura o richiedi contenuti su GitHub.