Rumore quantistico e mitigazione degli errori
Toshinari Itoko (28 giugno 2024)
Scarica il pdf della lezione originale. Si noti che alcuni frammenti di codice potrebbero diventare deprecati, poiché si tratta di immagini statiche.
Il tempo approssimativo della QPU per eseguire questo esperimento è di 1 m 40 s.
1. Introduzione
Nel corso di questa lezione esamineremo il rumore e il modo in cui può essere attenuato nei computer quantistici. Inizieremo ad analizzare gli effetti del rumore utilizzando un simulatore che può simulare il rumore in vari modi, tra cui l'utilizzo di profili di rumore provenienti da computer quantistici reali. Poi passeremo ai veri computer quantistici, nei quali il rumore è intrinseco. Verranno esaminati gli effetti della mitigazione degli errori, comprese le combinazioni di elementi come l'estrapolazione a rumore zero (ZNE) e il gate-twirling.
Inizieremo caricando alcuni pacchetti.
# !pip install qiskit qiskit_aer qiskit_ibm_runtime
# !pip install jupyter
# !pip install matplotlib pylatexencimport 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. Simulazione rumorosa senza mitigazione degli errori
Qiskit Aer è un simulatore classico per il calcolo quantistico. Può simulare non solo l'esecuzione ideale, ma anche l'esecuzione rumorosa di circuiti quantistici. Questo notebook mostra come eseguire una simulazione rumorosa utilizzando Qiskit Aer:
- Costruire un modello di rumore
- Costruire un campionatore rumoroso (simulatore) con il modello di rumore
- Eseguire un circuito quantistico sul campionatore rumoroso
noise_model = NoiseModel()
...
noisy_sampler = Sampler(options={"backend_options": {"noise_model": noise_model}})
job = noisy_sampler.run([circuit])
2.1 Costruisci un circuito di prova
Consideriamo circuiti giocattolo a 1 qubit che ripetono semplicemente X porte d volte (d=0... 100) e misurare l'osservabile Z .
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:
from qiskit.quantum_info import SparsePauliOp
obs = SparsePauliOp.from_list([("Z", 1.0)])
obsOutput:
SparsePauliOp(['Z'],
coeffs=[1.+0.j])
2.2 Costruire un modello di rumore
Per effettuare una simulazione rumorosa, è necessario specificare NoiseModel. In questa sezione mostriamo come costruire NoiseModel .
Occorre innanzitutto definire gli errori quantistici (o di lettura) da aggiungere a un modello di rumore.
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 Costruisci un campionatore rumoroso con il modello di rumore
from qiskit_aer.primitives import SamplerV2 as Sampler
noisy_sampler = Sampler(options={"backend_options": {"noise_model": noise_model}})2.4 Esegui circuiti quantistici sul campionatore rumoroso
job = noisy_sampler.run(circuits, shots=400)result = job.result()result[0].data.meas.get_counts()Output:
{'0': 389, '1': 11}
2.5 Risultati del grafico
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 Simulazione ideale
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:
2.7 Esercizio fisico
Modificando il codice sottostante,
- Provare 25x numero di scatti (= 10_000 scatti) e assicurarsi che si ottenga un grafico più omogeneo
- Modificare i parametri del rumore (OVER_ROTATION_ANGLE, AMPLITUDE_DAMPING_PARAM, PREP0_MEAS1, o PREP1_MEAS0 ) e vedere come cambia il grafico
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:
2.8 Simulazione rumorosa più realistica
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
) # EagleOutput:
<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:
3. Calcolo quantistico reale con mitigazione degli errori
In questa parte, dimostriamo come ottenere risultati mitigati dall'errore (valori di aspettativa) utilizzando Qiskit Estimator. Consideriamo circuiti trotterizzati a 6-qubit per simulare l'evoluzione temporale del modello di Ising unidimensionale e vediamo come l'errore scala rispetto al numero di passi temporali.
backend = service.least_busy(
operational=True, simulator=False, min_num_qubits=127
) # Eagle
backendOutput:
<IBMBackend('ibm_strasbourg')>
NUM_QUBITS = 6
NUM_TIME_STEPS = list(range(8))
RX_ANGLE = 0.1
RZZ_ANGLE = 0.13.1 Costruire circuiti
# 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)Per conoscere in anticipo l'uscita ideale, utilizziamo circuiti di calcolo-non calcolo che consistono in un primo stadio in cui viene applicato il circuito originale e in un secondo stadio in cui viene invertito . Si noti che il risultato ideale di tali circuiti sarà banalmente lo stato di ingresso , che ha i valori di aspettativa banali per qualsiasi osservabile di Pauli, ad esempio .
# Print the circuit with 2 time steps
circuits[2].draw(output="mpl")Output:
Nota: come mostrato sopra, il circuito con passi temporali avrà strati di gate a due-qubit.
obs = SparsePauliOp.from_sparse_list([("Z", [0], 1.0)], num_qubits=NUM_QUBITS)
obsOutput:
SparsePauliOp(['IIIIIZ'],
coeffs=[1.+0.j])
3.2 Traspila i circuiti
Trasponiamo i circuiti per il backend con l'ottimizzazione (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:
3.3 Eseguire utilizzando Estimator (con diversi livelli di resilienza)
Impostare il livello di resilienza (estimator.options.resilience_level) è il modo più semplice per applicare la mitigazione degli errori quando si utilizza Qiskit Estimator. Estimator supporta i seguenti livelli di resilienza (al 28/06/2024). Per ulteriori dettagli, consulta la guida alla configurazione della mitigazione degli errori.
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 Risultati del grafico
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:
4. (Facoltativo) Personalizza le opzioni di mitigazione degli errori
È possibile personalizzare l'applicazione delle tecniche di mitigazione degli errori tramite le opzioni illustrate di seguito.
# 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_000Per i dettagli sulle opzioni di attenuazione degli errori, consultare le seguenti guide e il riferimento API.