Zum Hauptinhalt springe

Kombinierung vo Fehlerminderungsoptione mit em Estimator-Primitive

Nutzungsschätzung: Siebe Minute uf emm Heron r2-Prozessor (HINWEIS: Des isch nur e Schätzung. Ihri Laufzeit kann variiere.)

Hintergrund

Dä Walkthrough untersucht d'Fehlerunterdrückungs- und Fehlerminderungsoptione, wo mit em Estimator-Primitive vo Qiskit Runtime verfügbar sin. Sie wäre e Schaltung und e Observable konstruiere und Jobs mit em Estimator-Primitive unter Verwendung vo verschiedene Kombinatione vo Fehlerminderungseinstellunge ischreiche. Danoch zeichne Sie d'Ergebnisse uf, um d'Auswirkunge vo de verschiedene Einstellunge z'beobachte. D'meischte Beispiele verwende e 10-Qubit-Schaltung, um Visualisierunge z'erleichere, und am End könne Sie de Workflow uf 50 Qubits skaliere.

Des sin d'Fehlerunterdrückungs- und Minderungsoptione, wo Sie verwende wäre:

  • Dynamical Decoupling
  • Messfehlerkompensation
  • Gate Twirling
  • Zero-Noise Extrapolation (ZNE)

Anforderunge

Stelle Sie vor em Beginn vo dem Walkthrough sicher, dass Sie Folgendes installiert hän:

  • Qiskit SDK v2.1 oder höher, mit Unterstützung für Visualisierung
  • Qiskit Runtime v0.40 oder höher (pip install qiskit-ibm-runtime)

Setup

# Added by doQumentation — required packages for this notebook
!pip install -q matplotlib numpy qiskit qiskit-ibm-runtime
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 Estimator

Schritt 1: Klassischi Eingabe uf es Quanteproblem abbildé

Dä Walkthrough geht devoo us, dass des klassischi Problem scho uf Quantemechanik abgebildet worde isch. Fange Sie mit dä Konstruktion vo nere Schaltung und nere Observable zum Messe aa. Weil d'Technike, wo do verwendet wäre, uf vieli verschiedeni Arte vo Schaltunge anwendbar sin, verwendet dä Walkthrough dä Einfachheit halber d'efficient_su2-Schaltung us dä Qiskit-Schaltungsbibliothek.

efficient_su2 isch e parametrisirti Quanteschaltung, wo so konzipiert isch, dass si uf Quantehardware mit begrenzter Qubit-Konnektivität effizient ausführbar isch und dennoch ausdrucksstark gnueg, um Probleme in Anwendungsdomäne wie Optimierung und Chemie z'löse. Si wird durch abwechselni Schichte vo parametrisirti Ein-Qubit-Gates mit nere Schicht konstruiert, wo es feschts Muster vo Zwei-Qubit-Gates enthält, für e gewählti Anzahl vo Wiederholunge. Des Muster vo de Zwei-Qubit-Gates kann vom Benutzer spezifiziert wäre. Do könne Sie des igebaueti pairwise-Muster verwende, weil's d'Schaltungstiefe minimiert, indem's d'Zwei-Qubit-Gates so dicht wie möglich packt. Des Muster kann nur mit linearer Qubit-Konnektivität ausgeführt wäre.

n_qubits = 10
reps = 1

circuit = efficient_su2(n_qubits, entanglement="pairwise", reps=reps)

circuit.decompose().draw("mpl", scale=0.7)

Output of the previous code cell

Output of the previous code cell

Für unseri Observable nehme mir de Pauli-ZZ-Operator, wo uf's letzte Qubit wirkt, ZIIZ I \cdots I.

# Z on the last qubit (index -1) with coefficient 1.0
observable = SparsePauliOp.from_sparse_list(
[("Z", [-1], 1.0)], num_qubits=n_qubits
)

An dem Punkt könnte Sie mit dä Ausführung vo Ihrer Schaltung wiitertue und d'Observable messe. Sie möchte aber au d'Ausgabe vom Quantegerät mit dä korrekten Antwort vergleiche – des heißt, em theoretische Wert vo dä Observable, falls d'Schaltung ohne Fehler ausgeführt worde wär. Für kleini Quanteschaltunge könne Sie dä Wert berechne, indem Sie d'Schaltung uf emm klassische Computer simuliere, aber des isch für größeri Utility-Scale-Schaltunge nit möglich. Sie könne des Problem mit dä "Spiegelschaltungs"-Technik (au bekannt als "Compute-Uncompute") umgehe, wo zum Benchmarking vo dä Leistung vo Quantegeräte nützlich isch.

Spiegelschaltung

Bei dä Spiegelschaltungstechnik verkettet Sie d'Schaltung mit ihrer inversen Schaltung, wo durch Umkehrig vo jedem Gate dä Schaltung in umgekehrter Reihefolg gebildet wird. D'resultieriendi Schaltung implementiert de Identitätsoperator, wo trivial simuliert wäre kann. Weil d'Struktur vo dä ursprünglichi Schaltung in dä Spiegelschaltung erhalte bleibt, gibt d'Ausführung vo dä Spiegelschaltung dennoch e Vorstellung devoo, wie des Quantegerät bi dä ursprünglichi Schaltung abschnide würd.

D'folgenди Codezelle weist Ihrer Schaltung zufällige Parameter zu und konstruiert dann d'Spiegelschaltung unter Verwendung vo dä unitary_overlap-Klasse. Füege vor em Spiegele vo dä Schaltung e Barrier-Instruktion ii, um z'verhindre, dass dä Transpiler d'zwei Teile vo dä Schaltung uf beide Site vo dä Barrier zusammeführt. Ohne d'Barrier würd dä Transpiler d'ursprünglichi Schaltung mit ihrer Inversen zusammeführe, was zu nere transpilierte Schaltung ohne Gates führt.

# 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 of the previous code cell

Output of the previous code cell

Schritt 2: Problem für d'Ausführung uf Quantehardware optimiere

Sie müsse Ihri Schaltung optimiere, bevor Sie si uf Hardware ausführe. Dä Prozess umfasst e paar Schritte:

  • Wähle Sie es Qubit-Layout, wo d'virtuelle Qubits vo Ihrer Schaltung uf physischi Qubits uf dä Hardware abbildet.
  • Füege Sie no Bedarf Swap-Gates ii, um Interaktione zwische Qubits z'route, wo nit verbunde sin.
  • Übersetze Sie d'Gates in Ihrer Schaltung in Instruction Set Architecture (ISA)-Instruktione, wo direkt uf dä Hardware ausgeführt wäre könne.
  • Führe Sie Schaltungsoptimierunge durch, um d'Schaltungstiefe und Gate-Anzahl z'minimiere.

Dä in Qiskit igebaueti Transpiler kann all die Schritte für Sie durchführe. Weil des Beispiel e hardwareeffiziente Schaltung verwendet, sott dä Transpiler in dä Lag si, es Qubit-Layout z'wähle, wo kei Swap-Gates zum Routing vo Interaktione bruucht.

Sie müsse des z'verwendendi Hardwaregerät auswähle, bevor Sie Ihri Schaltung optimiere. D'folgenди Codezelle fordert des am wenigschte ausgelasteti Gerät mit mindestens 127 Qubits aa.

service = QiskitRuntimeService()
backend = service.least_busy(
operational=True, simulator=False, min_num_qubits=127
)

Sie könne Ihri Schaltung für Ihr gewähltes Backend transpiliere, indem Sie en Pass-Manager erstelle und dann de Pass-Manager uf dä Schaltung ausführe. E einfachi Möglichkeit, en Pass-Manager z'erstelle, isch d'Verwendung vo dä Funktion generate_preset_pass_manager. Luege Sie Transpilierung mit Pass-Manager für e detailliertori Erklärung vo dä Transpilierung mit Pass-Manager.

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 of the previous code cell

Output of the previous code cell

D'transpilierti Schaltung enthält jetzt nur no ISA-Instruktione. D'Ein-Qubit-Gates sin in Bezug uf X\sqrt{X}-Gates und RzR_z-Rotatione zerlegt worde, und d'CX-Gates sin in ECR-Gates und Ein-Qubit-Rotatione zerlegt worde.

Dä Transpilationsprozess hät d'virtuelle Qubits vo dä Schaltung uf physischi Qubits uf dä Hardware abgebildet. D'Informatione über des Qubit-Layout sin im layout-Attribut vo dä transpilierte Schaltung gspeichert. D'Observable isch au in Bezug uf d'virtuelle Qubits definiert worde, deshalb müsse Sie des Layout uf d'Observable anwende, was Sie mit dä Methode apply_layout vo SparsePauliOp tue könne.

isa_observable = observable.apply_layout(isa_circuit.layout)

print("Original observable:")
print(observable)
print()
print("Observable with layout applied:")
print(isa_observable)
Original observable:
SparsePauliOp(['ZIIIIIIIII'],
coeffs=[1.+0.j])

