Skip to main content
IBM Quantum Platform

Quantenrauschen und Fehlerminderung

Note

Toshinari Itoko (28. Juni 2024)

Laden Sie die PDF-Datei der Originalvorlesung herunter. Beachten Sie, dass einige Codeschnipsel veraltet sein könnten, da es sich um statische Bilder handelt.

Die ungefähre QPU-Zeit für die Durchführung dieses Experiments beträgt 1 m 40 s.


1. Einführung

In dieser Lektion werden wir uns mit dem Rauschen befassen und damit, wie es auf Quantencomputern gemildert werden kann. Zunächst werden wir die Auswirkungen des Rauschens mit Hilfe eines Simulators untersuchen, der das Rauschen auf verschiedene Weise simulieren kann, u. a. mit Rauschprofilen von echten Quantencomputern. Dann werden wir zu echten Quantencomputern übergehen, bei denen das Rauschen inhärent ist. Wir werden uns mit den Auswirkungen der Fehlerminderung befassen, einschließlich Kombinationen von Dingen wie Null-Rausch-Extrapolation (ZNE) und Gate-Twirling.

Wir beginnen mit dem Laden einiger Pakete.

# !pip install qiskit qiskit_aer qiskit_ibm_runtime
# !pip install jupyter
# !pip install matplotlib pylatexenc
import qiskit

qiskit.__version__

Output:

'2.0.2'
import qiskit_aer

qiskit_aer.__version__

Output:

'0.17.1'
import qiskit_ibm_runtime

qiskit_ibm_runtime.__version__

Output:

'0.40.1'

2. Laute Simulation ohne Fehlerminderung

Qiskit Aer ist ein klassischer Simulator für Quantencomputer. Es kann nicht nur die ideale Ausführung, sondern auch die verrauschte Ausführung von Quantenschaltungen simulieren. Dieses Notizbuch zeigt, wie man mit Qiskit Aer eine Rauschsimulation durchführt:

  1. Erstellen eines Lärmmodells
  2. Erstellen eines verrauschten Samplers (Simulators) mit dem Rauschmodell
  3. Führen Sie eine Quantenschaltung auf dem verrauschten Probenehmer aus
noise_model = NoiseModel()
...
noisy_sampler = Sampler(options={"backend_options": {"noise_model": noise_model}})
job = noisy_sampler.run([circuit])

2.1 Eine Testschaltung aufbauen

Wir betrachten Spielzeug-1-Qubit-Schaltungen, die einfach X Gatter d mal wiederholen (d=0... 100) und messen Sie die Z beobachtbar.

from qiskit.circuit import QuantumCircuit

MAX_DEPTH = 100
circuits = []
for d in range(MAX_DEPTH + 1):
    circ = QuantumCircuit(1)
    for _ in range(d):
        circ.x(0)
        circ.barrier(0)
    circ.measure_all()
    circuits.append(circ)

display(circuits[3].draw(output="mpl"))

Output:

Output of the previous code cell
from qiskit.quantum_info import SparsePauliOp

obs = SparsePauliOp.from_list([("Z", 1.0)])
obs

Output:

SparsePauliOp(['Z'],
              coeffs=[1.+0.j])

2.2 Ein Geräuschmodell erstellen

Um eine Rauschsimulation durchzuführen, müssen wir NoiseModel angeben. In diesem Abschnitt zeigen wir, wie man NoiseModel erstellt.

Zunächst müssen wir Quantenfehler (oder Auslesefehler) definieren, die einem Rauschmodell hinzugefügt werden.

from qiskit_aer.noise.errors import (
    coherent_unitary_error,
    amplitude_damping_error,
    ReadoutError,
)
from qiskit.circuit.library import RXGate

# Coherent (unitary) error: Over X-rotation error
# https://qiskit.github.io/qiskit-aer/stubs/qiskit_aer.noise.coherent_unitary_error.html#qiskit_aer.noise.coherent_unitary_error
OVER_ROTATION_ANGLE = 0.05
coherent_error = coherent_unitary_error(RXGate(OVER_ROTATION_ANGLE).to_matrix())

