Fehlerreduktionsoptionen mit dem Schätz-Basiselement kombinieren
Geschätzte Laufzeit: 7 Minuten auf einem Heron- r2 -Prozessor (HINWEIS: Dies ist nur eine Schätzung. (Die Laufzeit kann variieren.)
Lernziele
- Wie dynamische Entkopplung, Messfehlerminderung, Gate-Twirling und rauschfreie Extrapolation selektiv auf Hardware umgesetzt werden.
- Wie sie sich hinsichtlich ihrer Fähigkeit zur Dämpfung von Hardwaregeräuschen vergleichen.
Voraussetzungen
- Die Grundlagen der dynamischen Entkopplung, der Minderung von Messfehlern, des Gate-Twirling und der rauschfreien Extrapolation, wie in diesem Leitfaden beschrieben.
Hintergrund
In diesem Tutorial werden die Optionen zur Fehlerunterdrückung und Fehlerminderung behandelt, die mit der „ IBM Quantum “-Estimator-Primitive zur Verfügung stehen. In diesem Tutorial wird gezeigt, wie die folgenden Methoden jeweils einzeln implementiert werden:
- Dynamische Entkopplung
- Verringerung von Messfehlern
- Torzwirbeln
- Null-Rausch-Extrapolation (ZNE)
Beachten Sie, dass eine Alternative zur individuellen Implementierung dieser Techniken darin besteht, sie mithilfe einer Resilienzstufe zu implementieren, wobei die Werte resilience_level 0, 1 und 2 annimmt:
- 0: Es wurden keine Maßnahmen zur Risikominderung umgesetzt.
- 1: Es wurde eine Maßnahme zur Minderung von Messfehlern implementiert.
- 2: Gate-Twirling, Minderung von Messfehlern und ZNE sind implementiert.
In diesem Tutorial erstellen Sie eine Schaltung und eine Beobachtungsgröße und reichen Jobs unter Verwendung der Estimator-Primitive mit verschiedenen Kombinationen von Einstellungen zur Fehlerminderung ein. Anschließend stellen Sie die Ergebnisse grafisch dar, um die Auswirkungen der verschiedenen Einstellungen zu beobachten. Der Großteil des Tutorials verwendet eine Schaltung mit 10 Qubits, um die Visualisierung zu vereinfachen; am Ende werden Sie den Arbeitsablauf auf 50 Qubits ausweiten.
Anforderungen
Bevor Sie mit dieser Anleitung beginnen, stellen Sie sicher, dass Sie Folgendes installiert haben:
- Qiskit SDK v2.1 oder später, mit Visual isierungsunterstützung
- Qiskit Runtime v0.40 oder später (
pip install qiskit-ibm-runtime)
Einrichtung
import matplotlib.pyplot as plt
import numpy as np
from qiskit.circuit.library import efficient_su2, unitary_overlap
from qiskit.quantum_info import SparsePauliOp
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit_ibm_runtime import QiskitRuntimeService
from qiskit_ibm_runtime import Batch, EstimatorV2 as EstimatorBeispiel für einen Simulator im kleinen Maßstab
Wir werden diesen Schritt überspringen, da die Behebung von Laufzeitfehlern auf Simulatoren nicht unterstützt wird.
Beispiel für Hardware
Schritt 1: Klassische Eingaben einem Quantenproblem zuordnen
Diese Anleitung geht davon aus, dass das klassische Problem bereits auf Quanten abgebildet wurde. Beginnen Sie mit dem Aufbau einer Schaltung und eines Messgeräts. Die hier verwendeten Techniken lassen sich auf viele verschiedene Arten von Schaltungen anwenden, aber der Einfachheit halber wird in dieser Anleitung die efficient_su2 Schaltung aus der Qiskit-Schaltungsbibliothek verwendet.
efficient_su2 ist ein parametrisierter Quantenschaltkreis, der so konzipiert ist, dass er auf Quantenhardware mit begrenzter Qubit-Konnektivität effizient ausgeführt werden kann, dabei aber dennoch über genügend Ausdruckskraft verfügt, um Probleme in Anwendungsbereichen wie Optimierung und Chemie zu lösen. Es wird aufgebaut, indem Schichten aus parametrisierten Einzelqubit-Gattern mit einer Schicht, die ein festes Muster aus Zwei-Qubit-Gattern enthält, für eine bestimmte Anzahl von Wiederholungen abwechselnd angeordnet werden. Das Muster der Zwei-Qubit-Gatter kann vom Benutzer festgelegt werden. Hier können Sie das integrierte pairwise Muster verwenden, da es die Schaltungstiefe minimiert, indem es die Zwei-Qubit-Gatter so dicht wie möglich anordnet. Dieses Muster lässt sich ausschließlich mit linearen Qubit-Verbindungen realisieren.
n_qubits = 10
reps = 1
circuit = efficient_su2(n_qubits, entanglement="pairwise", reps=reps)
circuit.decompose().draw("mpl", scale=0.7)Output:
Nehmen wir als Observable den Pauli- -Operator, der auf das letzte Qubit wirkt: . Beachte, dass die Tatsache, dass das letzte Qubit dem ersten Element dieser Zeichenfolge entspricht, darauf zurückzuführen ist, dass Qiskit die Little-Endian-Notation verwendet.
# Z on the last qubit (index -1) with coefficient 1.0
observable = SparsePauliOp.from_sparse_list(
[("Z", [-1], 1.0)], num_qubits=n_qubits
)Nun könnten Sie Ihre Schaltung in Betrieb nehmen und die Messgröße erfassen. Sie möchten jedoch auch die Ausgabe des Quantenbauelements mit der richtigen Antwort vergleichen – also mit dem theoretischen Wert der Observable, wenn die Schaltung fehlerfrei ausgeführt worden wäre. Bei kleinen Quantenschaltungen lässt sich dieser Wert durch Simulation der Schaltung auf einem klassischen Computer berechnen, bei größeren Schaltungen im industriellen Maßstab ist dies jedoch nicht möglich. Sie können dieses Problem mit der „Mirror-Circuit“-Technik (auch als „Compute-Uncompute“ bekannt) umgehen, die sich gut zur Leistungsmessung von Quantenbauelementen eignet.
Spiegelschaltung
Bei der Spiegelschaltungstechnik wird die Schaltung mit ihrer Umkehrschaltung verknüpft, die durch Invertieren der einzelnen Gatter der Schaltung in umgekehrter Reihenfolge gebildet wird. Die resultierende Schaltung implementiert den Identitätsoperator, der trivialerweise simuliert werden kann. Da die Struktur der ursprünglichen Schaltung in der Spiegelschaltung erhalten bleibt, gibt die Ausführung der Spiegelschaltung immer noch einen Eindruck davon, wie das Quantengerät auf der ursprünglichen Schaltung funktionieren würde.
Die folgende Codezelle weist Ihrer Schaltung zufällige Parameter zu und erstellt anschließend mithilfe der unitary_overlap Klasse die Spiegel-Schaltung. Fügen Sie vor dem Spiegeln der Schaltung eine Bar riereanweisung ein, um zu verhindern, dass der Transpiler die beiden Teile der Schaltung auf beiden Seiten der Barriere zusammenführt und dadurch eine transpilierte Schaltung ohne Gatter entsteht.
# Generate random parameters
rng = np.random.default_rng(1234)
params = rng.uniform(-np.pi, np.pi, size=circuit.num_parameters)
# Assign the parameters to the circuit
assigned_circuit = circuit.assign_parameters(params)
# Add a barrier to prevent circuit optimization of mirrored operators
assigned_circuit.barrier()
# Construct mirror circuit
mirror_circuit = unitary_overlap(assigned_circuit, assigned_circuit)
mirror_circuit.decompose().draw("mpl", scale=0.7)Output:
Schritt 2: Optimierung des Problems für die Ausführung auf Quantenhardware
Sie müssen Ihre Schaltung optimieren, bevor Sie sie auf der Hardware betreiben. Dieser Prozess umfasst mehrere Schritte:
- Wählen Sie ein Qubit-Layout, das die virtuellen Qubits Ihrer Schaltung auf physische Qubits in der Hardware abbildet.
- Fügen Sie nach Bedarf Swap-Gates ein, um Wechselwirkungen zwischen nicht verbundenen Qubits zu steuern.
- Übersetzen Sie die Gatter in Ihrer Schaltung in ISA-Befehle (Instruction Set Architecture), die direkt in der Hardware ausgeführt werden können.
- Optimieren Sie die Schaltung, um die Schaltungstiefe und die Anzahl der Gatter zu minimieren.
Der in Qiskit integrierte Transpiler kann alle diese Schritte für Sie ausführen. Da dieses Beispiel eine hardwareeffiziente Schaltung verwendet, sollte der Transpiler in der Lage sein, ein Qubit-Layout auszuwählen, bei dem keine Swap-Gates für Routing-Interaktionen eingefügt werden müssen.
Bevor Sie Ihre Schaltung optimieren, müssen Sie das zu verwendende Hardwaregerät auswählen. Die folgende Codezelle fordert das am wenigsten ausgelastete Gerät mit mindestens 127 Qubits an.
service = QiskitRuntimeService()
backend = service.least_busy(
operational=True, simulator=False, min_num_qubits=127
)print(backend)Output:
<IBMBackend('ibm_fez')>
Sie können Ihren Schaltkreis auf das Backend Ihrer Wahl transpilieren, indem Sie einen Pass-Manager erstellen und diesen anschließend auf den Schaltkreis anwenden. Eine einfache Möglichkeit, einen Pass-Manager zu erstellen, ist die Verwendung der generate_preset_pass_manager Funktion. Eine ausführlichere Erläuterung zum Transpiling mit Pass-Managern finden Sie unter „Transpile with pass managers“.
pass_manager = generate_preset_pass_manager(
optimization_level=3, backend=backend, seed_transpiler=1234
)
isa_circuit = pass_manager.run(mirror_circuit)
isa_circuit.draw("mpl", idle_wires=False, scale=0.7, fold=-1)Output:
Die transpilierte Schaltung enthält nun ausschließlich ISA-Befehle. Alle Gatter wurden in „ “-Gatter, „ “-Rotationen und CZ-Gatter zerlegt.
Der Transpilierungsprozess hat die virtuellen Qubits des Schaltkreises auf physische Qubits in der Hardware abgebildet. Die Informationen über das Qubit-Layout sind im layout -Attribut der umgesetzten Schaltung gespeichert. Die Observable wurde auch in Bezug auf die virtuellen Qubits definiert, also müssen Sie dieses Layout auf die Observable anwenden, was Sie mit der apply_layout methode von SparsePauliOp.
isa_observable = observable.apply_layout(isa_circuit.layout)
print("Original observable:")
print(observable)
print()
print("Observable with layout applied:")
print(isa_observable)Output:
Original observable:
SparsePauliOp(['ZIIIIIIIII'],
coeffs=[1.+0.j])
Observable with layout applied:
SparsePauliOp(['IIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII'],
coeffs=[1.+0.j])
Schritt 3: Mit „ Qiskit primitives “ ausführen
Sie sind nun bereit, Ihre Schaltung mit dem Estimator-Primitiv zu betreiben.
Hier führen Sie fünf separate Aufträge aus, wobei Sie zunächst keine Fehlerunterdrückung oder -minderung anwenden und nach und nach verschiedene Optionen zur Fehlerunterdrückung und -minderung aktivieren, die im „ IBM Quantum Compute Service“ verfügbar sind. Informationen zu den Optionen finden Sie auf den folgenden Seiten:
- Überblick über alle Optionen
- Dynamische Entkopplung
- Robustheit, einschließlich Minderung von Messfehlern und rauschfreier Extrapolation (ZNE)
- Wirbeln
Da diese Jobs unabhängig voneinander ausgeführt werden können, können Sie den Batch-Modus nutzen, damit Quantum Compute den Zeitpunkt ihrer Ausführung optimieren kann.
pub = (isa_circuit, isa_observable)
jobs = []
with Batch(backend=backend) as batch:
estimator = Estimator(mode=batch)
estimator.options.environment.job_tags = [
"TUT_CEM_SS"
] # add tag for this small scale job
# Set number of shots
estimator.options.default_shots = 100_000
# Disable runtime compilation and error mitigation
estimator.options.resilience_level = 0
# Run job with no error mitigation
job0 = estimator.run([pub])
jobs.append(job0)
# Add dynamical decoupling (DD)
estimator.options.dynamical_decoupling.enable = True
estimator.options.dynamical_decoupling.sequence_type = "XpXm"
job1 = estimator.run([pub])
jobs.append(job1)
# Add readout error mitigation (DD + TREX)
estimator.options.resilience.measure_mitigation = True
job2 = estimator.run([pub])
jobs.append(job2)
# Add gate twirling (DD + TREX + Gate Twirling)
estimator.options.twirling.enable_gates = True
estimator.options.twirling.num_randomizations = "auto"
job3 = estimator.run([pub])
jobs.append(job3)
# Add zero-noise extrapolation (DD + TREX + Gate Twirling + ZNE)
estimator.options.resilience.zne_mitigation = True
estimator.options.resilience.zne.noise_factors = (1, 3, 5)
estimator.options.resilience.zne.extrapolator = ("exponential", "linear")
job4 = estimator.run([pub])
jobs.append(job4)Schritt 4: Nachbearbeitung und Rückgabe des Ergebnisses im gewünschten klassischen Format
Schließlich können Sie die Daten analysieren. Hier rufen Sie die Auftragsergebnisse ab, extrahieren daraus die gemessenen Erwartungswerte und stellen die Werte einschließlich Fehlerbalken mit einer Standardabweichung dar.
# Retrieve the job results
results = [job.result() for job in jobs]
# Unpack the PUB results (there's only one PUB result in each job result)
pub_results = [result[0] for result in results]
# Unpack the expectation values and standard errors
expectation_vals = np.array(
[float(pub_result.data.evs) for pub_result in pub_results]
)
standard_errors = np.array(
[float(pub_result.data.stds) for pub_result in pub_results]
)
# Plot the expectation values
fig, ax = plt.subplots()
labels = ["No mitigation", "+ DD", "+ TREX", "+ Twirling", "+ ZNE"]
ax.bar(
range(len(labels)),
expectation_vals,
yerr=standard_errors,
label="experiment",
)
ax.axhline(y=1.0, color="gray", linestyle="--", label="ideal")
ax.set_xticks(range(len(labels)))
ax.set_xticklabels(labels)
ax.set_ylabel("Expectation value")
ax.legend(loc="upper left")
plt.show()Output:
Auf dieser kleinen Skala ist es schwierig, die Wirkung der meisten Fehlerbegrenzungsverfahren zu erkennen, aber die Extrapolation ohne Rauschen führt zu einer spürbaren Verbesserung. Beachten Sie jedoch, dass diese Verbesserung nicht umsonst ist, da das ZNE-Ergebnis auch einen größeren Fehlerbalken aufweist.
Beispiel für groß angelegte Hardware
Bei der Entwicklung eines Experiments ist es sinnvoll, mit einer kleinen Schaltung zu beginnen, um Visualisierungen und Simulationen zu vereinfachen. Nachdem Sie unseren Workflow nun auf einer 10-Qubit-Schaltung entwickelt und getestet haben, können Sie ihn auf bis zu 50 Qubits skalieren. Die folgende Codezelle wiederholt alle Schritte dieser Anleitung, wendet sie nun jedoch auf eine 50-Qubit-Schaltung an.
n_qubits = 50
reps = 1
# Construct circuit and observable
circuit = efficient_su2(n_qubits, entanglement="pairwise", reps=reps)
observable = SparsePauliOp.from_sparse_list(
[("Z", [-1], 1.0)], num_qubits=n_qubits
)
# Assign parameters to circuit
params = rng.uniform(-np.pi, np.pi, size=circuit.num_parameters)
assigned_circuit = circuit.assign_parameters(params)
assigned_circuit.barrier()
# Construct mirror circuit
mirror_circuit = unitary_overlap(assigned_circuit, assigned_circuit)
# Transpile circuit and observable
isa_circuit = pass_manager.run(mirror_circuit)
isa_observable = observable.apply_layout(isa_circuit.layout)
# Run jobs
pub = (isa_circuit, isa_observable)
jobs = []
with Batch(backend=backend) as batch:
estimator = Estimator(mode=batch)
estimator.options.environment.job_tags = [
"TUT_CEM_LS"
] # add tag for this large scale job
# Set number of shots
estimator.options.default_shots = 100_000
# Disable runtime compilation and error mitigation
estimator.options.resilience_level = 0
# Run job with no error mitigation
job0 = estimator.run([pub])
jobs.append(job0)
# Add dynamical decoupling (DD)
estimator.options.dynamical_decoupling.enable = True
estimator.options.dynamical_decoupling.sequence_type = "XpXm"
job1 = estimator.run([pub])
jobs.append(job1)
# Add readout error mitigation (DD + TREX)
estimator.options.resilience.measure_mitigation = True
job2 = estimator.run([pub])
jobs.append(job2)
# Add gate twirling (DD + TREX + Gate Twirling)
estimator.options.twirling.enable_gates = True
estimator.options.twirling.num_randomizations = "auto"
job3 = estimator.run([pub])
jobs.append(job3)
# Add zero-noise extrapolation (DD + TREX + Gate Twirling + ZNE)
estimator.options.resilience.zne_mitigation = True
estimator.options.resilience.zne.noise_factors = (1, 3, 5)
estimator.options.resilience.zne.extrapolator = ("exponential", "linear")
job4 = estimator.run([pub])
jobs.append(job4)
# Retrieve the job results
results = [job.result() for job in jobs]
# Unpack the PUB results (there's only one PUB result in each job result)
pub_results = [result[0] for result in results]
# Unpack the expectation values and standard errors
expectation_vals = np.array(
[float(pub_result.data.evs) for pub_result in pub_results]
)
standard_errors = np.array(
[float(pub_result.data.stds) for pub_result in pub_results]
)
# Plot the expectation values
fig, ax = plt.subplots()
labels = ["No mitigation", "+ DD", "+ TREX", "+ Twirling", "+ ZNE"]
ax.bar(
range(len(labels)),
expectation_vals,
yerr=standard_errors,
label="experiment",
)
ax.axhline(y=1.0, color="gray", linestyle="--", label="ideal")
ax.set_xticks(range(len(labels)))
ax.set_xticklabels(labels)
ax.set_ylabel("Expectation value")
ax.legend(loc="upper left")
plt.show()Output:
Wenn Sie die 50-Qubit-Ergebnisse mit den 10-Qubit-Ergebnissen von vorhin vergleichen, können Sie Folgendes feststellen (Ihre Ergebnisse können von Lauf zu Lauf unterschiedlich sein):
- Bei allen Experimenten liegen die Ergebnisse näher am Idealwert, und alle Fehlerbalken sind kleiner.
- Die Einführung der dynamischen Entkopplung könnte die Leistung im Vergleich zum Fall ohne Abhilfemaßnahmen verschlechtert haben. Das ist nicht verwunderlich, da die Schaltung sehr dicht ist. Die dynamische Entkopplung ist vor allem dann sinnvoll, wenn es in der Schaltung große Lücken gibt, in denen die Qubits untätig sind, ohne dass Gatter an sie angelegt werden. Wenn diese Lücken nicht vorhanden sind, ist die dynamische Entkopplung nicht wirksam und kann aufgrund von Fehlern in den Entkopplungsimpulsen selbst die Leistung sogar verschlechtern. Der 10-Qubit-Schaltkreis war möglicherweise zu klein, als dass wir diesen Effekt hätten beobachten können.
- Bei der rauschfreien Extrapolation liegt das Ergebnis sehr nahe am Idealwert. Dies verdeutlicht die Leistungsfähigkeit von ZNE.
Nächste Schritte
Wenn Sie diese Arbeit interessant fanden, könnten Sie auch an den folgenden Materialien zu weiteren Techniken der Fehlerminderung und -unterdrückung interessiert sein, die in diesem Tutorial nicht erwähnt wurden: