Zum Hauptinhalt springen

Transpiler-Einstellungen vergleichen

Paketversionen

Der Code auf dieser Seite wurde mit den folgenden Anforderungen entwickelt. Wir empfehlen, diese oder neuere Versionen zu verwenden.

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

Verschiedene Transpiler-Einstellungen bieten unterschiedliche Arten der Optimierung für den Circuit, oft auf Kosten einer längeren klassischen Verarbeitungszeit. Dieser Leitfaden führt dich durch den vollständigen Prozess des Erstellens, Transpilierens und Übermittelns von Circuits, um die Leistung verschiedener Einstellungen zu testen.

Beachte, dass dieselbe Einstellung die Ergebnisse eines Circuits verbessern, aber einen anderen Circuit beeinträchtigen kann. Inspiziere die resultierenden transpilierten Circuits, bevor du sie auf echter Hardware ausführst.

Einrichten und Beispiel-Circuit erstellen

# 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

Erstelle einen kleinen Circuit, den der Transpiler zu optimieren versucht. Dieses Beispiel erstellt einen Circuit, der Grover's Algorithmus mit einem Orakel ausführt, das den Zustand 111 markiert. Simuliere anschließend die ideale Verteilung (was du messen würdest, wenn du dies unendlich oft auf einem perfekten Quantencomputer ausführen würdest) für einen späteren Vergleich.

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

Transpilieren

Als nächstes transpilierst du die Circuits für den QPU. Du wirst die Leistung des Transpilers mit optimization_level auf 0 (niedrigste) gegenüber 3 (höchste) vergleichen. Der niedrigste Optimierungsgrad macht das absolute Minimum, um den Circuit auf dem Gerät auszuführen; er ordnet die Circuit-Qubits den Geräte-Qubits zu und fügt Swap-Gates hinzu, um alle Zwei-Qubit-Operationen zu ermöglichen. Der höchste Optimierungsgrad ist viel intelligenter und verwendet viele Tricks, um die Gesamtzahl der Gates zu reduzieren. Da Multi-Qubit-Gates hohe Fehlerquoten haben und Qubits im Laufe der Zeit dekohärieren, sollten die kürzeren Circuits bessere Ergebnisse liefern.

important

Dieses Beispiel verwendet IBM Quantum®-Hardware, aber du kannst es auf jedem Qiskit-kompatiblen QPU ausprobieren. Deine Ergebnisse könnten abweichen.

Die folgende Zelle transpiliert qc für beide Werte von optimization_level, gibt die Anzahl der Zwei-Qubit-Gates aus und fügt die transpilierten Circuits einer Liste hinzu. Einige der Transpiler-Algorithmen sind zufällig, daher wird ein Seed für die Reproduzierbarkeit gesetzt.

# 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

Da CNOTs üblicherweise eine hohe Fehlerrate haben, sollte der mit optimization_level=3 transpilierte Circuit deutlich besser abschneiden.

Eine weitere Möglichkeit, die Leistung zu verbessern, ist Dynamical Decoupling, indem du eine Sequenz von Gates auf ruhende Qubits anwendest. Damit werden unerwünschte Wechselwirkungen mit der Umgebung teilweise kompensiert. Die folgende Zelle fügt Dynamic Decoupling zum mit optimization_level=3 transpilierten Circuit hinzu und ergänzt ihn in der 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 of the previous code cell

Den Circuit ausführen

Zu diesem Zeitpunkt hast du eine Liste von Circuits, die mit verschiedenen Einstellungen transpiliert wurden. Als nächstes führe diese Circuits mit dem Sampler Primitive aus und speichere die Ergebnisse in result.

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

Ergebnisse anzeigen

Zum Schluss stelle die Ergebnisse der Gerätedurchläufe der idealen Verteilung gegenüber. Du kannst sehen, dass die Ergebnisse mit optimization_level=3 aufgrund der geringeren Gate-Anzahl näher an der idealen Verteilung liegen, und optimization_level=3 + dd ist aufgrund des Dynamical Decoupling noch näher dran.

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

Du kannst dies bestätigen, indem du die Hellinger-Fidelität zwischen jedem Ergebnissatz und der idealen Verteilung berechnest (höher ist besser, und 1 bedeutet perfekte Fidelität).

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

Nächste Schritte

Empfehlungen