Bruit quantique et atténuation des erreurs
Toshinari Itoko (28 juin 2024)
Télécharger le pdf de la conférence originale. Notez que certains extraits de code peuvent devenir obsolètes car il s'agit d'images statiques.
Le temps approximatif d'exécution de cette expérience par la QPU est de 1 m 40 s.
1. Introduction
Tout au long de cette leçon, nous examinerons le bruit et la manière dont il peut être atténué sur les ordinateurs quantiques. Nous commencerons par examiner les effets du bruit à l'aide d'un simulateur qui peut simuler le bruit de plusieurs façons, notamment en utilisant des profils de bruit provenant de véritables ordinateurs quantiques. Nous passerons ensuite à de véritables ordinateurs quantiques, dans lesquels le bruit est inhérent. Nous examinerons les effets de l'atténuation des erreurs, y compris les combinaisons d'éléments tels que l'extrapolation à bruit nul (ZNE) et le tourbillonnement de portes.
Nous commencerons par charger quelques paquets.
# !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. Simulation bruyante sans atténuation des erreurs
Qiskit Aer est un simulateur classique pour l'informatique quantique. Il peut simuler non seulement l'exécution idéale mais aussi l'exécution bruyante de circuits quantiques. Ce carnet montre comment exécuter une simulation bruyante à l'aide de Qiskit Aer :
- Construire un modèle de bruit
- Construire un échantillonneur bruyant (simulateur) avec le modèle de bruit
- Exécuter un circuit quantique sur l'échantillonneur bruyant
noise_model = NoiseModel()
...
noisy_sampler = Sampler(options={"backend_options": {"noise_model": noise_model}})
job = noisy_sampler.run([circuit])
2.1 Construire un circuit de test
Nous considérons des circuits jouets à 1 qubit qui répètent simplement X portes d fois (d=0... 100) et mesurer l'observable 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 Construire un modèle de bruit
Pour effectuer une simulation bruyante, nous devons spécifier NoiseModel. Nous montrons comment construire NoiseModel dans cette section.
Nous devons d'abord définir les erreurs quantiques (ou de lecture) à ajouter à un modèle de bruit.
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 Construisez un échantillonneur bruyant avec le modèle de bruit
from qiskit_aer.primitives import SamplerV2 as Sampler
noisy_sampler = Sampler(options={"backend_options": {"noise_model": noise_model}})2.4 Exécutez des circuits quantiques sur l'échantillonneur bruyant
job = noisy_sampler.run(circuits, shots=400)result = job.result()result[0].data.meas.get_counts()Output:
{'0': 389, '1': 11}
2.5 Résultats du tracé
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 Simulation idéale
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 Exercice
En modifiant le code ci-dessous,
- Essayer 25x le nombre de prises de vue (= 10 000 prises de vue) et s'assurer qu'un tracé plus lisse est obtenu
- Modifiez les paramètres de bruit (OVER_ROTATION_ANGLE, AMPLITUDE_DAMPING_PARAM, PREP0_MEAS1, ou PREP1_MEAS0 ) et observez les changements sur le graphique
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 Simulation bruyante plus réaliste
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. Calcul quantique réel avec atténuation des erreurs
Dans cette partie, nous montrons comment obtenir des résultats à erreur atténuée (valeurs d'espérance) à l'aide de l'estimateur Qiskit. Nous considérons des circuits trotterisés à 6 qubits pour simuler l'évolution temporelle d'un modèle d'Ising unidimensionnel et nous voyons comment l'erreur évolue en fonction du nombre de pas de temps.
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 Construire des circuits
# 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)Pour connaître la sortie idéale à l'avance, nous utilisons des circuits de calcul-décompilation qui consistent en un premier étage où le circuit original est appliqué, et un deuxième étage où il est inversé . Notez que le résultat idéal de ces circuits sera trivialement l'état d'entrée , qui a les valeurs triviales de l'espérance pour toutes les observables de Pauli, par exemple, .
# Print the circuit with 2 time steps
circuits[2].draw(output="mpl")Output:
Remarque : comme indiqué ci-dessus, le circuit avec des pas de temps de aura couches de portes à deux qubits.
obs = SparsePauliOp.from_sparse_list([("Z", [0], 1.0)], num_qubits=NUM_QUBITS)
obsOutput:
SparsePauliOp(['IIIIIZ'],
coeffs=[1.+0.j])
3.2 Transpiler les circuits
Nous transposons les circuits pour le backend avec optimisation (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 Exécuter à l'aide de l'estimateur (avec différents niveaux de résilience)
Définir le niveau de résilience (estimator.options.resilience_level) est le moyen le plus simple d'appliquer une atténuation des erreurs lors de l'utilisation de Qiskit Estimator. Estimator prend en charge les niveaux de résilience suivants (au 28 juin 2024). Pour plus de détails, consultez le guide de configuration de l'atténuation des erreurs.
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 Résultats du tracé
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. (Facultatif) Personnaliser les options d'atténuation des erreurs
Nous pouvons personnaliser l'application des techniques d'atténuation des erreurs grâce aux options présentées ci-dessous.
# 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_000Voir les guides suivants et la référence API pour les détails des options d'atténuation des erreurs.