Aller au contenu principal

Construire des modèles de bruit

Versions des packages

Le code de cette page a été développé avec les dépendances suivantes. Nous recommandons d'utiliser ces versions ou des versions plus récentes.

qiskit[all]~=2.3.0
qiskit-ibm-runtime~=0.43.1
qiskit-aer~=0.17

Cette page montre comment utiliser le module noise de Qiskit Aer pour construire des modèles de bruit permettant de simuler des circuits quantiques en présence d'erreurs. C'est utile pour émuler des processeurs quantiques bruités et pour étudier les effets du bruit sur l'exécution d'algorithmes quantiques.

# Added by doQumentation — required packages for this notebook
!pip install -q numpy qiskit qiskit-aer qiskit-ibm-runtime
import numpy as np
from qiskit import QuantumCircuit
from qiskit.quantum_info import Kraus, SuperOp
from qiskit.visualization import plot_histogram
from qiskit.transpiler import generate_preset_pass_manager
from qiskit_aer import AerSimulator

# Import from Qiskit Aer noise module
from qiskit_aer.noise import (
NoiseModel,
QuantumError,
ReadoutError,
depolarizing_error,
pauli_error,
thermal_relaxation_error,
)

Le module noise de Qiskit Aer

Le module noise de Qiskit Aer contient des classes Python pour construire des modèles de bruit personnalisés pour la simulation. Il y a trois classes essentielles :

  1. La classe NoiseModel qui stocke un modèle de bruit utilisé pour la simulation bruitée.

  2. La classe QuantumError qui décrit les erreurs de porte CPTP. Elles peuvent être appliquées :

    • Après les instructions gate ou reset
    • Avant les instructions measure.
  3. La classe ReadoutError qui décrit les erreurs classiques de lecture.

Initialisation d'un modèle de bruit à partir d'un backend

Tu peux initialiser un modèle de bruit avec des paramètres issus des dernières données de calibration d'un backend physique :

from qiskit_ibm_runtime import QiskitRuntimeService

service = QiskitRuntimeService()
backend = service.backend("ibm_fez")
noise_model = NoiseModel.from_backend(backend)

Cela produit un modèle de bruit qui approxime grossièrement les erreurs que l'on rencontrerait en utilisant ce backend. Si tu souhaites un contrôle plus fin sur les paramètres du modèle de bruit, tu devras créer ton propre modèle de bruit, comme décrit dans la suite de cette page.

Erreurs quantiques

Plutôt que de manipuler directement l'objet QuantumError, de nombreuses fonctions utilitaires existent pour générer automatiquement un type spécifique d'erreur quantique paramétrée. Elles sont contenues dans le module noise et couvrent de nombreux types d'erreurs couramment utilisés en recherche sur l'informatique quantique. Les noms des fonctions et le type d'erreur qu'elles retournent sont :

Fonction d'erreur standardDétails
kraus_errorun canal d'erreur CPTP général à n qubits donné sous forme d'une liste de matrices de Kraus [K0,...][K_0, ...].
mixed_unitary_errorune erreur unitaire mixte à n qubits donnée sous forme d'une liste de matrices unitaires et de probabilités [(U0,p0),...][(U_0, p_0),...].
coherent_unitary_errorune erreur unitaire cohérente à n qubits donnée sous forme d'une seule matrice unitaire UU.
pauli_errorun canal d'erreur de Pauli à n qubits (unitaire mixte) donné sous forme d'une liste de Pauli et de probabilités [(P0,p0),...][(P_0, p_0),...]
depolarizing_errorun canal d'erreur dépolarisant à n qubits paramétré par une probabilité de dépolarisation pp.
reset_errorune erreur de reset à un seul qubit paramétrée par les probabilités p0,p1p_0, p_1 de réinitialisation vers l'état 0\vert0\rangle, 1\vert1\rangle.
thermal_relaxation_errorun canal de relaxation thermique à un seul qubit paramétré par les constantes de temps de relaxation T1T_1, T2T_2, le temps de porte tt, et la population thermique de l'état excité p1p_1.
phase_amplitude_damping_errorUn canal d'erreur d'amortissement de phase et d'amplitude combiné généralisé à un seul qubit, défini par un paramètre d'amortissement d'amplitude λ\lambda, un paramètre d'amortissement de phase γ\gamma, et une population thermique de l'état excité p1p_1.
amplitude_damping_errorUn canal d'erreur d'amortissement d'amplitude généralisé à un seul qubit, défini par un paramètre d'amortissement d'amplitude λ\lambda, et une population thermique de l'état excité p1p_1.
phase_damping_errorUn canal d'erreur d'amortissement de phase à un seul qubit défini par un paramètre d'amortissement de phase γ\gamma.

Combinaison d'erreurs quantiques

Les instances de QuantumError peuvent être combinées en utilisant la composition, le produit tensoriel et l'expansion tensorielle (produit tensoriel en ordre inversé) pour produire de nouvelles QuantumErrors :

  • Composition : E(ρ)=E2(E1(ρ))\cal{E}(\rho)=\cal{E_2}(\cal{E_1}(\rho)) avec error = error1.compose(error2)
  • Produit tensoriel : E(ρ)=(E1E2)(ρ)\cal{E}(\rho) =(\cal{E_1}\otimes\cal{E_2})(\rho) avec error = error1.tensor(error2)
  • Produit d'expansion : E(ρ)=(E2E1)(ρ)\cal{E}(\rho) =(\cal{E_2}\otimes\cal{E_1})(\rho) avec error = error1.expand(error2)

Exemple

Pour construire une erreur de bit-flip à 5% sur un seul qubit :

# Construct a 1-qubit bit-flip and phase-flip errors
p_error = 0.05
bit_flip = pauli_error([("X", p_error), ("I", 1 - p_error)])
phase_flip = pauli_error([("Z", p_error), ("I", 1 - p_error)])
print(bit_flip)
print(phase_flip)
QuantumError on 1 qubits. Noise circuits:
P(0) = 0.05, Circuit =
┌───┐
q: ┤ X ├
└───┘
P(1) = 0.95, Circuit =
┌───┐
q: ┤ I ├
└───┘
QuantumError on 1 qubits. Noise circuits:
P(0) = 0.05, Circuit =
┌───┐
q: ┤ Z ├
└───┘
P(1) = 0.95, Circuit =
┌───┐
q: ┤ I ├
└───┘
# Compose two bit-flip and phase-flip errors
bitphase_flip = bit_flip.compose(phase_flip)
print(bitphase_flip)
QuantumError on 1 qubits. Noise circuits:
P(0) = 0.0025000000000000005, Circuit =
┌───┐┌───┐
q: ┤ X ├┤ Z ├
└───┘└───┘
P(1) = 0.0475, Circuit =
┌───┐┌───┐
q: ┤ X ├┤ I ├
└───┘└───┘
P(2) = 0.0475, Circuit =
┌───┐┌───┐
q: ┤ I ├┤ Z ├
└───┘└───┘
P(3) = 0.9025, Circuit =
┌───┐┌───┐
q: ┤ I ├┤ I ├
└───┘└───┘
# Tensor product two bit-flip and phase-flip errors with
# bit-flip on qubit-0, phase-flip on qubit-1
error2 = phase_flip.tensor(bit_flip)
print(error2)
QuantumError on 2 qubits. Noise circuits:
P(0) = 0.0025000000000000005, Circuit =
┌───┐
q_0: ┤ X ├
├───┤
q_1: ┤ Z ├
└───┘
P(1) = 0.0475, Circuit =
┌───┐
q_0: ┤ I ├
├───┤
q_1: ┤ Z ├
└───┘
P(2) = 0.0475, Circuit =
┌───┐
q_0: ┤ X ├
├───┤
q_1: ┤ I ├
└───┘
P(3) = 0.9025, Circuit =
┌───┐
q_0: ┤ I ├
├───┤
q_1: ┤ I ├
└───┘

Conversion vers et depuis des opérateurs QuantumChannel

On peut aussi convertir dans les deux sens entre les objets QuantumError de Qiskit Aer et les objets QuantumChannel de Qiskit.

