Probabilistische Fehlerkompensation mit schattierten Lichtkegeln
Geschätzte Laufzeit: 10 Minuten auf einem Heron-Prozessor (HINWEIS: Dies ist lediglich eine Schätzung. (Die Laufzeit kann variieren.)
Lernziele
- Was die probabilistische Fehlerkompensation (PEC) ist und warum ihr Abtast-Overhead exponentiell mit dem auf die Schaltung einwirkenden Gesamtrauschen wächst
- Wie „Shaded Lightcones“ (SLC) den Beitrag jedes Rauschterms zur Zielgröße begrenzen, sodass Sie das Budget für die Rauschunterdrückung dort einsetzen können, wo es darauf ankommt – und wie Sie durch die Abschattung – über die bereits durch einen rein geometrischen Lichtkegel erzielte Reduzierung des Overheads ohne Bias hinaus – einen begrenzten Restbias gegen einen noch geringeren Abtast-Overhead eintauschen können
- Wie man mit
NoiseLearnerV3das Layer-Rauschen erlernt und übersamplomaticund dieExecutorPrimitive Anti-Rauschen einspeist - Wie man PEC und PEC+SLC mit TREX und Nachselektion kombiniert, um einen Erwartungswert auf der Hardware zu schätzen
Voraussetzungen
- Der Qiskit-Patterns -Workflow
- Berechnung der Erwartungswerte einer beobachtbaren Größe mithilfe der Estimator -Primitive
- Fehlerminderungstechniken wie „Pauli Twirling“ und „TREX“, die im Abschnitt „Kombination von Fehlerminderungsoptionen mit der Estimator-Primitive“ behandelt werden
Hintergrund
In diesem Tutorial wird gezeigt, wie sich Fehler mithilfe des Add-ons „Shaded Light Cone“ (SLC) minimieren lassen. Dieses Add-on ist eine Weiterentwicklung der Technik der probabilistischen Fehlerkompensation (PEC), bei der ein Benutzer das Rauschen einzelner Schichten in einer Schaltung ermittelt und dieses Rauschen anschließend durch die Anwendung von Einzel-Qubit-Gattern und Nachbearbeitungsverfahren kompensiert. Im Vergleich zu anderen Methoden bietet PEC robustere Schätzgrenzen für den Bias des korrigierten Ergebnisses, ist jedoch in der Regel mit einem höheren Zeitaufwand für die QPU verbunden. Während des PEC-Prozesses wird das durchschnittliche Ergebnis, um die durch Rauschen verursachte Abschwächung des Erwartungswerts auszugleichen, um den Faktor neu skaliert, wobei die gelernte Rauschfehlerrate des Pauli- s in der Schicht des Schaltkreises ist. Diese Skalierung erhöht die Varianz um den Faktor und vervielfacht somit auch die Anzahl der auf der QPU erforderlichen Schaltungsausführungen um den Faktor , was wir als Abtastkosten oder Abtast-Overhead bezeichnen. Da exponentiell wächst, beschränkt sich PEC häufig auf flache Schaltungen oder Schaltungen mit wenigen Qubits. Erfahren Sie mehr über PEC im Artikel „Probabilistische Fehlerkompensation mit spärlichen Pauli-Lindblad-Modellen auf verrauschten Quantenprozessoren “.
Wenn wir Fehler identifizieren können, die nicht behoben werden müssen, können wir diese Stichprobenkosten exponentiell senken. Ein erster Schritt in diese Richtung ist die Implementierung einer lokal orientierten Fehlerminderung, bei der ein schnell berechenbarer herkömmlicher „Lichtkegel“ verwendet wird, um den PEC-Overhead zu reduzieren, indem die Fehlerempfindlichkeit einer Observable im gesamten Schaltkreis begrenzt wird, wodurch die Anwendbarkeit von PEC bei bestimmten Problemen auf größere Maßstäbe ausgeweitet wird. Fehler außerhalb dieses Lichtkegels können das Messergebnis nicht beeinflussen und können daher aus dem Fehlerausgleichsverfahren ausgeschlossen werden. Dieser Ausschluss verringert den Stichprobenaufwand – in manchen Fällen sogar erheblich –, ohne dass dabei zusätzliche Verzerrungen entstehen. Insbesondere bei der Messung einer lokalen Observable in einem Schaltkreis mit fester Tiefe erreicht der erforderliche Abtast-Overhead bei einer Skalierung der Anzahl der Qubits im Schaltkreis schließlich ein Plateau (siehe Abb. 2b in „Locality and error mitigation of quantum circuits “).
„Shaded Lightcones“ (SLC) gehen noch einen Schritt weiter und nutzen klassische Simulationen, um die Fehleranfälligkeit in der gesamten Schaltung genauer einzugrenzen. Dadurch wird ein Teil der QPU-Zeit gegen CPU-Zeit eingetauscht, wodurch sich der für die Renormierung des Bias erforderliche Abtast-Overhead verringert. Anstelle einer starren Grenze wird jedem potenziellen Fehler in der Schaltung ein abgestufter „Grad“ zugewiesen, der die Anfälligkeit des Erwartungswerts der Observable gegenüber diesem Fehler nach oben begrenzt. (Bei den im Folgenden vorgestellten, rückwärts propagierten Grenzwerten hängt diese Anfälligkeit auch vom Anfangszustand der Schaltung ab – in typischen Arbeitsabläufen und in diesem Tutorial ist dies der Zustand, in dem alle Bits auf Null gesetzt sind.) Diese verfeinerte Charakterisierung ermöglicht eine effizientere und gezieltere Anwendung von PEC mit geringerer Varianz und gibt dem Anwender gleichzeitig die Möglichkeit, den Bias bei der Schätzung der Observablen kontrolliert anzupassen. Weitere Einzelheiten finden Sie unter „Lightcone-Shading für die klassisch beschleunigte Minderung von Quantenfehlern“.
Unser Workflow für das SLC-Add-on nutzt die Bibliothek samplomatic in Verbindung mit den Executor Klassen und QuantumProgram , die in v0.47.0qiskit-ibm-runtime zu Qiskit Runtime hinzugefügt wurden. Dadurch erhalten Benutzer eine modularere Kontrolle über die Ausführungseinstellungen zur Fehlerunterdrückung und -minderung, ohne dass die Benutzerfreundlichkeit darunter leidet. Weitere Informationen finden Sie im Leitfaden zum Modell der gerichteten Ausführung.
Der SLC-Workflow zur Fehlerminderung auf einen Blick
Zur Modellierung des Rauschens der QPU verwenden wir ein spärliches Pauli-Lindblad-Rauschmodell mit Pauli-Fehlerraten für ein und zwei Qubits, die lokal an jedem Qubit und an jeder Kante des Bausteins erzeugt werden. Mit dieser Auswahl sieht der in diesem Tutorial vorgestellte SLC-Workflow zur Fehlerminderung wie folgt aus:
a. CPU — CPU-gebundene Auswirkungen von Ein- und Zwei-Qubit-Pauli-Fehlern pro Fehler
- Vorwärtsausbreitung (gebundener Effekt auf die Observable). Übertrage jeden Fehler bis zum Ende der Schaltung und berechne seinen Kommutator mit der Observable.
- Kürze die Terme des Operators während der Entwicklung, um die Berechnung überschaubar zu halten.
- Diese Grenzen lassen sich durch eine grobe Rückpropagation der Observablen auf der Grundlage quantenphysikalischer Geschwindigkeitsgrenzen weiter eingrenzen.
- Rückwärtsausbreitung (Einfluss der Anfangsbedingungen). Übertrage jeden Fehler bis zum Anfang des Schaltkreises und berechne dessen Kommutator mit dem Anfangszustand.
b. QPU – Lärmpegel ermitteln. Verwenden Sie NoiseLearnerV3 zur Schätzung der Raten des Pauli-Lindblad-Rauschmodells.
c. CPU – Maßnahmen zur Risikominderung priorisieren
- Aktualisiere die zusammengeführten Grenzen mit den erlernten Rauschraten. Die zuvor berechneten Vorwärts- und Rückwärtsgrenzen kombinieren und mit den erlernten Rauschraten aktualisieren.
- Ordnen Sie die Rauschkomponenten nach ihrer zu mindernenden Bedeutung, indem Sie die berechneten Grenzen und die erlernten Raten verwenden. Ordnen Sie jeden möglichen Rauschfehler nach seiner geschätzten Auswirkung auf die Verzerrung und den damit verbundenen Korrekturaufwand.
d. QPU – Anti-Rauschen einfügen und ausführen. Führen Sie die gewünschte Schaltung mit Anti-Rauschen (inversem Rauschen) aus, das mithilfe von Box Anmerkungen festgelegt wurde.
e. CPU – Schätzung beobachtbar. Berechnen Sie den Erwartungswert unter Anwendung einer messungsbasierten Nachselektion, um die Auswirkungen des nicht-markovschen Rauschens zu verringern.
Überblick über das Lernen mit Rauschen
Das Rauschlernen ist ein gängiger Schritt in verschiedenen Methoden zur Fehlerminderung, der vom Rauschlerner durchgeführt wird; es kommt auch im Tutorial zur probabilistischen Fehlerverstärkung vor. NoiseLearnerV3In können Sie die zu lernenden Rauschschichten gezielt als CircuitInstruction Objekte identifizieren, sodass Sie die gewünschten SLC-Rauschgrenzen für jede Schicht auf die oben beschriebene Weise berechnen können. Das Pauli-Lindblad-Modell liefert Koeffizienten, die bei der PEC+SLC-Priorisierung verwendet werden. generate_noise_model_paulisDie Art und Weise, wie die Gates in Schichten zusammengefasst werden, lässt sich mithilfe der generate_boxing_pass_manager Hilfsfunktionen und find_unique_box_instructions festlegen und anschließend in die SLC-Nutzfunktion einlesen, wie in Schritt 2 unten beschrieben.
Teil 1 | Teil 2 | Teil 3 |
|---|---|---|
| Pauli-Twirl-Schichten mit Zwei-Qubit-Gattern | Identitätspaare von Schichten wiederholen und Rauschen lernen | Berechnung der Genauigkeit (Fehler für jeden Rauschkanal) |
![]() | ![]() | ![]() |
Übersicht über die Nachbearbeitung
Nachdem wir die Berechnung auf Quantenhardware unter Verwendung des samplomatic und Executor Frameworks durchgeführt haben, wandeln wir unsere Bitstring-Messwerte in den gewünschten Observablenwert um. In diesem Tutorial veranschaulichen wir den Arbeitsablauf anhand eines gespiegelten Ising-Schaltkreises (der in Schritt 1 unten aufgebaut wurde), bei dem der ideale Erwartungswert der gemessenen Observable 1 beträgt, da die zweite Hälfte des Schaltkreises die erste aufhebt. Bei der Berechnung des beobachtbaren Werts mit der executor_expectation_values Funktion wenden wir einige Nachbearbeitungsverfahren an, die den Einfluss von Rauschen verringern. Dazu gehören das Entfernen von Aufnahmen, die durch nicht-markovisches Rauschen beeinträchtigt sind, die Minderung von Auslesefehlern sowie die Berücksichtigung von Details unserer PEC-Implementierung. Einzelheiten werden im folgenden Schritt 4 erläutert.
Anforderungen
Bevor Sie mit dieser Anleitung beginnen, stellen Sie bitte sicher, dass Folgendes installiert ist:
- Qiskit SDK v2.2 oder höher, mit Visual isierungsunterstützung
- Qiskit Runtime v0.47 oder später (
pip install qiskit-ibm-runtime) - „Shaded Lightcones“ – Qiskit-Add-on v0.1 oder höher (
pip install qiskit-addon-slc) - Qiskit-Add-on „utils“ v0.3 oder höher (
pip install qiskit-addon-utils) - Samplomatic v0.13 oder höher (
pip install samplomatic)
Einrichtung
Importieren Sie zunächst die Pakete und Funktionen, die zur Ausführung dieses Notebooks benötigt werden.
from multiprocessing import set_start_method
# Setting this value prevents itertools.starmap deadlock on UNIX systems
set_start_method("spawn")
# Needed to prevent PySCF from parallelizing internally (SLC only)
%set_env OMP_NUM_THREADS=1Output:
env: OMP_NUM_THREADS=1
import numpy as np
from matplotlib import pyplot as plt
from qiskit import QuantumCircuit
from qiskit.quantum_info import SparsePauliOp
from qiskit.transpiler import generate_preset_pass_manager, PassManager
from qiskit_ibm_runtime import (
QiskitRuntimeService,
QuantumProgram,
Executor,
NoiseLearnerV3,
)
import samplomatic
from samplomatic.utils import find_unique_box_instructions
from samplomatic.transpiler import generate_boxing_pass_manager
from qiskit_addon_utils.exp_vals.measurement_bases import (
get_measurement_bases,
)
from qiskit_addon_utils.exp_vals.expectation_values import (
executor_expectation_values,
)
from qiskit_addon_utils.noise_management import (
gamma_from_noisy_boxes,
trex_factors,
)
from qiskit_addon_utils.noise_management.post_selection import PostSelector
from qiskit_addon_utils.noise_management.post_selection.transpiler.passes import (
AddPostSelectionMeasures,
AddSpectatorMeasures,
)
from qiskit_addon_slc.bounds import (
compute_backward_bounds,
compute_forward_bounds,
compute_local_scales,
merge_bounds,
tighten_with_speed_limit,
)
from qiskit_addon_slc.utils import (
generate_noise_model_paulis,
map_modifier_ref_to_ref,
)
from qiskit_addon_slc.visualization import draw_shaded_lightconeBeispiel für einen Simulator im kleinen Maßstab
Wie andere lernbasierte Methoden zur Fehlerminderung mindert auch PEC mit abgeschatteten Lichtkegeln das physikalische Rauschen eines bestimmten Quantenprozessors, sodass es von Hardware-Funktionen abhängt, für die es in einem idealen Simulator keine sinnvolle Entsprechung gibt:
NoiseLearnerV3charakterisiert experimentell den spärlichen Pauli-Lindblad-Rauschkanal auf jeder einzelnen Zwei-Qubit-Schicht. Bei einem geräuschlosen Simulator gibt es keine Geräusche, die unterdrückt werden müssten.- Die
ExecutorPrimitive nimmt die vonsamplomaticauf einem Backend erzeugten, mit Anti-Rauschen versetzten, gedrehten Schaltkreise abtasten.
Die Berechnung der Grenze des schattierten Lichtkegels ist klassisch, aber sie ist nur im Zusammenhang mit den erlernten Hardware-Rauschraten aussagekräftig, die das Minderungspotenzial und den Abtast-Overhead bestimmen. Aus diesen Gründen lassen wir das Beispiel mit dem Simulator im kleinen Maßstab außer Acht und demonstrieren den vollständigen PEC+SLC-Workflow direkt auf der Hardware, wobei jeder Schritt des Qiskit-Musters im Folgenden einzeln erläutert wird.
Beispiel für groß angelegte Hardware
Wir führen den gesamten PEC+SLC-Workflow auf einem gespiegelten Ising-Schaltkreis mit 20 Qubits durch, der auf der „ IBM Quantum® “-Hardware ausgeführt wird, und folgen dabei den vier Schritten eines Qiskit-Musters.
Schritt 1: Das Problem erfassen
Unsere Demonstration ist wie folgt aufgebaut:
- Wir entscheiden uns dafür, die Zeitentwicklung unter einem Hamilton-Operator für eine Ising-Kette mit der Form „ 1D “ durchzuführen. Die Ising-Kette „ 1D “ bietet eine schön kompakte Schaltungsstruktur, die sich gut zur Veranschaulichung von PEC-Implementierungen eignet.
- Wir haben uns dafür entschieden, die beobachtbare Größe „ “ zu messen, die eine „ “-Komponente und eine „ “-Komponente enthält. Dies ist hilfreich, um zu verdeutlichen, wie die SLC-Grenzen verschiedene Arten von Fehlern unterschiedlich behandeln.
- Wir möchten, dass der ideale Erwartungswert dieser Observablen genau 1 beträgt, damit sich die Qualität der gemilderten Schätzungen leicht beurteilen lässt. Durch Spiegeln der Schaltung sind wir schon fast am Ziel: Für jedes Gatter in der zweiten Hälfte der Schaltung gibt es ein inverses Gatter in der ersten Hälfte, sodass die ideale Schaltung als Identitätsschaltung fungiert.
- Schließlich setzt das Erreichen des idealen Erwartungswerts von 1 voraus, dass der Schaltkreis in einem +1-Eigenzustand der Observable beginnt – und somit dank der Spiegelung im Idealfall auch dort endet. Da die Observable eine nicht- e Komponente enthält und die
ExecutorPrimitive am Ende der Schaltung die gewünschte Messbasis bereitstellt, fügen wir die entsprechenden Gatter zur Basisvorbereitung mithilfe einerprepare_basisFunktion am Anfang der Spiegel-Schaltung ein. Wir verwenden dieget_measurement_basesFunktion, um zu ermitteln, welche Gatter benötigt werden und wo sie platziert werden müssen, und behalten dabei die Feinheiten der Qubit-Indizierung im Blick, die sich ausboxden Annotationskonventionen ergeben, wie im Abschnitt über die Vorbereitung von Messungen in kanonischer Basis erläutert.
# Width of the Ising chain (a free parameter of this demonstration)
num_qubits = 20
# The observable to measure: one X component (on qubit 6) and one Z component
# (on qubit 13), as motivated in "Step 1: Map the problem" above
target_obs_sparse = [("XZ", [6, 13], 1.0)]observable = SparsePauliOp.from_sparse_list(
target_obs_sparse, num_qubits=num_qubits
)# Determine the measurement basis each qubit requires for this observable.
# The reverser is used during post-processing in Step 4.
bases_virt, reverser_virt = get_measurement_bases(observable)# Free parameters of the demonstration circuit: the number of Trotter steps
# sets the circuit depth, and rx_angle is the transverse-field rotation angle
# applied in each step (pi/4 makes the dynamics non-Clifford)
num_trotter_steps = 10
rx_angle = np.pi / 4def construct_ising_circuit(
num_qubits: int,
num_trotter_steps: int,
rx_angle: float,
) -> QuantumCircuit:
circuit = QuantumCircuit(num_qubits)
for _ in range(num_trotter_steps):
circuit.rx(rx_angle, range(num_qubits))
for first_qubit in (1, 2):
for idx in range(first_qubit, num_qubits, 2):
# equivalent to Rzz(-pi/2):
circuit.sdg([idx - 1, idx])
circuit.cz(idx - 1, idx)
return circuit
def prepare_basis(
circuit: QuantumCircuit, basis: list[int]
) -> QuantumCircuit:
# basis is a list of integer values from 0 to 3. These map to the basis measurement as:
# 0 = I; 1 = Z; 2 = X; 3 = Y
assert len(basis) == circuit.num_qubits
out_circ = circuit.copy_empty_like()
for qb, bas in enumerate(basis):
if bas == 2:
out_circ.h(qb)
elif bas == 3:
out_circ.rx(-np.pi / 2, qb)
out_circ.barrier()
out_circ.compose(circuit, inplace=True)
return out_circ
def mirror_circuit(circuit: QuantumCircuit) -> QuantumCircuit:
mirror_circ = circuit.copy_empty_like()
mirror_circ.compose(circuit.inverse(), inplace=True)
mirror_circ.barrier()
mirror_circ.compose(circuit, inplace=True)
mirror_circ.measure_active()
return mirror_circ# Instantiate the mirrored circuit and prepend the basis-preparation gates
circuit = construct_ising_circuit(num_qubits, num_trotter_steps, rx_angle)
mirrored_circuit = mirror_circuit(circuit)
mirrored_circuit = prepare_basis(mirrored_circuit, bases_virt[0])mirrored_circuit.draw(
"mpl", fold=-1, scale=0.3, idle_wires=False, measure_arrows=False
)Output:
Schritt 2: Optimieren
Wir optimieren die Details in Bezug auf die zu durchlaufende Schaltung, die zu messende Größe und die Parameter für das Rauschlernen. Als Ausgangspunkt wählen wir den am wenigsten ausgelasteten Heron-Prozessor aus und instanziieren ihn mit aktivierten Bruchgates. Diese fraktionierten Gatter ermöglichen eine höhere Empfindlichkeit bei einigen unserer Filtervorgänge nach der Auswahl.
# Initialize IBM Quantum Compute Service using your saved credentials
service = QiskitRuntimeService()
# Select the least busy Heron device. This tutorial targets Heron processors:
# the usage estimate and the non-Markovian-noise post-selection discussed in
# Step 4 are specific to this processor family.
backend = service.least_busy(
operational=True,
simulator=False,
filters=lambda b: b.processor_type["family"] == "Heron",
)
# Re-fetch with fractional gates enabled (least_busy does not forward this)
# Fractional gates are enabled so the non-Clifford Rx rotations are supported natively.
backend = service.backend(backend.name, use_fractional_gates=True)
print(f"Selected backend: {backend.name}")Output:
Selected backend: ibm_kingston
Zunächst werden wir unsere Schaltung in ISA-Befehle transpilieren, wie es für die Ausführung auf unseren QPUs erforderlich ist. Die Wahl der physikalischen Qubits, auf denen die Berechnungen ausgeführt werden, hat einen großen Einfluss auf die Qualität der Ergebnisse, da die Kohärenzzeiten und die Gattertreue innerhalb eines Geräts variieren und sich im Laufe der Zeit verändern. Für ein sorgfältiges Experiment kann es sich lohnen, eine Kette von Qubits anhand kurzer Benchmark-Experimente manuell auszuwählen (siehe beispielsweise das Tutorial „Echtzeit-Benchmarking zur Qubit-Auswahl “). Für die Zwecke dieses Tutorials nutzen wir den Qiskit-Transpiler, um ein Layout auf der Grundlage der Kopplungskarte des Backends und dessen aktuellster Kalibrierungsdaten auszuwählen.
isa_pm = generate_preset_pass_manager(backend=backend, optimization_level=1)
isa_circuit = isa_pm.run(mirrored_circuit)
# The chain of physical qubits selected by the transpiler
layout = isa_circuit.layout.final_index_layout()
print(f"Selected layout: {layout}")
isa_observable = observable.apply_layout(
layout, num_qubits=isa_circuit.num_qubits
)Output:
Selected layout: [71, 58, 51, 50, 49, 48, 47, 46, 45, 44, 43, 42, 41, 36, 21, 22, 23, 16, 3, 2]
wire_order = layout + [
q for q in range(isa_circuit.num_qubits) if q not in layout
]
isa_circuit.draw(
"mpl",
fold=-1,
scale=0.3,
idle_wires=False,
wire_order=wire_order,
measure_arrows=False,
)Output:
Schaltkreis einrahmen
Um die Implementierung zu vereinfachen, nutzen wir den generate_boxing_pass_manager Transpilation-Durchlauf, bei dem die Schaltungsanweisungen in kommentierte Kästchen eingefügt werden. Diese Kästchen zeigen deutlich, an welchen Stellen im Fall von PEC eine Entstörung in den Schaltkreis eingespeist werden sollte. Einzelheiten zu den Einstellungen finden Sie in der Samplomatic-Dokumentation.
Beachten Sie, dass der SLC-Workflow die Verwendung von inject_noise_strategy="individual_modification" im weiteren Verlauf des Prozesses erfordert, da wir dadurch die InjectNoise Anmerkungen zu den BoxOp Anweisungen im Schaltkreis eindeutig identifizieren können. (Kästchen ohne Anmerkung InjectNoise lassen sich auf diese Weise nicht identifizieren.)
Die SLC-gebundenen Berechnungen gehen derzeit davon aus, dass die InjectNoise Annotationen mit site="after" platziert sind (siehe qiskit-addon-slc#33 ). Da sich der Standardwert dieser Einstellung in neueren Versionen geändert hat (siehe samplomatic#372 ), legen inject_noise_site="after" wir ihn samplomatic im Folgenden explizit fest.
Die find_unique_box_instructions Funktion durchläuft den angegebenen „boxed circuit“ und identifiziert diejenigen, die einzigartige Zwei-Qubit-Schichten ( 2Q ) oder Messungen aufweisen, zum Zweck des Rauschlernens und der Rauscheinjektion.
# Box circuit with Twirl and InjectNoise annotations
boxes_pm = generate_boxing_pass_manager(
twirling_strategy="active",
inject_noise_strategy="individual_modification",
inject_noise_site="after",
inject_noise_targets="gates",
measure_annotations="all",
)
boxed_circuit = boxes_pm.run(isa_circuit)
# Find the unique instructions (layers) from boxed circuit
unique_2q_instructions = find_unique_box_instructions(
boxed_circuit, normalize_annotations=None, undress_boxes=True
)boxed_circuit.draw(
"mpl",
fold=-1,
scale=0.3,
idle_wires=False,
wire_order=wire_order,
measure_arrows=False,
)Output:
Messungen der kanonischen Basen vorbereiten
Beachten Sie, dass wir besonders darauf achten müssen, die Reihenfolge der Qubits im Auge zu behalten. Im Folgenden führen wir den Begriff ein, um canonical_qubits die Reihenfolge der Qubits bei der Übergabe an den Executor angemessen anzupassen – dies ergibt sich aus der Art und Weise, wie die Qubit-Reihenfolge beim „Boxen“ von Schaltkreisen und beim Ermitteln eindeutiger Befehle erfasst wird. Weitere Informationen finden Sie in der Dokumentation zur Qubit-Reihenfolgekonvention.
# Determine the canonical qubits order
meas_box = boxed_circuit.data[-1]
canonical_qubits = [
idx
for idx, qubit in enumerate(boxed_circuit.qubits)
if qubit in meas_box.qubits
]
# map canonical qubit to physical (isa) qubit
c_2_p = {c: p for c, p in enumerate(canonical_qubits)}
# map physical (isa) qubit to virtual qubit (index in original circuit)
p_2_v = {p: v for v, p in enumerate(layout)}
# compute map between virtual and canonical qubit indices.
c_2_v = {c: p_2_v[p] for c, p in c_2_p.items()}
assert len(c_2_v) == num_qubits
bases_canon = [
np.array([base_i[c_2_v[c]] for c in range(num_qubits)], dtype=np.uint8)
for base_i in bases_virt
]Arbeitsablauf für Lightcone-Schattierung, Rauschlernen und Anti-Rausch-Injektion
In diesem Tutorial führen wir die SLC-gebundenen Berechnungen durch, bevor das Rauschlernen abgeschlossen ist, sodass die zu optimierende Schaltung zeitlich so nah wie möglich am erlernten Rauschmodell ausgeführt wird. Grundsätzlich lässt sich dieser Arbeitsablauf noch weiter parallelisieren: Ein Job zum Lernen des Rauschverhaltens kann ausgeführt werden, während parallel dazu die Rauschgrenzen geschätzt werden. Bei einer beliebigen Quantenschaltung kann die durch Rauschen begrenzte Berechnung schwach exponentiell skalieren, sodass die Parallelisierung der begrenzten Berechnung (beispielsweise über viele CPU-Kerne hinweg) bei einem gegebenen Rechenzeitbudget engere Grenzen ermöglicht, und die QPU-Ausführungen sowie die begrenzten Berechnungen können ihrerseits für einen möglichst effizienten Arbeitsablauf parallelisiert werden.
Vorhersage des zu erlernenden Rauschmodells Paulis
Bevor wir das Noise-Learning auf der Hardware ausführen, ermitteln wir zunächst, welche Pauli-Fehlerterme unser Rauschmodell enthalten wird. Zur Erinnerung: Wir modellieren das Rauschen jeder einzelnen Schicht als spärliches Pauli-Lindblad-Modell mit einer Fehlerrate für jedes Pauli-Element mit dem Gewicht 1 ( , oder ) an jedem aktiven Qubit und einer weiteren für jedes Pauli-Element mit dem Gewicht 2 an jedem verbundenen Paar aktiver Qubits. Die generate_noise_model_paulis Funktion durchläuft jede einzelne „boxed“ Ebene des angegebenen Schaltkreises und generiert unter Berücksichtigung der Qubit-Verbindungen des Schaltkreises genau diese Liste von Pauli-Termen.
Mit anderen Worten: Dieser Schritt prognostiziert die Struktur des Rauschmodells, das NoiseLearnerV3 später mit den gelernten Raten ausgefüllt wird. Da wir diese Struktur bereits im Voraus kennen, können wir für jeden potenziellen Fehler eine Vorwärts- und eine Rückwärtsgrenze berechnen, noch bevor das Rauschlernen überhaupt ausgeführt wurde.
noise_model_paulis = generate_noise_model_paulis(
unique_2q_instructions, backend.coupling_map, boxed_circuit
)noise_model_rates = {ref: None for ref in noise_model_paulis}a. Vordere Grenzen berechnen
Die compute_forward_bounds Funktion wertet die Kommutationsrelationen zwischen den Gattern in jeder Schicht und den zuvor erzeugten Pauli-Termen dahingehend aus, wie sich Fehler bei der Vorwärtsausbreitung auf die gewünschte Observable auswirken. Bei Gattern, die mit den Pauli-Termen kommutieren, wird nichts unternommen. Clifford-Gatter werden an den Anfang der Schaltung verschoben. Bei Nicht-Clifford-Gattern schätzen wir deren Einfluss auf die Zielobservablen, um diese später (nachdem alle Grenzen zusammengeführt wurden) bei der Rauschunterdrückung vorrangig zu behandeln. Diese Schranke wird erreicht, indem zunächst die „ L2 “-Norm angewendet wird (nämlich die Quadratwurzel aus der Summe der Quadrate der Koeffizienten der relevanten Pauli-Terme). Wenn zu viele Qubit-Terme beteiligt sind, greifen wir auf eine weniger strenge Schranke zurück, die auf der Dreiecksungleichung basiert.
Die Parameter für die Gebietsberechnung festlegen
Die folgenden Einstellungen steuern die Berechnungen der klassischen Grenzen:
slc_atol: die absolute Toleranz der Berechnungen. Operanden mit Koeffizienten unterhalb dieses Schwellenwerts werden als vernachlässigbar betrachtet und verworfen.slc_eigval_max_qubits: die maximale Anzahl von Qubits, für die die strengere, auf Eigenwerten basierende ( L2-norm ) Schranke berechnet wird. Operatoren, die auf mehr Qubits als dieser Anzahl unterstützt werden, fallen auf die oben beschriebene, lockere Dreiecksungleichungsgrenze zurück.slc_evolution_max_terms: die maximale Anzahl von Pauli-Termen, die bei der Weiterleitung der Operatoren durch den Schaltkreis beibehalten werden. Durch das Weglassen der kleinsten Terme bleibt die Berechnung überschaubar, allerdings auf Kosten etwas ungenauerer Schätzgrenzen.slc_num_processes: die Anzahl der CPU-Prozesse, über die die gebundene Berechnung parallelisiert wird.slc_timeout: das Zeitbudget (in Sekunden) für die Berechnung der Bindung. Allen Ebenen, die innerhalb dieses Budgets nicht erreicht werden, wird die triviale Grenze 2 zugewiesen.
In diesem Tutorial wird das Timeout absichtlich erreicht: Wir halten slc_timeout die Zeit bewusst kurz, damit das Notebook schnell läuft und um zu zeigen, dass das Erreichen des Timeouts unbedenklich ist. Die berechneten Schattierungen stellen in jeder Stufe Obergrenzen dar; das Erreichen des Zeitlimits führt daher lediglich dazu, dass die Grenzen für die nicht erreichten Schichten weiter gefasst werden (trivial); die Auswirkungen eines Fehlers werden dabei niemals unterschätzt. Dies zeigt sich in den folgenden Visualisierungen darin, dass die Ebenens ganz links vollständig mit der trivialen Grenze ausgefüllt sind. Durch eine Verlängerung des Timeouts (oder eine Erhöhung der Anzahl der Prozesse) werden die Grenzen enger gesetzt und der Overhead bei der Stichprobenentnahme weiter reduziert.
slc_atol = 1e-8
slc_eigval_max_qubits = 18
slc_evolution_max_terms = 1000
slc_num_processes = 8
slc_timeout = 60forward_bounds = compute_forward_bounds(
boxed_circuit,
noise_model_paulis,
isa_observable,
evolution_max_terms=slc_evolution_max_terms,
eigval_max_qubits=slc_eigval_max_qubits,
atol=slc_atol,
num_processes=slc_num_processes,
timeout=slc_timeout,
)Output:
Bounds computation timed out.
Visualisieren Sie den SLC zur manuellen Überprüfung
Man kann das Verhalten der schraffierten Grenzen interpretieren, indem man untersucht, wie die Messungen und die Pauli-Terme mit den lokalen Fehlern zusammenwirken. Diese Muster sind charakteristisch für dieses zeitliche Evolutionsproblem des „Kicked“-Ising-Hamiltonian und tauchen auch in der Arbeit „Lightcone Shading for Classically Accelerated Quantum Error Mitigation“ auf, wobei sie mehrere verräterische Merkmale aufweisen:
- Wir können die beiden Kegel, die sich aus den beiden nicht identischen Paulis im Observablen ergeben, klar voneinander unterscheiden.
- Wir können sehen, dass die X-Messung am Qubit 6 mit dem X-Fehler in der äußersten Schicht kommutiert.
- Wir sehen, dass der Z-Pauli-Operator auf Qubit 13 mit dem Z-Fehler in der äußersten Schicht kommutiert.
- Wenn wir das oben angegebene Zeitlimit erreichen, werden die verbleibenden Schichten auf der linken Seite vollständig mit trivialen Grenzen von zwei gefüllt.
for p in "XYZ":
display(
draw_shaded_lightcone(
boxed_circuit,
forward_bounds,
noise_model_paulis,
pauli_filter=p,
scale=0.15,
fold=-1,
idle_wires=False,
wire_order=wire_order,
measure_arrows=False,
)
)Output:
b. Die oberen Schranken mit Hilfe von Quanten-Geschwindigkeitsgrenzen verschärfen
Anschließend verschärfen wir die Grenzen mithilfe der tighten_with_speed_limit Funktion. Die geometrische Ausdehnung des Lichtkegels der Observablen ist in den oben berechneten Vorwärtsgrenzen bereits vollständig berücksichtigt. Diese Funktion fügt einen Selbstkonsistenz-Durchlauf hinzu, der die begrenzte Geschwindigkeit nutzt, mit der Informationen durch den Schaltkreis fließen können – der Lichtkegel wächst um höchstens ein Qubit pro Schicht aus Zwei-Qubit-Gattern –, um die zuvor berechneten Grenzen untereinander enger anzugleichen. Der Informationsfluss verläuft in beide Richtungen:
- Von den Kommutatorgrenzen zu den Grenzen der rückwärtspropagierten Observable: Wenn die Observable mit einem Fehler vom Typ „ “ auf einem Qubit kommutiert, kann sie dort keine nennenswerte Komponente vom Typ „ “ oder „ “ aufweisen.
- Von den Schranken für die rückwärtspropagierte Observable zu den Kommutator-Schranken: Wenn die Observable an einem Qubit nur eine geringe „ “- und „ “-Komponente aufweist, dann muss sie dort nahezu mit kommutieren.
forward_bounds_tighter = tighten_with_speed_limit(
forward_bounds, boxed_circuit, noise_model_paulis, isa_observable
)Visualisieren Sie den SLC zur manuellen Überprüfung
Grundsätzlich sorgt diese Verschärfung für einen sanfteren Übergang zwischen den berechneten Grenzen und den trivialen Grenzen, die den Schichten jenseits des Timeouts zugewiesen werden. In diesem Beispiel ist der Effekt kaum sichtbar, da sich die Lichtkegel bis zum Erreichen der Zeitüberschreitung bereits bis zu den Rändern der Schaltung ausgebreitet haben.
for p in "XYZ":
display(
draw_shaded_lightcone(
boxed_circuit,
forward_bounds_tighter,
noise_model_paulis,
pauli_filter=p,
scale=0.15,
fold=-1,
idle_wires=False,
wire_order=wire_order,
measure_arrows=False,
)
)Output:
c. Rückwärtsgrenzen berechnen
In diesem Teil der Rauschvorhersage wird untersucht, wie sich ein Fehler in einer bestimmten Schicht auf den Eingangs auswirken kann. Die compute_backward_bounds Funktion invertiert zunächst die Schaltung und entfernt die Messgatter, um anschließend eine ähnliche Analyse wie bei den Vorwärtsberechnungen durchzuführen.
backward_bounds = compute_backward_bounds(
boxed_circuit,
noise_model_paulis,
evolution_max_terms=slc_evolution_max_terms,
num_processes=slc_num_processes,
timeout=slc_timeout,
)Visualisieren Sie den SLC zur manuellen Überprüfung
Anhand der Berechnung der Rückwärtsgrenzen lässt sich erkennen, wie die Struktur des Anfangszustands das frühe Verhalten der Fehlerausbreitung bestimmt:
- Wir können deutlich erkennen, wie die Z-Fehler zunächst mit dem Anfangszustand |0⟩ kommutieren.
- Nur bei Qubit 6, wo wir den +1-Eigenzustand der X-Basis initialisieren, kommutiert ein Z-Fehler nicht, während ein X-Fehler sehr wohl kommutiert.
for p in "XYZ":
display(
draw_shaded_lightcone(
boxed_circuit,
backward_bounds,
noise_model_paulis,
pauli_filter=p,
scale=0.15,
fold=-1,
idle_wires=False,
wire_order=wire_order,
measure_arrows=False,
)
)Output:
Vorschau der zusammengeführten Grenzen ohne erlernte Rauschraten
Die merged_bounds Funktion bestimmt den Punkt im Schaltkreis, an dem der Wechsel von Rückwärtsgrenzen zu Vorwärtsgrenzen die geschätzte Gesamtverzerrung der gewünschten Observable minimiert. Diese Verzerrung wird als Summe der rückwärtsgerichteten Beiträge für alle Rauschstellen vor diesem Punkt sowie der vorwärtsgerichteten Beiträge für alle Rauschstellen danach berechnet. Derzeit wird dies für alle Qubits einheitlich gehandhabt.
Der optimale Zeitpunkt für den Wechsel von rückwärtsgerichteten zu vorwärtsgerichteten Grenzen hängt von den erlernten Rauschraten ab. Bislang haben wir noch keine Raten ermittelt: noise_model_rates Für jede Schicht wurde der Wert auf None gesetzt, was merge_bounds als gleichmäßige Rauschraten behandelt wird. Die hier dargestellten zusammengeführten Grenzen sind daher nur eine Vorschau; wir berechnen sie in Schritt 3 anhand der tatsächlich erlernten Raten neu.
merged_bounds = merge_bounds(
boxed_circuit,
forward_bounds_tighter,
backward_bounds,
noise_model_rates,
)Output:
Missing noise rates. Partitioning backward/forward commutator bounds by assuming uniform error rates.
Optimal spacetime partitioning not implemented!Just partitioning list of noisy boxes.
Visualisieren Sie den SLC zur manuellen Überprüfung
Nach der Zusammenführung der hinteren und der verschärften vorderen Grenzen wird das Verhalten der kombinierten SLCs deutlich:
- Die obige Funktion gibt an, bei welcher Partition der Übergang von hinteren zu engeren vorderen Schranken stattfindet.
- Wie unten zu sehen ist, enthalten die SLCs nun teilweise rückwärtsgerichtete und teilweise verschärfte Vorwärtsgrenzen.
for p in "XYZ":
display(
draw_shaded_lightcone(
boxed_circuit,
merged_bounds,
noise_model_paulis,
pauli_filter=p,
scale=0.15,
fold=-1,
idle_wires=False,
wire_order=wire_order,
measure_arrows=False,
)
)Output:
Schritt 3: Ausführen
In diesem Abschnitt beginnen wir mit dem Teil des Arbeitsablaufs, bei dem ein echtes Quantenbauelement zum Einsatz kommt. Diese lernbasierte Methode zur Fehlerminderung umfasst zwei Schritte:
NoiseLearnerV3Lerne den Klang kennen, indem du … verwendest.- Führen Sie eine Fehlerminderungsschaltung mit dem
samplomaticundExecutorFramework aus.
Anhand der begrenzten Fehler unserer Quantenschaltung ermitteln wir die damit verbundenen Rauschraten, um unser Fehlerbudget zu priorisieren, den Abtast-Overhead zu bestimmen und die Ausführung auf einer QPU durchzuführen.
a. Informieren Sie sich über die Lärmgrenzwerte
Der „Noise Learner“ charakterisiert die Rauschprozesse, die die Gatter in einer oder mehreren Schaltungen von Interesse beeinflussen, auf der Grundlage des spärlichen Pauli-Lindblad-Rauschmodells. Die run() Methode startet einen Noise-Learning-Auftrag für die angegebenen eindeutigen Zwei-Qubit-Schichten unter Verwendung der in der Noise-Learner-Konfiguration festgelegten Optionen. Diese Optionen steuern die Pauli-Twirling-Strategie, die Anzahl der Randomisierungen und Versuche, die Lerntiefen sowie die Nachauswahl.
Auch die Lerntiefe wählen wir bewusst aus. Eine praktische Erkenntnis im Zusammenhang mit samplomatic lernbasierter Mitigation ist, dass es äußerst vorteilhaft ist, wenn die tiefste Lerntiefe mit der Tiefe der Schaltung übereinstimmt, für die eine Mitigation angestrebt wird. Da NLv3layer_pair_depths in Schichtpaaren gemessen werden (eine Schicht plus ihre Inverse), setzen wir den tiefsten Wert auf die Hälfte der Tiefe der Zwei-Qubit-Schicht der Schaltung.
post_selection_enabled = True# Match the deepest noise-learning depth to the depth of the circuit being
# mitigated. NLv3 ``layer_pair_depths`` are measured in layer pairs (a layer
# plus its inverse), so the deepest value is half the circuit's two-qubit-layer
# depth. Learning to this depth markedly improves the quality of the mitigation.
#
# We measure the two-qubit-layer depth on the pre-boxed ISA circuit: after
# boxing, every two-qubit gate is hidden inside a full-width ``BoxOp``, so a
# ``num_qubits == 2`` filter on ``boxed_circuit`` matches nothing (and
# ``QuantumCircuit.depth`` does not recurse into boxes).
depth_2q = isa_circuit.depth(lambda instr: instr.operation.num_qubits == 2)
max_layer_pair_depth = depth_2q // 2 # dividing by 2 since we want pairs
# Use a fixed schedule of learning depths, but drop any that exceed the circuit's
# depth and always cap the deepest value at ``max_layer_pair_depth`` so we never
# learn deeper than the circuit being mitigated.
candidate_depths = [1, 2, 4, 8, 12, 16, 24, 32, 40, 48]
layer_pair_depths = sorted(
{d for d in candidate_depths if d < max_layer_pair_depth}
| {max_layer_pair_depth}
)
noise_learner_options = {
"num_randomizations": 64,
"shots_per_randomization": 128,
"layer_pair_depths": layer_pair_depths,
"post_selection": {
"enable": post_selection_enabled,
"strategy": "edge",
"x_pulse_type": "rx",
},
"environment": {"job_tags": ["TUT_SLC"]},
}
noise_learner = NoiseLearnerV3(backend, noise_learner_options)noise_learner_job = noise_learner.run(unique_2q_instructions)noise_learner_result = noise_learner_job.result()if post_selection_enabled:
print(
"Minimum fraction of shots kept for noise learning experiments: ",
end="",
)
print(
f"{min([min(d.values()) for d in [nlr.metadata['post_selection']['fraction_kept'] for nlr in noise_learner_result[:2]]]):.2f}"
)Output:
Minimum fraction of shots kept for noise learning experiments: 0.71
# Get a dict mapping each InjectNoise.ref to its learned PauliLindbladMap
refs_2_plm = noise_learner_result.to_dict(
unique_2q_instructions, require_refs=False
)b.i. Aktualisierung der zusammengeführten Grenzen anhand der tatsächlich erlernten Rauschraten
Nachdem das spezifische Rauschmodell nun trainiert wurde, können wir die ermittelten Rauschraten auf die vorhergesagten Rauschgrenzen anwenden und so endgültig feststellen, welche Grenzen den größten Einfluss auf die Minimierung des Bias haben.
merged_bounds = merge_bounds(
boxed_circuit,
forward_bounds_tighter,
backward_bounds,
refs_2_plm,
)Output:
Optimal spacetime partitioning not implemented!Just partitioning list of noisy boxes.
b.ii. Berechne das local_scales für die Hardware-Ausführung
compute_local_scales Es werden alle möglichen Rauschfehler in der Schaltung untersucht und abgeschätzt, inwieweit dieser Fehler das Endergebnis der Messung verfälschen könnte und wie aufwendig dessen Korrektur wäre. Anschließend werden die Fehler danach gewichtet, inwieweit sich ihre Behebung lohnt, und es wird die Teilmenge ausgewählt, die die Verzerrung so weit wie möglich reduziert, ohne dabei das zulässige Budget für Stichprobenkosten zu überschreiten (bzw. die gewünschte Genauigkeit zu erreichen). Das Ergebnis ist eine Reihe von Skalierungsfaktoren, die angeben, welche Fehler aktiv gemindert werden und welche unberücksichtigt bleiben (local_scales), zusammen mit dem prognostizierten Gesamt-Overhead der Stichprobenkosten (sampling_costs) und der verbleibenden Verzerrung (residual_bias_bound).
Die Möglichkeit, die gewünschte Restvorspannung zu steuern, ist ein entscheidendes Merkmal der SLC-Implementierung von PEC. Während bei der ursprünglichen Implementierung der Abtast-Overhead stets auf eine Null-Verzerrung ausgerichtet war, können wir den erforderlichen Abtast-Overhead nun durch einen Kompromiss hinsichtlich der erwarteten verbleibenden Verzerrung anpassen. Dies hilft dem Benutzer, ein festgelegtes Stichprobenbudget einzuhalten, was insbesondere bei der ersten Prototypenentwicklung eines Arbeitsablaufs nützlich sein kann.
id_map = map_modifier_ref_to_ref(boxed_circuit)summed_rates = 0.0
for box_id, noise_id in id_map.items():
learned_plm = refs_2_plm[noise_id]
summed_rates += np.sum(learned_plm.rates)
# print(f"{box_id}:\tgamma = {np.exp(2 * summed_rates):1.6e}\tsampling cost = {np.exp(4 * summed_rates):1.6e}")
total_gamma = np.exp(2 * summed_rates)
print(
f"Full PEC gamma={total_gamma}, sampling cost (gamma^2) = {total_gamma**2}"
)Output:
Full PEC gamma=12.750876317157042, sampling cost (gamma^2) = 162.58484685543633
biases = []
costs = []
for bias in [0.0] + np.arange(0.001, 0.102, 0.01).tolist():
_, cost_, bias_ = compute_local_scales(
boxed_circuit,
merged_bounds,
refs_2_plm,
sampling_cost_budget=np.inf,
bias_tolerance=bias,
)
biases.append(bias_)
costs.append(cost_)Abwägung zwischen dem Aufwand für die Stichprobenziehung und der verbleibenden Verzerrung
xticks = np.arange(0, 11)
fig, ax = plt.subplots()
ax.scatter(
[0], [total_gamma**2], marker="D", c="tab:orange", label="full PEC"
)
ax.plot(
100 * np.array(biases),
np.array(costs),
"o-",
c="tab:blue",
label="PEC+SLC",
)
ax.set_yscale("log")
ax.set_xticks(xticks, [f"{x:.1f}" for x in xticks])
ax.set_xlabel("Remaining bias [%]")
ax.set_ylabel(r"Sampling overhead, $\gamma^2$")
ax.grid()
ax.legend()
fig.suptitle("PEC sampling overhead reduction due to SLC")Output:
Text(0.5, 0.98, 'PEC sampling overhead reduction due to SLC')
chosen_bias_thres = 0.1local_scales, sampling_cost, residual_bias_bound = compute_local_scales(
boxed_circuit,
merged_bounds,
refs_2_plm,
sampling_cost_budget=np.inf,
bias_tolerance=chosen_bias_thres,
)
print(
f"PEC+SLC sampling cost (gamma^2) = {sampling_cost} "
f"w/ remaining bias = {100 * residual_bias_bound:.1f}%"
)Output:
PEC+SLC sampling cost (gamma^2) = 48.13646509622307 w/ remaining bias = 10.0%
c. Führen Sie die gewünschte Schaltung mit Antinoise aus
c.i. Bereiten Sie die Schaltungsvorlage vor, indem Sie samplex
template_circuitDas samplex ist eine Ausgabe der build Methode von Samplomatic, die alle Informationen enthält, die zur Erzeugung zufälliger Parameter für erforderlich sind. Diese werden dann zum Einrichten der QuantumProgram Objekte verwendet, die wiederum auf einer QPU mit der Executor Primitive ausgeführt werden. samplexJedes QuantumProgram kann mehrere Elemente enthalten, die man sich als ein Paar aus template und vorstellen kann.
Weitere Informationen findest du im Tutorial „Hello Samplomatic “.
# Build template circuit and samplex for later use with the "Executor"
template_circuit, samplex = samplomatic.build(boxed_circuit)# Set up postselection if it's been enabled
if post_selection_enabled:
# Set up post selection PM (to add PS instructions)
post_selection_pm = PassManager(
[
AddSpectatorMeasures(backend.coupling_map),
AddPostSelectionMeasures(x_pulse_type="rx"),
]
)
final_template_circuit = post_selection_pm.run(template_circuit)
else:
final_template_circuit = template_circuitc.ii. Richten Sie die QuantumProgram
num_randomizations = 4096
shots_per_randomization = 64
chunk_size = 256# Set up QuantumProgram
program = QuantumProgram(shots=shots_per_randomization, noise_maps=refs_2_plm)
# no EM
# Collect up a dict of the other arguments that need to be bound to samplex_inputs
samplex_inputs = {
f"noise_scales.{ref}": float(0) for ref in local_scales.keys()
}
samplex_inputs |= {"basis_changes": {"basis0": bases_canon[0]}}
# Convert samplex_inputs into a dict to pass to QuantumProgram
samplex_arguments = (
samplex.inputs().bind(**samplex_inputs).make_broadcastable()
)
program.append_samplex_item(
circuit=final_template_circuit,
samplex=samplex,
samplex_arguments=samplex_arguments,
shape=(num_randomizations,),
chunk_size=chunk_size,
)
# plain PEC
# Collect a dict of the other arguments that need to be bound to samplex_inputs
samplex_inputs = {
f"noise_scales.{ref}": float(-1) for ref in local_scales.keys()
}
samplex_inputs |= {"basis_changes": {"basis0": bases_canon[0]}}
# Convert samplex_inputs into a dict to pass to QuantumProgram
samplex_arguments = (
samplex.inputs().bind(**samplex_inputs).make_broadcastable()
)
program.append_samplex_item(
circuit=final_template_circuit,
samplex=samplex,
samplex_arguments=samplex_arguments,
shape=(num_randomizations,),
chunk_size=chunk_size,
)
# PEC+SLC
# Collect a dict of the other arguments that need to be bound to samplex_inputs
samplex_inputs = {
f"noise_scales.{ref}": float(-1) for ref in local_scales.keys()
}
samplex_inputs |= {"basis_changes": {"basis0": bases_canon[0]}}
samplex_inputs |= {"local_scales": local_scales}
# Convert samplex_inputs into a dict to pass to QuantumProgram
samplex_arguments = (
samplex.inputs().bind(**samplex_inputs).make_broadcastable()
)
program.append_samplex_item(
circuit=final_template_circuit,
samplex=samplex,
samplex_arguments=samplex_arguments,
shape=(num_randomizations,),
chunk_size=chunk_size,
)c.iii. Programm mit der Executor Primitive ausführen
executor = Executor(backend)job_exec = executor.run(program)results_exec = job_exec.result()Schritt 4: Nachbearbeitung
executor_expectation_valuesDa wir den endgültigen Erwartungswert, der uns interessiert, mithilfe von berechnen, wenden wir einige Nachbearbeitungsverfahren an, um sicherzustellen, dass wir Ergebnisse von höchstmöglicher Qualität erhalten. Zunächst wenden wir unser „Twirled Readout Error Extinction“-Verfahren (TREX) an, das alle während des Auslesevorgangs auftretenden Fehler berücksichtigt. Anschließend beheben wir Fehler, die durch nicht-markovisches Rauschen in unseren Heron-Backends verursacht werden, mithilfe einer Post-Selektionsmethode. Bei diesem Verfahren werden aktive und passive Qubits gemessen, anschließend wird an jedem Qubit eine langsame Rotation durchgeführt, und danach erfolgt eine erneute Messung. PostSelectorIn Fällen, in denen die beiden Messungen nicht wie erwartet ein umgedrehtes Qubit bestätigen, werden diese Messdurchläufe durch Anwendung einer mask aus der verworfen. Im Rahmen der Maskenberechnung kann eine bestimmte Strategie festgelegt werden, um anhand von Einzel-Qubit-Knoten oder benachbarten Zuschauer-Kanten zu filtern, was sich sowohl auf die Anzahl der herausgefilterten Durchläufe als auch auf die Qualität der Ergebnisse auswirken kann.
measurement_noise_map = noise_learner_result[2].to_pauli_lindblad_map()
trex_scale_factors = trex_factors(measurement_noise_map, reverser_virt)post_selection_strategy = "node"def post_process_conv(datum, steps=16, gamma=None, ps=False, trex=False):
meas = datum["meas"]
flips = datum["measurement_flips.meas"]
signs = datum.get("pauli_signs", None)
meas_basis_axis = None
avg_axis = 0
mask = None
if ps and post_selection_enabled:
# Post-select the results
post_selector = PostSelector.from_circuit(
circuit=final_template_circuit, coupling_map=backend.coupling_map
)
# Compute the ps mask for filtering results
mask = post_selector.compute_mask(
datum, strategy=post_selection_strategy
)
# Compute fraction of shots kept from post selection
total_num_shots = num_randomizations * shots_per_randomization
ps_ratio = np.sum(mask) * 100 / total_num_shots / len(bases_canon)
print(
f"With {post_selection_strategy}-based post selection ({ps_ratio:.1f}% of shots kept):"
)
results = []
for i in range(steps, num_randomizations + 1, steps):
# Compute mitigated expvals w/out post-selection
res = executor_expectation_values(
meas[:i],
reverser_virt,
meas_basis_axis,
avg_axis=avg_axis,
measurement_flips=flips[:i],
pauli_signs=signs[:i] if signs is not None else None,
postselect_mask=mask[:i] if mask is not None else None,
rescale_factors=trex_scale_factors if trex else None,
gamma_factor=gamma,
)
results.append(res[0])
return resultsgamma_pec = gamma_from_noisy_boxes(refs_2_plm, id_map)
gamma_slc = gamma_from_noisy_boxes(refs_2_plm, id_map, local_scales)steps = 16results = {}
for label, result_idx, gamma, use_ps, use_trex in [
("PEC", 1, gamma_pec, True, True),
("PEC+SLC", 2, gamma_slc, True, True),
("Unmitigated", 0, None, False, False),
]:
res = post_process_conv(
results_exec[result_idx],
steps=steps,
gamma=gamma,
ps=use_ps,
trex=use_trex,
)
results[label] = resOutput:
With node-based post selection (24.1% of shots kept):
With node-based post selection (24.2% of shots kept):
Anhand der Auswertung der Versuchsergebnisse können wir das Verhalten der verschiedenen Ansätze direkt miteinander vergleichen: PEC, PEC in Kombination mit SLC und die unmodifizierte Basislinie. Einige konkrete Punkte, die besonders hervorzuheben sind:
- Das unverfälschte Ergebnis liegt weit außerhalb des 10-prozentigen Verzerrungsbandes (siehe 0.35 ) und wird durch die Anzahl der Randomisierungen nicht beeinflusst.
- Bei diesem Gerät verursacht die vollständige PEC einen Abtast-Overhead von . Nach anfänglichen starken Schwankungen stellt die einfache PEC-Schätzung das Signal wieder her und pendelt sich bei etwa 1.1 ein, wobei der genaue Wert leicht überschritten wird. Beachten Sie, dass das 10-prozentige Bias-Band nur für den PEC+SLC-Schätzer gilt: Der reine PEC-Schätzer ist grundsätzlich unverzerrt, sodass diese verbleibende Abweichung nicht durch eine Bias-Garantie abgedeckt ist, sondern stattdessen statistische Schwankungen und Unvollkommenheiten im gelernten Rauschmodell widerspiegelt, was bei dieser Problemgröße durchaus nachvollziehbar ist.
- SLC reduziert den Overhead um weitere ~3.4-fold (auf , was einer Rest-Bias-Grenze von etwa 10 % entspricht). Auch PEC+SLC unterliegt bei wenigen Randomisierungen starken Schwankungen und reicht anfangs sogar bis in den unphysikalischen Bereich hinein, doch seine Schätzung konvergiert stetig gegen den exakten Wert und pendelt sich nahe bei 1.0 deutlich innerhalb des Bandes ein. Es erreicht oder übertrifft die Genauigkeit von einfachem PEC bei etwa einem Drittel der Abtastkosten und verdeutlicht damit den Vorteil der Lichtkegelschattierung.
- Die Fehlerbalken beider gemilderter Schätzungen verengen sich mit zunehmender Anzahl von Randomisierungen; bei einer festen Anzahl von Randomisierungen weist der PEC+SLC-Schätzer die geringere statistische Varianz auf, was mit seinem geringeren „ “ im Einklang steht.
Beachten Sie, dass die erlernten Rauschraten (und damit auch die Overheads und die korrigierten Schätzwerte) vom Backend und dessen Kalibrierung zum Zeitpunkt der Ausführung abhängen, sodass eine erneute Ausführung dieses Notebooks zu quantitativ unterschiedlichen Werten führen kann.
fig, ax = plt.subplots(1, 1, figsize=(12, 6))
ax.axhline(1.0, color="black", label="Exact")
ax.fill_between(
[-50, 4100], -10, 0, color="grey", alpha=0.25, label="Unphysical"
)
ax.fill_between([-50, 4100], 1, 10, color="grey", alpha=0.25)
ax.fill_between(
[-50, 4100], 0.9, 1.1, color="red", alpha=0.25, label="10% bias"
)
for label, res in results.items():
ax.errorbar(
list(range(steps, num_randomizations + 1, steps)),
[r[0] for r in res],
yerr=[r[1] for r in res],
alpha=0.75,
marker="o",
linestyle="",
markerfacecolor="none",
label=label,
)
ax.set_ylabel(r"$\langle X_{6}Z_{13}\rangle$")
ax.set_xlabel("# randomizations")
ax.grid()
ax.legend(ncols=2)
ax.set_ylim([-0.1, 2.0])
ax.set_xlim([-50, 4100])Output:
(-50.0, 4100.0)
Nächste Schritte
Wenn Sie diese Arbeit interessant fanden, könnten Sie auch an folgenden Materialien interessiert sein:


