Skip to main content
IBM Quantum Platform

Comparer les paramètres du transpilateur

  • Le code présenté sur cette page a été développé en tenant compte des exigences suivantes. Nous vous recommandons d'utiliser ces versions ou des versions plus récentes.

    qiskit[all]~=2.5.1
    qiskit-ibm-runtime~=0.47.0
    

Les différents paramètres du transpileur permettent d'appliquer divers types d'optimisation au circuit, souvent au prix d'un allongement du temps de traitement classique. Ce guide vous accompagne tout au long du processus de création, de compilation et de soumission de circuits afin de vous permettre de tester les performances de différents paramètres.

Il convient de noter qu'un même réglage peut améliorer les performances d'un circuit tout en nuisant à celles d'un autre. Veillez à vérifier les circuits transpilés obtenus avant de les exécuter sur du matériel réel.


Configurer et créer un circuit type

# 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éez un petit circuit que le transcompilateur pourra 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, simulez la distribution idéale (ce que vous vous attendriez à mesurer si vous exécutiez cela un nombre infini de fois sur un ordinateur quantique parfait) pour pouvoir la comparer ultérieurement.

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:

Output of the previous code cell
ideal_distribution = Statevector.from_instruction(qc).probabilities_dict()

plot_histogram(ideal_distribution)

Output:

Output of the previous code cell

Transpiler

Ensuite, transcompilez les circuits pour le QPU. Vous comparerez les performances du transcompilateur avec optimization_level réglé sur 0 (le plus bas) par rapport à 3 (le plus élevé). Le niveau d'optimisation le plus bas effectue le strict minimum nécessaire pour faire fonctionner le circuit sur le dispositif; il mappe les qubits du circuit aux qubits du dispositif et ajoute des portes d'échange pour permettre toutes les opérations à deux qubits. Le niveau d'optimisation le plus élevé est beaucoup plus intelligent et utilise de nombreuses astuces pour réduire le nombre total de portes. Étant donné que les portes multi-qubits ont des taux d'erreur élevés et que les qubits se décohèrent avec le temps, les circuits plus courts devraient donner de meilleurs résultats.

Important

Cet exemple utilise le matériel d' IBM Quantum®, mais vous pouvez l'essayer sur n'importe quel QPU compatible avec Qiskit. Vos résultats peuvent varier.

La cellule suivante transcompile qc pour les deux valeurs de optimization_level, affiche le nombre de portes à deux qubits et ajoute les circuits transcompilés à une liste. Certains algorithmes du transcompilateur sont aléatoires, il définit donc une graine pour assurer la reproductibilité.

# 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.name

Output:

'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

Étant donné que 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 consiste à recourir au découplage dynamique, en appliquant une séquence de portes à des qubits au repos. Cela permet d'éliminer certaines interactions indésirables avec l'environnement. La cellule suivante ajoute un 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)

Output:

Output of the previous code cell

Effectuez le circuit

À ce stade, vous disposez d'une liste de circuits transpilés avec différents paramètres. Ensuite, exécutez ces circuits à l'aide de la primitive Sampler et enregistrez 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, tracez les résultats des essais sur l'appareil en fonction de la distribution idéale. On constate que les résultats obtenus avec optimization_level=3 se rapprochent davantage de la distribution idéale grâce au nombre réduit de grilles, et que optimization_level=3 + dd s'en rapproche encore davantage 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",
    ],
)

Output:

Output of the previous code cell

Vous pouvez le vérifier en calculant la fidélité de Hellinger entre chaque ensemble de résultats et la distribution idéale (plus le chiffre est élevé, mieux c'est, et 1 correspond à une fidélité parfaite).

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

Output:

0.774
0.978
0.979

Etapes suivantes

Recommandations
Cette page a-t-elle été utile ?
Signaler un bogue, une coquille ou proposer du contenu sur GitHub.