# Convert to Kraus operator
bit_flip_kraus = Kraus(bit_flip)
print(bit_flip_kraus)
Kraus([[[-9.74679434e-01+0.j,  0.00000000e+00+0.j],
[ 0.00000000e+00+0.j, -9.74679434e-01+0.j]],

[[ 0.00000000e+00+0.j, 2.23606798e-01+0.j],
[ 2.23606798e-01+0.j, -4.96506831e-17+0.j]]],
input_dims=(2,), output_dims=(2,))
# Convert to Superoperator
phase_flip_sop = SuperOp(phase_flip)
print(phase_flip_sop)
SuperOp([[1. +0.j, 0. +0.j, 0. +0.j, 0. +0.j],
[0. +0.j, 0.9+0.j, 0. +0.j, 0. +0.j],
[0. +0.j, 0. +0.j, 0.9+0.j, 0. +0.j],
[0. +0.j, 0. +0.j, 0. +0.j, 1. +0.j]],
input_dims=(2,), output_dims=(2,))
# Convert back to a quantum error
print(QuantumError(bit_flip_kraus))

# Check conversion is equivalent to original error
QuantumError(bit_flip_kraus) == bit_flip
QuantumError on 1 qubits. Noise circuits:
P(0) = 1.0, Circuit =
┌───────┐
q: ┤ kraus ├
└───────┘
True

Erreur de lecture

Les erreurs classiques de lecture sont spécifiées par une liste de vecteurs de probabilités d'assignation P(AB)P(A|B) :

  • AA est la valeur du bit classique enregistrée
  • BB est la valeur réelle du bit retournée par la mesure

Par exemple, pour un qubit : P(AB)=[P(A0),P(A1)] P(A|B) = [P(A|0), P(A|1)].

# Measurement misassignment probabilities
p0given1 = 0.1
p1given0 = 0.05

ReadoutError([[1 - p1given0, p1given0], [p0given1, 1 - p0given1]])
ReadoutError([[0.95 0.05]
[0.1 0.9 ]])

Les erreurs de lecture peuvent aussi être combinées avec compose, tensor et expand, tout comme les erreurs quantiques.

Ajouter des erreurs à un modèle de bruit

Lors de l'ajout d'une erreur quantique à un modèle de bruit, on doit spécifier le type d'instruction sur laquelle elle agit et les qubits auxquels l'appliquer. Il existe deux types d'erreurs quantiques :

  1. Erreur quantique sur tous les qubits
  2. Erreur quantique sur des qubits spécifiques

1. Erreur quantique sur tous les qubits

Cela applique la même erreur à chaque occurrence d'une instruction, quel que soit le qubit sur lequel elle agit.

Elle s'ajoute avec noise_model.add_all_qubit_quantum_error(error, instructions) :

# Create an empty noise model
noise_model = NoiseModel()

# Add depolarizing error to all single qubit u1, u2, u3 gates
error = depolarizing_error(0.05, 1)
noise_model.add_all_qubit_quantum_error(error, ["u1", "u2", "u3"])

# Print noise model info
print(noise_model)
NoiseModel:
Basis gates: ['cx', 'id', 'rz', 'sx', 'u1', 'u2', 'u3']
Instructions with noise: ['u3', 'u2', 'u1']
All-qubits errors: ['u1', 'u2', 'u3']

2. Erreur quantique sur des qubits spécifiques

Cela applique l'erreur à chaque occurrence d'une instruction agissant sur une liste de qubits spécifiée. Remarque que l'ordre des qubits est important : par exemple, une erreur appliquée aux qubits [0, 1] pour une porte à deux qubits est différente de celle appliquée aux qubits [1, 0].

Elle s'ajoute avec noise_model.add_quantum_error(error, instructions, qubits) :

# Create an empty noise model
noise_model = NoiseModel()

# Add depolarizing error to all single qubit u1, u2, u3 gates on qubit 0 only
error = depolarizing_error(0.05, 1)
noise_model.add_quantum_error(error, ["u1", "u2", "u3"], [0])

# Print noise model info
print(noise_model)
NoiseModel:
Basis gates: ['cx', 'id', 'rz', 'sx', 'u1', 'u2', 'u3']
Instructions with noise: ['u3', 'u2', 'u1']
Qubits with noise: [0]
Specific qubit errors: [('u1', (0,)), ('u2', (0,)), ('u3', (0,))]

Remarque sur les erreurs quantiques non locales

NoiseModel ne prend pas en charge l'ajout d'erreurs quantiques non locales sur des qubits. Elles doivent être gérées en dehors du NoiseModel. Cela suggère d'écrire ta propre passe de transpilation (TransformationPass) et d'exécuter cette passe juste avant le simulateur si tu dois insérer des erreurs quantiques dans ton circuit selon tes propres conditions.

