Skip to main content
IBM Quantum Platform

Crie uma etapa personalizada do transpiler

  • O código desta página foi desenvolvido usando os seguintes requisitos. Recomendamos o uso dessas versões ou de versões mais recentes.

    qiskit[all]~=2.5.0
    

O Qiskit SDK permite que você crie passagens de transpilação personalizadas e as execute no objeto PassManager ou as adicione a um StagedPassManager. Aqui demonstraremos como escrever uma passagem de transpilador, concentrando-nos na criação de uma passagem que executa o giro de Pauli nas portas quânticas ruidosas em um circuito quântico. Este exemplo usa o DAG, que é o objeto manipulado pelo tipo de passe TransformationPass .

  • Antes de criar uma passagem, é importante apresentar a representação interna dos circuitos quânticos no Qiskit, o gráfico acíclico dirigido (DAG) (consulte este tutorial para obter uma visão geral). Para seguir essas etapas, instale a biblioteca graphviz para as funções de plotagem DAG.

    No Qiskit, nas etapas de transpilagem, os circuitos são representados usando um DAG. Em geral, um DAG é composto por vértices (também conhecidos como “nós”) e arestas direcionadas que conectam pares de vértices em uma orientação específica. Essa representação é armazenada usando qiskit.dagcircuit.DAGCircuit objetos que são compostos por objetos DagNode individuais. A vantagem dessa representação em relação a uma lista pura de portas (ou seja, uma lista de rede ) é que o fluxo de informações entre as operações é explícito, facilitando a tomada de decisões de transformação.

    Este exemplo ilustra o DAG criando um circuito simples que prepara um estado Bell e aplica uma rotação RZR_Z, dependendo do resultado da medição.

    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 que prepara um estado de Bell e aplica uma rotação R_Z dependendo do resultado da medição.
    Circuito

    Use a função qiskit.tools.visualization.dag_drawer() para visualizar o DAG desse circuito. Há três tipos de nós de gráfico: nós de qubit/clbit (verde), nós de operação (azul) e nós de saída (vermelho). Cada borda indica o fluxo de dados (ou dependência) entre dois nós.

    from qiskit.converters import circuit_to_dag
    from qiskit.visualization import dag_drawer
    
    dag = circuit_to_dag(qc)
    dag_drawer(dag)
    O DAG do circuito consiste em nós que são conectados por bordas direcionais. É uma forma visual de representar qubits ou bits clássicos, as operações e a maneira como os dados fluem.
    DAG

Passagens do transpiler

As etapas do transpiler TransformationPasssão classificadas como AnalysisPass ou. As passagens, em geral, funcionam com o DAG e o property_set, um objeto semelhante a um dicionário para armazenar propriedades determinadas pelas passagens de análise. As etapas de análise funcionam tanto com o DAG quanto com seu property_set. Eles não podem modificar o DAG, mas podem modificar o property_set. Isso contrasta com as etapas de transformação, que modificam o DAG e podem ler (mas não gravar) nele property_set. Por exemplo, as etapas de transformação convertem um circuito para sua ISA ou realizam etapas de roteamento para inserir portas SWAP onde necessário.


Criar uma passagem PauliTwirl do transpiler

O exemplo a seguir constrói uma passagem de transpilador que adiciona giros de Pauli. O giro de Pauli é uma estratégia de supressão de erros que randomiza a forma como os qubits experimentam canais ruidosos, que assumimos como portas de dois qubits neste exemplo (porque elas são muito mais propensas a erros do que as portas de um único qubit). Os giros de Pauli não afetam a operação das portas de dois qubits. Eles são escolhidos de forma que aqueles aplicados antes da porta de dois qubits (à esquerda) sejam neutralizados por aqueles aplicados após a porta de dois qubits (à direita). Nesse sentido, as operações de dois qubits são idênticas, mas a maneira como são realizadas é diferente. Uma vantagem do giro de Pauli é que ele transforma erros coerentes em erros estocásticos, que podem ser melhorados com o aumento da média.

As passagens do Transpiler atuam no DAG, portanto, o método importante a ser substituído é .run(), que recebe o DAG como entrada. A inicialização de pares de Paulis, conforme mostrado, preserva a operação de cada porta de dois qubits. Isso é feito com o método auxiliar build_twirl_set, que passa por cada Pauli de dois qubits (conforme obtido em pauli_basis(2)) e encontra o outro Pauli que preserva a operação.

No DAG, use o método op_nodes() para retornar todos os seus nós. O DAG também pode ser usado para coletar execuções, que são sequências de nós que são executadas ininterruptamente em um qubit. Eles podem ser coletados como execuções de um único qubit com collect_1q_runs, execuções de dois qubit com collect_2q_runs e execuções de nós em que os nomes das instruções estão em uma lista de nomes com collect_runs. O site DAGCircuit tem muitos métodos para pesquisar e percorrer um gráfico. Um método comumente usado é o topological_op_nodes, que fornece os nós em uma ordem de dependência. Outros métodos, como o bfs_successors , são usados principalmente para determinar como os nós interagem com as operações subsequentes em um DAG.

No exemplo, queremos substituir cada nó, que representa uma instrução, por um subcircuito construído como um mini DAG. O mini DAG tem um registro quântico de dois qubits adicionado a ele. As operações são adicionadas ao mini DAG usando apply_operation_back, que coloca o Instruction na saída do mini DAG (enquanto apply_operation_front o colocaria na entrada do mini DAG). O nó é então substituído pelo mini DAG usando substitute_node_with_dag, e o processo continua em cada instância de CXGate e ECRGate no DAG (correspondente às portas de base de dois qubits nos 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, 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

Use a passagem PauliTwirl do transpiler

O código a seguir usa o pass criado acima para transpilá-lo. Considere um circuito simples com cx portas 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

Para aplicar o passe personalizado, crie um gerenciador de passes usando o passe PauliTwirl e execute-o em 50 circuitos.

pm = PassManager([PauliTwirl()])
twirled_qcs = [pm.run(qc) for _ in range(50)]

Cada porta de dois qubits está agora imprensada entre duas Paulis.

twirled_qcs[-1].draw("mpl")

Output:

Output of the previous code cell

Os operadores são os mesmos se Operator de qiskit.quantum_info for usado:

np.all([Operator(twirled_qc).equiv(qc) for twirled_qc in twirled_qcs])

Output:

np.True_

Próximas etapas

Recomendações
Esta página foi útil?
Relate um bug, erro de digitação ou solicite conteúdo no GitHub.