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
graphvizpara 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.DAGCircuitobjetos que são compostos por objetosDagNodeindividuais. 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 , 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 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)
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, 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 dagUse 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:
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:
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
- Para saber como usar a função
generate_preset_passmanagerem vez de escrever seus próprios passes, comece com o tópico Definições padrão de transpilação e opções de configuração. - Experimente o guia Comparar configurações do transpiler.
- Consulte a documentação da API do transpiler.