Exécuter une simulation bruitée avec un modèle de bruit

La commande AerSimulator(noise_model=noise_model) renvoie un simulateur configuré selon le modèle de bruit donné. En plus de définir le modèle de bruit du simulateur, elle remplace également les portes de base du simulateur en fonction des portes du modèle de bruit.

Exemples de modèles de bruit

Nous allons maintenant donner quelques exemples de modèles de bruit. Pour nos démonstrations, nous utilisons un circuit de test simple générant un état GHZ à n qubits :

# System Specification
n_qubits = 4
circ = QuantumCircuit(n_qubits)

# Test Circuit
circ.h(0)
for qubit in range(n_qubits - 1):
circ.cx(qubit, qubit + 1)
circ.measure_all()
print(circ)
┌───┐                ░ ┌─┐
q_0: ┤ H ├──■─────────────░─┤M├─────────
└───┘┌─┴─┐ ░ └╥┘┌─┐
q_1: ─────┤ X ├──■────────░──╫─┤M├──────
└───┘┌─┴─┐ ░ ║ └╥┘┌─┐
q_2: ──────────┤ X ├──■───░──╫──╫─┤M├───
└───┘┌─┴─┐ ░ ║ ║ └╥┘┌─┐
q_3: ───────────────┤ X ├─░──╫──╫──╫─┤M├
└───┘ ░ ║ ║ ║ └╥┘
meas: 4/════════════════════════╩══╩══╩══╩═
0 1 2 3

Simulation idéale

# Ideal simulator and execution
sim_ideal = AerSimulator()
result_ideal = sim_ideal.run(circ).result()
plot_histogram(result_ideal.get_counts(0))

Output of the previous code cell

Exemple de bruit 1 : modèle de bruit simple à bit-flip

Considérons un exemple de modèle de bruit simplifié, courant en recherche sur la théorie de l'information quantique :

  • Lors de l'application d'une porte à un seul qubit, l'état du qubit est inversé avec la probabilité p_gate1.
  • Lors de l'application d'une porte à deux qubits, des erreurs sur un seul qubit sont appliquées à chaque qubit.
  • Lors de la réinitialisation d'un qubit, le qubit est réinitialisé à 1 au lieu de 0 avec la probabilité p_reset.
  • Lors de la mesure d'un qubit, l'état du qubit est inversé avec la probabilité p_meas.
# Example error probabilities
p_reset = 0.03
p_meas = 0.1
p_gate1 = 0.05

# QuantumError objects
error_reset = pauli_error([("X", p_reset), ("I", 1 - p_reset)])
error_meas = pauli_error([("X", p_meas), ("I", 1 - p_meas)])
error_gate1 = pauli_error([("X", p_gate1), ("I", 1 - p_gate1)])
error_gate2 = error_gate1.tensor(error_gate1)

# Add errors to noise model
noise_bit_flip = NoiseModel()
noise_bit_flip.add_all_qubit_quantum_error(error_reset, "reset")
noise_bit_flip.add_all_qubit_quantum_error(error_meas, "measure")
noise_bit_flip.add_all_qubit_quantum_error(error_gate1, ["u1", "u2", "u3"])
noise_bit_flip.add_all_qubit_quantum_error(error_gate2, ["cx"])

print(noise_bit_flip)
NoiseModel:
Basis gates: ['cx', 'id', 'rz', 'sx', 'u1', 'u2', 'u3']
Instructions with noise: ['u3', 'u2', 'measure', 'cx', 'reset', 'u1']
All-qubits errors: ['reset', 'measure', 'u1', 'u2', 'u3', 'cx']

Exécuter la simulation bruitée

# Create noisy simulator backend
sim_noise = AerSimulator(noise_model=noise_bit_flip)

# Transpile circuit for noisy basis gates
passmanager = generate_preset_pass_manager(
optimization_level=3, backend=sim_noise
)
circ_tnoise = passmanager.run(circ)

# Run and get counts
result_bit_flip = sim_noise.run(circ_tnoise).result()
counts_bit_flip = result_bit_flip.get_counts(0)

# Plot noisy output
plot_histogram(counts_bit_flip)

Output of the previous code cell

Exemple 2 : relaxation thermique T1/T2

