Confronta le impostazioni del transpiler
Il codice presente in questa pagina è stato sviluppato sulla base dei seguenti requisiti. Si consiglia di utilizzare queste versioni o quelle più recenti.
qiskit[all]~=2.5.1 qiskit-ibm-runtime~=0.47.0
Impostazioni diverse del transpiler offrono diversi tipi di ottimizzazione del circuito, spesso a scapito di tempi di elaborazione classici più lunghi. Questa guida illustra l'intero processo di creazione, transpilazione e invio dei circuiti, al fine di dimostrare come testare le prestazioni delle diverse impostazioni.
Si noti che la stessa impostazione potrebbe migliorare i risultati di un circuito e compromettere quelli di un altro. Assicurati di controllare i circuiti transpilati risultanti prima di eseguirli su hardware reale.
Configurare e creare un circuito di esempio
# 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_fidelityCrea un piccolo circuito che il transpiler possa provare a ottimizzare. Questo esempio crea un circuito che esegue l'algoritmo di Grover con un oracolo che contrassegna lo stato 111. Successivamente, simula la distribuzione ideale (quella che ti aspetteresti di misurare se eseguissi questo calcolo 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")Output:
ideal_distribution = Statevector.from_instruction(qc).probabilities_dict()
plot_histogram(ideal_distribution)Output:
Transpile
Successivamente, transpilare i circuiti per la QPU. Confronterai le prestazioni del transpiler con optimization_level impostato su 0 (il più basso) rispetto a 3 (il più alto). Il livello di ottimizzazione più basso fa il minimo indispensabile per far funzionare il circuito sul dispositivo; mappa i qubit del circuito sui qubit del dispositivo e aggiunge porte di scambio per consentire tutte le operazioni a due qubit. Il livello di ottimizzazione più elevato è molto più intelligente e utilizza numerosi accorgimenti per ridurre il numero complessivo di porte logiche. Poiché i gate multi-qubit hanno alti tassi di errore e i qubit decoeriscono nel tempo, i circuiti più corti dovrebbero dare risultati migliori.
Questo esempio utilizza l'hardware dell' IBM Quantum®, ma è possibile provarlo su qualsiasi QPU compatibile con Qiskit. I tuoi risultati potrebbero essere diversi.
La seguente cella transpone qc per entrambi i valori di optimization_level, stampa il numero di porte a due qubit e aggiunge i circuiti transposti a un elenco. Alcuni algoritmi del transpiler sono randomizzati, quindi imposta un seme per garantire la riproducibilità.
# Use IBM Quantum Compute Service 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.nameOutput:
'ibm_fez'
# 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)Output:
Two-qubit gates (optimization_level=0): 21
Two-qubit gates (optimization_level=3): 12
Poiché i CNOT hanno solitamente un alto tasso di errore, il circuito transpiled con optimization_level=3 dovrebbe funzionare molto meglio.
Un altro modo per migliorare le prestazioni consiste nel disaccoppiamento dinamico, applicando una sequenza di porte ai qubit inattivi. Questo elimina alcune interazioni indesiderate con l'ambiente. La cella seguente aggiunge il disaccoppiamento dinamico al circuito compilato con optimization_level=3 e lo aggiunge all'elenco.
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:
Esegui il circuito
A questo punto, disponi di un elenco di circuiti transpilati con impostazioni diverse. Successivamente, esegui questi circuiti utilizzando 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()Visualizza risultati
Infine, traccia un grafico dei risultati ottenuti dalle prove effettuate sul dispositivo rispetto alla distribuzione ideale. Si può notare che i risultati con optimization_level=3 sono più vicini alla distribuzione ideale grazie al minor numero di gate, mentre optimization_level=3 + dd è ancora più vicino grazie al disaccoppiamento dinamico.
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:
È possibile verificarlo calcolando la fedeltà di Hellinger tra ciascun insieme di risultati e la distribuzione ideale (più è alto il valore, meglio è; 1 indica una fedeltà perfetta).
for prob in binary_prob:
print(f"{hellinger_fidelity(prob, ideal_distribution):.3f}")Output:
0.774
0.978
0.979
Passi successivi
-
Scopri alcune risorse avanzate sulla transpilazione, come ad esempio:
-
Sfoglia i tutorial disponibili.