# Incoherent error: Amplitude dumping error
# https://qiskit.github.io/qiskit-aer/stubs/qiskit_aer.noise.amplitude_damping_error.html#qiskit_aer.noise.amplitude_damping_error
AMPLITUDE_DAMPING_PARAM = 0.02  # in [0, 1] (0: no error)
incoherent_error = amplitude_damping_error(AMPLITUDE_DAMPING_PARAM)

# Readout (measurement) error: Readout error
# https://qiskit.github.io/qiskit-aer/stubs/qiskit_aer.noise.ReadoutError.html#qiskit_aer.noise.ReadoutError
PREP0_MEAS1 = 0.03  # P(1|0): Probability of preparing 0 and measuring 1
PREP1_MEAS0 = 0.08  # P(0|1): Probability of preparing 1 and measuring 0
readout_error = ReadoutError(
    [[1 - PREP0_MEAS1, PREP0_MEAS1], [PREP1_MEAS0, 1 - PREP1_MEAS0]]
)
from qiskit_aer.noise import NoiseModel

noise_model = NoiseModel()
noise_model.add_quantum_error(coherent_error.compose(incoherent_error), "x", (0,))
noise_model.add_readout_error(readout_error, (0,))

2.3 Erstellen Sie einen lauten Sampler mit dem Geräuschmodell

from qiskit_aer.primitives import SamplerV2 as Sampler

noisy_sampler = Sampler(options={"backend_options": {"noise_model": noise_model}})

2.4 Quantenschaltungen auf dem verrauschten Sampler ausführen

job = noisy_sampler.run(circuits, shots=400)
result = job.result()
result[0].data.meas.get_counts()

Output:

{'0': 389, '1': 11}

2.5 Plot-Ergebnisse

import matplotlib.pyplot as plt

plt.title("Noisy simulation")
ds = list(range(MAX_DEPTH + 1))
plt.plot(
    ds,
    [result[d].data.meas.expectation_values(["Z"]) for d in ds],
    color="gray",
    linestyle="-",
)
plt.scatter(ds, [result[d].data.meas.expectation_values(["Z"]) for d in ds], marker="o")
plt.hlines(0, xmin=0, xmax=MAX_DEPTH, colors="black")
plt.ylim(-1, 1)
plt.xlabel("Circuit depth")
plt.ylabel("Measured <Z>")
plt.show()

2.6 Ideale Simulation

ideal_sampler = Sampler()
job_ideal = ideal_sampler.run(circuits)
result_ideal = job_ideal.result()
plt.title("Ideal simulation")
ds = list(range(MAX_DEPTH + 1))
plt.plot(
    ds,
    [result_ideal[d].data.meas.expectation_values(["Z"]) for d in ds],
    color="gray",
    linestyle="-",
)
plt.scatter(
    ds, [result_ideal[d].data.meas.expectation_values(["Z"]) for d in ds], marker="o"
)
plt.hlines(0, xmin=0, xmax=MAX_DEPTH, colors="black")
plt.xlabel("Circuit depth")
plt.ylabel("Measured <Z>")
plt.show()

Output:

Output of the previous code cell

2.7 Übung

Indem Sie den nachstehenden Code ändern,

  • Versuchen Sie 25x Anzahl der Schüsse (= 10_000 Schüsse) und stellen Sie sicher, dass eine glattere Darstellung erhalten wird
  • Ändern Sie die Rauschparameter (OVER_ROTATION_ANGLE, AMPLITUDE_DAMPING_PARAM, PREP0_MEAS1 oder PREP1_MEAS0 ) und beobachten Sie, wie sich die Darstellung ändert
OVER_ROTATION_ANGLE = 0.05
coherent_error = coherent_unitary_error(RXGate(OVER_ROTATION_ANGLE).to_matrix())
AMPLITUDE_DAMPING_PARAM = 0.02  # in [0, 1] (0: no error)
incoherent_error = amplitude_damping_error(AMPLITUDE_DAMPING_PARAM)
PREP0_MEAS1 = 0.1  # P(1|0): Probability of preparing 0 and measuring 1
PREP1_MEAS0 = 0.05  # P(0|1): Probability of preparing 1 and measuring 0
readout_error = ReadoutError(
    [[1 - PREP0_MEAS1, PREP0_MEAS1], [PREP1_MEAS0, 1 - PREP1_MEAS0]]
)
noise_model = NoiseModel()
noise_model.add_quantum_error(coherent_error.compose(incoherent_error), "x", (0,))
noise_model.add_readout_error(readout_error, (0,))
options = {
    "backend_options": {"noise_model": noise_model},
}
noisy_sampler = Sampler(options=options)
job = noisy_sampler.run(circuits, shots=400)
result = job.result()
plt.title("Noisy simulation")
ds = list(range(MAX_DEPTH + 1))
plt.plot(
    ds,
    [result[d].data.meas.expectation_values(["Z"]) for d in ds],
    marker="o",
    linestyle="-",
)
plt.hlines(0, xmin=0, xmax=MAX_DEPTH, colors="black")
plt.ylim(-1, 1)
plt.xlabel("Depth")
plt.ylabel("Measured <Z>")
plt.show()

Output:

Output of the previous code cell

2.8 Realistischere Simulation von Störgeräuschen

from qiskit_aer import AerSimulator
from qiskit_ibm_runtime import SamplerV2 as Sampler, QiskitRuntimeService

service = QiskitRuntimeService()
real_backend = service.least_busy(
    operational=True, simulator=False, min_num_qubits=127
)  # Eagle

Output:

<IBMBackend('ibm_strasbourg')>
aer = AerSimulator.from_backend(real_backend)
noisy_sampler = Sampler(mode=aer)
job = noisy_sampler.run(circuits)
result = job.result()
plt.title("Noisy simulation with noise model from real backend")
ds = list(range(MAX_DEPTH + 1))
plt.plot(
    ds,
    [result[d].data.meas.expectation_values(["Z"]) for d in ds],
    marker="o",
    linestyle="-",
)
plt.hlines(0, xmin=0, xmax=MAX_DEPTH, colors="black")
plt.ylim(-1, 1)
plt.xlabel("Depth")
plt.ylabel("Measured <Z>")
plt.show()

Output:

Output of the previous code cell

3. Echte Quantenberechnung mit Fehlerminderung

In diesem Teil zeigen wir, wie man mit Qiskit Estimator fehlerbereinigte Ergebnisse (Erwartungswerte) erhält. Wir betrachten trotterisierte 6-Qubit-Schaltungen zur Simulation der zeitlichen Entwicklung des eindimensionalen Ising-Modells und sehen, wie der Fehler mit der Anzahl der Zeitschritte skaliert.

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

Output:

<IBMBackend('ibm_strasbourg')>
NUM_QUBITS = 6
NUM_TIME_STEPS = list(range(8))
RX_ANGLE = 0.1
RZZ_ANGLE = 0.1

3.1 Schaltungen bauen

# Build circuits with different number of time steps
circuits = []
for n_steps in NUM_TIME_STEPS:
    circ = QuantumCircuit(NUM_QUBITS)
    for i in range(n_steps):
        # rx layer
        for q in range(NUM_QUBITS):
            circ.rx(RX_ANGLE, q)
        # 1st rzz layer
        for q in range(1, NUM_QUBITS - 1, 2):
            circ.rzz(RZZ_ANGLE, q, q + 1)
        # 2nd rzz layer
        for q in range(0, NUM_QUBITS - 1, 2):
            circ.rzz(RZZ_ANGLE, q, q + 1)
    circ.barrier()  # need not to optimize the circuit
    # Uncompute stage
    for i in range(n_steps):
        for q in range(0, NUM_QUBITS - 1, 2):
            circ.rzz(-RZZ_ANGLE, q, q + 1)
        for q in range(1, NUM_QUBITS - 1, 2):
            circ.rzz(-RZZ_ANGLE, q, q + 1)
        for q in range(NUM_QUBITS):
            circ.rx(-RX_ANGLE, q)
    circuits.append(circ)

Um den idealen Ausgang im Voraus zu kennen, verwenden wir Compute-Uncompute-Schaltungen, die aus einer ersten Stufe bestehen, in der die ursprüngliche Schaltung UU angewendet wird, und einer zweiten Stufe, in der sie umgekehrt wird U†U^\dagger. Man beachte, dass das ideale Ergebnis solcher Schaltungen trivialerweise der Eingangszustand ∣000000⟩|000000\rangle ist, der die trivialen Erwartungswerte für alle Pauli-Beobachtungsgrößen hat, zum Beispiel ⟨IIIIIZ⟩=1\langle IIIIIZ \rangle = 1.