Considérons maintenant un modèle d'erreur plus réaliste basé sur la relaxation thermique avec l'environnement du qubit :

  • Chaque qubit est paramétré par une constante de temps de relaxation thermique T1T_1 et une constante de temps de déphasage T2T_2.
  • Remarque que l'on doit avoir T22T1T_2 \le 2 T_1.
  • Les taux d'erreur sur les instructions sont déterminés par les temps de porte et les valeurs T1T_1, T2T_2 des qubits.
# T1 and T2 values for qubits 0-3
T1s = np.random.normal(
50e3, 10e3, 4
) # Sampled from normal distribution mean 50 microsec
T2s = np.random.normal(
70e3, 10e3, 4
) # Sampled from normal distribution mean 50 microsec

# Truncate random T2s <= T1s
T2s = np.array([min(T2s[j], 2 * T1s[j]) for j in range(4)])

# Instruction times (in nanoseconds)
time_u1 = 0 # virtual gate
time_u2 = 50 # (single X90 pulse)
time_u3 = 100 # (two X90 pulses)
time_cx = 300
time_reset = 1000 # 1 microsecond
time_measure = 1000 # 1 microsecond

# QuantumError objects
errors_reset = [
thermal_relaxation_error(t1, t2, time_reset) for t1, t2 in zip(T1s, T2s)
]
errors_measure = [
thermal_relaxation_error(t1, t2, time_measure) for t1, t2 in zip(T1s, T2s)
]
errors_u1 = [
thermal_relaxation_error(t1, t2, time_u1) for t1, t2 in zip(T1s, T2s)
]
errors_u2 = [
thermal_relaxation_error(t1, t2, time_u2) for t1, t2 in zip(T1s, T2s)
]
errors_u3 = [
thermal_relaxation_error(t1, t2, time_u3) for t1, t2 in zip(T1s, T2s)
]
errors_cx = [
[
thermal_relaxation_error(t1a, t2a, time_cx).expand(
thermal_relaxation_error(t1b, t2b, time_cx)
)
for t1a, t2a in zip(T1s, T2s)
]
for t1b, t2b in zip(T1s, T2s)
]

# Add errors to noise model
noise_thermal = NoiseModel()
for j in range(4):
noise_thermal.add_quantum_error(errors_reset[j], "reset", [j])
noise_thermal.add_quantum_error(errors_measure[j], "measure", [j])
noise_thermal.add_quantum_error(errors_u1[j], "u1", [j])
noise_thermal.add_quantum_error(errors_u2[j], "u2", [j])
noise_thermal.add_quantum_error(errors_u3[j], "u3", [j])
for k in range(4):
noise_thermal.add_quantum_error(errors_cx[j][k], "cx", [j, k])

print(noise_thermal)
NoiseModel:
Basis gates: ['cx', 'id', 'rz', 'sx', 'u2', 'u3']
Instructions with noise: ['u3', 'u2', 'measure', 'cx', 'reset']
Qubits with noise: [0, 1, 2, 3]
Specific qubit errors: [('reset', (0,)), ('reset', (1,)), ('reset', (2,)), ('reset', (3,)), ('measure', (0,)), ('measure', (1,)), ('measure', (2,)), ('measure', (3,)), ('u2', (0,)), ('u2', (1,)), ('u2', (2,)), ('u2', (3,)), ('u3', (0,)), ('u3', (1,)), ('u3', (2,)), ('u3', (3,)), ('cx', (0, 0)), ('cx', (0, 1)), ('cx', (0, 2)), ('cx', (0, 3)), ('cx', (1, 0)), ('cx', (1, 1)), ('cx', (1, 2)), ('cx', (1, 3)), ('cx', (2, 0)), ('cx', (2, 1)), ('cx', (2, 2)), ('cx', (2, 3)), ('cx', (3, 0)), ('cx', (3, 1)), ('cx', (3, 2)), ('cx', (3, 3))]

Exécuter la simulation bruitée

# Run the noisy simulation
sim_thermal = AerSimulator(noise_model=noise_thermal)

# Transpile circuit for noisy basis gates
passmanager = generate_preset_pass_manager(
optimization_level=3, backend=sim_thermal
)
circ_tthermal = passmanager.run(circ)

# Run and get counts
result_thermal = sim_thermal.run(circ_tthermal).result()
counts_thermal = result_thermal.get_counts(0)

# Plot noisy output
plot_histogram(counts_thermal)

Output of the previous code cell

Prochaines étapes

Recommandations