Confrontare le impostazioni del transpiler
Package versions
The code on this page was developed using the following requirements. We recommend using these versions or newer.
qiskit[all]~=2.4.0
qiskit-ibm-runtime~=0.46.1
Diverse impostazioni del transpiler forniscono diversi tipi di ottimizzazione al circuito, spesso a scapito di un tempo di elaborazione classica più lungo. Questa guida percorre l'intero processo di creazione, transpilazione e invio di circuiti per dimostrare come testare le prestazioni delle varie impostazioni.
Nota che la stessa impostazione potrebbe migliorare i risultati di un circuito mentre ne ostacola un altro. Assicurati di ispezionare i circuiti transpilati risultanti prima di eseguirli su hardware reale.
Configurazione e creazione del circuito di esempio
# 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
Crea un piccolo circuito che il transpiler proverà a ottimizzare. In questo esempio viene creato un circuito che esegue l'algoritmo di Grover con un oracolo che contrassegna lo stato 111. Poi simula la distribuzione ideale (quella che ti aspetteresti di misurare eseguendo il circuito su un computer quantistico perfetto un numero infinito di volte) per un confronto successivo.
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")
ideal_distribution = Statevector.from_instruction(qc).probabilities_dict()
plot_histogram(ideal_distribution)
Transpilazione
Ora transpila i circuiti per la QPU. Confronterai le prestazioni del transpiler con optimization_level impostato a 0 (minimo) rispetto a 3 (massimo). Il livello di ottimizzazione minimo fa il minimo indispensabile per far girare il circuito sul dispositivo: mappa i qubit del circuito sui qubit del dispositivo e aggiunge gate di swap per consentire tutte le operazioni a due qubit. Il livello di ottimizzazione massimo è molto più intelligente e utilizza numerosi accorgimenti per ridurre il numero totale di gate. Poiché i gate a più qubit hanno tassi di errore elevati e i qubit decoerono nel tempo, i circuiti più corti dovrebbero produrre risultati migliori.
Questo esempio utilizza hardware IBM Quantum®, ma puoi provarlo su qualsiasi QPU compatibile con Qiskit. I tuoi risultati potrebbero essere diversi.
La cella seguente transpila qc per entrambi i valori di optimization_level, stampa il numero di gate a due qubit e aggiunge i circuiti transpilati a una lista. Alcuni algoritmi del transpiler sono randomizzati, quindi viene impostato un seed per la riproducibilità.
# 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
Poiché i CNOT hanno generalmente un tasso di errore elevato, il circuito transpilato con optimization_level=3 dovrebbe ottenere prestazioni molto migliori.
Un altro modo per migliorare le prestazioni è il dynamic decoupling, che consiste nell'applicare una sequenza di gate ai qubit in attesa. Questo annulla alcune interazioni indesiderate con l'ambiente. La cella seguente aggiunge il dynamic decoupling al circuito transpilato con optimization_level=3 e lo aggiunge alla 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)
Esecuzione del circuito
A questo punto hai una lista di circuiti transpilati con diverse impostazioni. Ora esegui questi circuiti usando la primitiva Sampler e salva i risultati in result.
sampler = Sampler(backend)
job = sampler.run(
[(circuit) for circuit in circuits], # sample all three circuits
shots=8000,
)
result = job.result()
Visualizzazione dei risultati
Per concludere, visualizza i risultati delle esecuzioni sul dispositivo rispetto alla distribuzione ideale. Puoi vedere che i risultati con optimization_level=3 sono più vicini alla distribuzione ideale grazie al minor numero di gate, e optimization_level=3 + dd è ancora più vicino grazie al dynamic decoupling.
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",
],
)
Puoi confermare questo calcolando la fedeltà di Hellinger tra ogni insieme di risultati e la distribuzione ideale (più alto è meglio, e 1 corrisponde alla fedeltà perfetta).
for prob in binary_prob:
print(f"{hellinger_fidelity(prob, ideal_distribution):.3f}")
0.985
0.989
0.988
Passi successivi
-
Esplora alcune risorse avanzate sulla transpilazione, come:
-
Esplora i tutorial disponibili.