Observable with layout applied:
SparsePauliOp(['IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII'],
coeffs=[1.+0.j])

Schritt 3: Ausführung mit Qiskit Primitives

Jetzt sin Sie bereit, Ihri Schaltung mit em Estimator-Primitive auszuführe.

Do reiche Sie fünf separate Jobs ii, fange Sie ohne Fehlerunterdrückung oder -minderung aa, und aktiviere sukzessiv verschiedeni Fehlerunterdrückungs- und -minderungsoptione, wo in Qiskit Runtime verfügbar sin. Informatione zu de Optione finde Sie uf de folgende Site:

Weil die Jobs unabhängig voneinander ausgeführt wäre könne, könne Sie de Batch-Modus verwende, damit Qiskit Runtime des Timing vo ihrer Ausführung optimiere kann.

pub = (isa_circuit, isa_observable)

jobs = []

with Batch(backend=backend) as batch:
estimator = Estimator(mode=batch)
# 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: Nachbearbeitig und Rückgab vom Ergebnis im gewünschte klassische Format

Jetzt könne Sie d'Date analysiere. Do ruefe Sie d'Jobergebnisse ab, extrahiere d'gmessene Erwartungswerte us ihne und zeichne d'Werte uf, iischließlich Fehlerbalke vo nere Standardabweichung.

# 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 of the previous code cell

In dem kleini Maßstab isch's schwierig, d'Wirkung vo de meischte Fehlerminderungstechnike z'sehe, aber Zero-Noise Extrapolation bietet e spürbari Verbesserung. Beachte Sie aber, dass die Verbesserung nit umsonst kommt, weil des ZNE-Ergebnis au en größere Fehlerbalke aufweist.

Skalierung vom Experiment noch obe

Bei dä Entwicklung vo emm Experiment isch's nützlich, mit nere kleini Schaltung anzefange, um Visualisierunge und Simulatione z'erleichere. Nachdem Sie Ihre Workflow uf nere 10-Qubit-Schaltung entwickelt und getestet hän, könne Sie en uf 50 Qubits skaliere. D'folgende Codezelle wiederholt alli Schritte in dem Walkthrough, wendet si aber jetzt uf e 50-Qubit-Schaltung aa.

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)
# 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 of the previous code cell

Wenn Sie d'50-Qubit-Ergebnisse mit de 10-Qubit-Ergebnisse vo vorher vergleiche, stelle Sie möglicherweise Folgendes fescht (Ihri Ergebnisse könne zwische de Läuf variiere):

  • D'Ergebnisse ohne Fehlerminderung sin schlechter. D'Ausführung vo dä größere Schaltung beinhaltet d'Ausführung vo mehr Gates, sodass's mehr Möglichkeite gibt, dass sich Fehler aasammle.
  • D'Hinzufüeging vo Dynamical Decoupling könnt d'Leistung verschlechtert hän. Des isch nit überraschend, weil d'Schaltung sehr dicht isch. Dynamical Decoupling isch hauptsächlich nützlich, wenn's großi Lücke in dä Schaltung gibt, während dere Qubits ohne angewandti Gates im Leerlauf sitze. Wenn die Lücke nit do sin, isch Dynamical Decoupling nit effektiv und kann d'Leistung tatsächlich verschlechtere, wege Fehler in de Dynamical-Decoupling-Pulse selber. D'10-Qubit-Schaltung war möglicherweise z'klei, um dä Effekt z'beobachte.
  • Mit Zero-Noise Extrapolation isch des Ergebnis so gut oder fascht so gut wie des 10-Qubit-Ergebnis, obwohl dä Fehlerbalke viel größer isch. Des demonstriert d'Leistungsfähigkeit vo dä ZNE-Technik!

Fazit

In dem Walkthrough hän Sie verschiedeni Fehlerminderungsoptione untersucht, wo für des Qiskit Runtime Estimator-Primitive verfügbar sin. Sie hän en Workflow mit nere 10-Qubit-Schaltung entwickelt und en dann uf 50 Qubits skaliert. Sie hän möglicherweise beobachtet, dass d'Aktivierung vo mehr Fehlerunterdrückungs- und -minderungsoptione nit immer d'Leistung verbessert (insbesondere d'Aktivierung vo Dynamical Decoupling in dem Fall). D'meischte Optione akzeptiere zusätzlichi Konfiguratione, wo Sie in Ihrer eigene Arbet teste könne!

Source: IBM Quantum docs — updated Apr 27, 2026
English version on doQumentation — updated May 7, 2026
This translation based on the English version of Mar 11, 2026