Pular para o conteúdo principal

Comparar configurações do transpilador

Versões dos pacotes

O código nesta página foi desenvolvido usando os seguintes requisitos. Recomendamos usar estas versões ou mais recentes.

qiskit[all]~=2.4.0
qiskit-ibm-runtime~=0.46.1

Diferentes configurações do transpilador oferecem diferentes tipos de otimização para o circuito, muitas vezes à custa de um tempo de processamento clássico mais longo. Este guia percorre todo o processo de criação, transpilação e envio de circuitos para demonstrar como testar o desempenho de diversas configurações.

Observe que a mesma configuração pode melhorar os resultados de um circuito e prejudicar outro. Certifique-se de inspecionar os circuitos transpilados resultantes antes de executá-los em hardware real.

Configurar e criar circuito de exemplo

# Added by doQumentation — required packages for this notebook
!pip install -q qiskit qiskit-ibm-runtime
# Create circuit to test transpiler on
from qiskit import QuantumCircuit
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit.circuit.library import grover_operator, DiagonalGate

# Use Statevector object to calculate the ideal output
from qiskit.quantum_info import Statevector
from qiskit.visualization import plot_histogram
from qiskit.transpiler import PassManager

from qiskit.circuit.library import XGate
from qiskit.quantum_info import hellinger_fidelity

Crie um pequeno circuito para o transpilador tentar otimizar. Este exemplo cria um circuito que executa o algoritmo de Grover com um oráculo que marca o estado 111. Em seguida, simule a distribuição ideal (o que você esperaria medir se executasse isso em um computador quântico perfeito um número infinito de vezes) para comparação posterior.

oracle = DiagonalGate([1] * 7 + [-1])
qc = QuantumCircuit(3)
qc.h([0, 1, 2])
qc = qc.compose(grover_operator(oracle))

qc.draw(output="mpl", style="iqp")

Output of the previous code cell

ideal_distribution = Statevector.from_instruction(qc).probabilities_dict()

plot_histogram(ideal_distribution)

Output of the previous code cell

Transpilar

Em seguida, transpile os circuitos para a QPU. Você vai comparar o desempenho do transpilador com optimization_level definido como 0 (mais baixo) contra 3 (mais alto). O nível de otimização mais baixo faz o mínimo necessário para que o circuito rode no dispositivo: mapeia os qubits do circuito para os qubits do dispositivo e adiciona portas swap para permitir todas as operações de dois qubits. O nível de otimização mais alto é muito mais inteligente e usa várias técnicas para reduzir a contagem total de portas. Como as portas de múltiplos qubits têm altas taxas de erro e os qubits decoerêm com o tempo, os circuitos mais curtos devem produzir melhores resultados.

important

Este exemplo usa hardware IBM Quantum®, mas você pode experimentá-lo em qualquer QPU compatível com Qiskit. Seus resultados podem ser diferentes.

A célula a seguir transpila qc para ambos os valores de optimization_level, imprime o número de portas de dois qubits e adiciona os circuitos transpilados a uma lista. Alguns algoritmos do transpilador são aleatorizados, portanto, é definida uma semente para reprodutibilidade.

# Use Qiskit Runtime to run jobs on hardware
from qiskit_ibm_runtime import (
QiskitRuntimeService,
SamplerV2 as Sampler,
)
# Select the backend with the fewest number of jobs in the queue
service = QiskitRuntimeService()
backend = service.least_busy(
operational=True, simulator=False, min_num_qubits=127
)
backend.name
'ibm_marrakesh'
# Need to add measurements to the circuit
qc.measure_all()

# Find the correct two-qubit gate
twoQ_gates = set(["ecr", "cz", "cx"])
for gate in backend.basis_gates:
if gate in twoQ_gates:
twoQ_gate = gate

circuits = []
for optimization_level in [0, 3]:
pm = generate_preset_pass_manager(
optimization_level, backend=backend, seed_transpiler=0
)
t_qc = pm.run(qc)
print(
f"Two-qubit gates (optimization_level={optimization_level}): ",
t_qc.count_ops()[twoQ_gate],
)
circuits.append(t_qc)
Two-qubit gates (optimization_level=0):  21
Two-qubit gates (optimization_level=3): 12

Como os CNOTs geralmente têm uma alta taxa de erro, o circuito transpilado com optimization_level=3 deve ter um desempenho muito melhor.

Outra maneira de melhorar o desempenho é por meio do desacoplamento dinâmico, aplicando uma sequência de portas a qubits ociosos. Isso cancela algumas interações indesejadas com o ambiente. A célula a seguir adiciona o desacoplamento dinâmico ao circuito transpilado com optimization_level=3 e o adiciona à lista.

from qiskit_ibm_runtime.transpiler.passes.scheduling import (
ASAPScheduleAnalysis,
PadDynamicalDecoupling,
)

# Get gate durations so the transpiler knows how long each operation takes
durations = backend.target.durations()

# This is the sequence we'll apply to idling qubits
dd_sequence = [XGate(), XGate()]

# Run scheduling and dynamic decoupling passes on circuit
pm = PassManager(
[
ASAPScheduleAnalysis(durations),
PadDynamicalDecoupling(durations, dd_sequence),
]
)
circ_dd = pm.run(circuits[1])

# Add this new circuit to our list
circuits.append(circ_dd)
circ_dd.draw(output="mpl", style="iqp", idle_wires=False)

Output of the previous code cell

Executar o circuito

Neste ponto, você tem uma lista de circuitos transpilados com diferentes configurações. Em seguida, execute esses circuitos usando o primitivo Sampler e armazene os resultados em result.

sampler = Sampler(backend)
job = sampler.run(
[(circuit) for circuit in circuits], # sample all three circuits
shots=8000,
)
result = job.result()

Ver resultados

Por fim, plote os resultados das execuções no dispositivo em relação à distribuição ideal. Você pode ver que os resultados com optimization_level=3 estão mais próximos da distribuição ideal devido à menor contagem de portas, e optimization_level=3 + dd está ainda mais próximo devido ao desacoplamento dinâmico.

binary_prob = [
{
k: v / res.data.meas.num_shots
for k, v in res.data.meas.get_counts().items()
}
for res in result
]
plot_histogram(
binary_prob + [ideal_distribution],
bar_labels=False,
legend=[
"optimization_level=0",
"optimization_level=3",
"optimization_level=3 + dd",
"ideal distribution",
],
)

Output of the previous code cell

Você pode confirmar isso calculando a fidelidade de Hellinger entre cada conjunto de resultados e a distribuição ideal (quanto maior, melhor, sendo 1 a fidelidade perfeita).

for prob in binary_prob:
print(f"{hellinger_fidelity(prob, ideal_distribution):.3f}")
0.985
0.989
0.988

Próximos passos

Recomendações