# Print the circuit with 2 time steps
circuits[2].draw(output="mpl")

Output:

Output of the previous code cell

Hinweis: Wie oben gezeigt, hat die Schaltung mit kk Zeitschritten 4k4k Zwei-Qubit-Gatterschichten.

obs = SparsePauliOp.from_sparse_list([("Z", [0], 1.0)], num_qubits=NUM_QUBITS)
obs

Output:

SparsePauliOp(['IIIIIZ'],
              coeffs=[1.+0.j])

3.2 Die Schaltkreise transpilieren

Wir transponieren die Schaltungen für das Backend mit Optimierung (optimization_level=1).

from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager

pm = generate_preset_pass_manager(optimization_level=1, backend=backend)
isa_circuits = pm.run(circuits)
display(isa_circuits[2].draw("mpl", idle_wires=False, fold=-1))

Output:

Output of the previous code cell

3.3 Ausführung mit Estimator (mit unterschiedlichen Resilienzstufen)

Die Einstellung der Resilienzstufe (estimator.options.resilience_level) ist der einfachste Weg, um bei der Verwendung von Qiskit Estimator Fehlerminderung anzuwenden. Estimator unterstützt die folgenden Resilienzstufen (Stand: 28.06.2024). Weitere Informationen finden Sie im Leitfaden zur Konfiguration der Fehlerbehebung.

image.png
from qiskit_ibm_runtime import Batch
from qiskit_ibm_runtime import EstimatorV2 as Estimator

jobs = []
job_ids = []
with Batch(backend=backend):
    for resilience_level in [0, 1, 2]:
        estimator = Estimator()
        estimator.options.resilience_level = resilience_level
        job = estimator.run(
            [(circ, obs.apply_layout(circ.layout)) for circ in isa_circuits]
        )
        job_ids.append(job.job_id())
        print(f"Job ID (rl={resilience_level}): {job.job_id()}")
        jobs.append(job)

Output:

Job ID (rl=0): d146vcnmya70008emprg
Job ID (rl=1): d146vdnqf56g0081sva0
Job ID (rl=2): d146ven5z6q00087c61g
# check job status
for job in jobs:
    print(job.status())

Output:

DONE
DONE
DONE
# REPLACE WITH YOUR OWN JOB IDS
jobs = [service.job(job_id) for job_id in job_ids]
# Get results
results = [job.result() for job in jobs]

3.4 Plot-Ergebnisse

plt.title("Error mitigation with different resilience levels")
labels = ["0 (No mitigation)", "1 (TREX)", "2 (ZNE + Gate twirling)"]
steps = NUM_TIME_STEPS
for result, label in zip(results, labels):
    plt.errorbar(
        x=steps,
        y=[result[s].data.evs for s in steps],
        yerr=[result[s].data.stds for s in steps],
        marker="o",
        linestyle="-",
        capsize=4,
        label=label,
    )
plt.hlines(
    1.0, min(steps), max(steps), linestyle="dashed", label="Ideal", colors="black"
)
plt.xlabel("Time steps")
plt.ylabel("Mitigated <IIIIIZ>")
plt.legend()
plt.show()

Output:

Output of the previous code cell

4. (Optional) Optionen zur Fehlerbehebung anpassen

Wir können die Anwendung der Fehlerbegrenzungstechniken über die unten aufgeführten Optionen individuell anpassen.

# TREX
estimator.options.twirling.enable_measure = True
estimator.options.twirling.num_randomizations = "auto"
estimator.options.twirling.shots_per_randomization = "auto"

# Gate twirling
estimator.options.twirling.enable_gates = True
# ZNE
estimator.options.resilience.zne_mitigation = True
estimator.options.resilience.zne.noise_factors = [1, 3, 5]
estimator.options.resilience.zne.extrapolator = ("exponential", "linear")

# Dynamical decoupling
estimator.options.dynamical_decoupling.enable = True  # Default: False
estimator.options.dynamical_decoupling.sequence_type = "XX"

# Other options
estimator.options.default_shots = 10_000

In den folgenden Leitfäden und der API-Referenz finden Sie Einzelheiten zu den Optionen zur Fehlerbehebung.

War diese Seite hilfreich?
Melden Sie auf GitHub einen Fehler, einen Tippfehler oder fordern Sie Inhalte an.