Comparer les paramètres du transpileur
Package versions
Le code de cette page a été développé avec les dépendances suivantes. Nous recommandons d'utiliser ces versions ou des versions plus récentes.
qiskit[all]~=2.4.0
qiskit-ibm-runtime~=0.46.1
Différents paramètres du transpileur offrent différents types d'optimisation du circuit, souvent au prix d'un temps de traitement classique plus long. Ce guide parcourt l'ensemble du processus de création, de transpilation et de soumission de circuits pour démontrer comment tester les performances de différents paramètres.
Remarque que le même paramètre peut améliorer les résultats d'un circuit tout en nuisant à un autre. Assure-toi d'inspecter les circuits transpilés résultants avant de les exécuter sur du matériel réel.
Configurer et créer un exemple de circuit
# 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
Crée un petit circuit que le transpileur va essayer d'optimiser. Cet exemple crée un circuit qui exécute l'algorithme de Grover avec un oracle qui marque l'état 111. Ensuite, simule la distribution idéale (ce que tu t'attendrais à mesurer si tu exécutais ce circuit sur un ordinateur quantique parfait un nombre infini de fois) pour la comparer plus tard.
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)
Transpiler
Ensuite, transpile les circuits pour le QPU. Tu compareras les performances du transpileur avec optimization_level réglé à 0 (le plus bas) par rapport à 3 (le plus haut). Le niveau d'optimisation le plus bas fait le strict minimum nécessaire pour faire fonctionner le circuit sur l'appareil ; il mappe les qubits du circuit vers les qubits de l'appareil et ajoute des portes swap pour permettre toutes les opérations à deux qubits. Le niveau d'optimisation le plus élevé est bien plus intelligent et utilise de nombreuses astuces pour réduire le nombre total de portes. Comme les portes multi-qubits ont des taux d'erreur élevés et que les qubits se décohèrent au fil du temps, les circuits plus courts devraient donner de meilleurs résultats.
Cet exemple utilise du matériel IBM Quantum®, mais tu peux l'essayer sur n'importe quel QPU compatible avec Qiskit. Tes résultats pourraient être différents.
La cellule suivante transpile qc pour les deux valeurs de optimization_level, affiche le nombre de portes à deux qubits, et ajoute les circuits transpilés à une liste. Certains algorithmes du transpileur étant aléatoires, elle définit une graine pour la reproductibilité.
# 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
Comme les CNOT ont généralement un taux d'erreur élevé, le circuit transpilé avec optimization_level=3 devrait offrir de bien meilleures performances.
Une autre façon d'améliorer les performances est le découplage dynamique, en appliquant une séquence de portes aux qubits inactifs. Cela annule certaines interactions indésirables avec l'environnement. La cellule suivante ajoute le découplage dynamique au circuit transpilé avec optimization_level=3 et l'ajoute à la liste.
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)
Exécuter le circuit
À ce stade, tu disposes d'une liste de circuits transpilés avec différents paramètres. Ensuite, exécute ces circuits à l'aide de la primitive Sampler et stocke les résultats dans result.
sampler = Sampler(backend)
job = sampler.run(
[(circuit) for circuit in circuits], # sample all three circuits
shots=8000,
)
result = job.result()
Afficher les résultats
Enfin, trace les résultats des exécutions sur l'appareil par rapport à la distribution idéale. Tu peux constater que les résultats avec optimization_level=3 sont plus proches de la distribution idéale en raison du nombre de portes réduit, et que optimization_level=3 + dd est encore plus proche grâce au découplage dynamique.
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",
],
)
Tu peux confirmer cela en calculant la fidélité de Hellinger entre chaque ensemble de résultats et la distribution idéale (plus la valeur est élevée, mieux c'est, et 1 correspond à une fidélité parfaite).
for prob in binary_prob:
print(f"{hellinger_fidelity(prob, ideal_distribution):.3f}")
0.985
0.989
0.988
Prochaines étapes
-
Explore des ressources avancées sur la transpilation, notamment :
-
Parcours les tutoriels disponibles.