Aller au contenu principal

Assistant d'apprentissage du bruit

Versions des paquets

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.4.1
qiskit-ibm-runtime~=0.47.0
samplomatic~=0.18.0

Les techniques d'atténuation d'erreurs PEA et PEC utilisent toutes deux un composant d'apprentissage du bruit basé sur un modèle de bruit de Pauli-Lindblad, qui est généralement géré lors de l'exécution après la soumission d'un ou plusieurs jobs via qiskit-ibm-runtime, sans accès local au modèle de bruit ajusté. Cependant, depuis qiskit-ibm-runtime v0.27.1, une classe NoiseLearner et la classe associée NoiseLearnerOptions ont été créées pour obtenir les résultats de ces expériences d'apprentissage du bruit. Ces résultats peuvent ensuite être stockés localement sous forme de NoiseLearnerResult et utilisés comme entrée dans des expériences ultérieures. Cette page offre un aperçu de son utilisation et des options associées disponibles.

De plus, à partir de qiskit-ibm-runtime v0.47.0, il existe une nouvelle classe NoiseLearnerV3 compatible avec la primitive Executor. Cette nouvelle version, également intégrée au modèle d'exécution dirigée, te permet de spécifier explicitement les couches que tu souhaites apprendre.

remarque

NoiseLearner ne fonctionne qu'avec EstimatorV2 et NoiseLearnerV3 ne fonctionne qu'avec Executor.

NoiseLearner

Vue d'ensemble

La classe NoiseLearner effectue des expériences qui caractérisent les processus de bruit en se basant sur un modèle de bruit de Pauli-Lindblad pour un ou plusieurs circuits. Elle possède une méthode run() qui exécute les expériences d'apprentissage et prend en entrée soit une liste de circuits, soit un PUB, et retourne un NoiseLearnerResult contenant les canaux de bruit appris ainsi que des métadonnées sur le ou les jobs soumis. Voici un extrait de code illustrant l'utilisation de cet assistant.

# Added by doQumentation — required packages for this notebook
!pip install -q numpy qiskit qiskit-ibm-runtime samplomatic
from qiskit import QuantumCircuit
from qiskit.transpiler import CouplingMap
from qiskit.transpiler import generate_preset_pass_manager

from qiskit_ibm_runtime import QiskitRuntimeService, EstimatorV2
from qiskit_ibm_runtime.noise_learner import NoiseLearner
from qiskit_ibm_runtime.options import (
NoiseLearnerOptions,
ResilienceOptionsV2,
EstimatorOptions,
)

# Build a circuit with two entangling layers
num_qubits = 27
edges = list(CouplingMap.from_line(num_qubits, bidirectional=False))
even_edges = edges[::2]
odd_edges = edges[1::2]

circuit = QuantumCircuit(num_qubits)
for pair in even_edges:
circuit.cx(pair[0], pair[1])
for pair in odd_edges:
circuit.cx(pair[0], pair[1])

# Choose a backend to run on
service = QiskitRuntimeService()
backend = service.least_busy()

# Transpile the circuit for execution
pm = generate_preset_pass_manager(backend=backend, optimization_level=3)
circuit_to_learn = pm.run(circuit)

# Instantiate a NoiseLearner object and execute the noise learning program
learner = NoiseLearner(mode=backend)
job = learner.run([circuit_to_learn])
noise_model = job.result()

Le NoiseLearnerResult.data résultant est une liste d'objets LayerError contenant le modèle de bruit pour chaque couche d'intrication individuelle appartenant aux circuits cibles. Chaque LayerError stocke les informations de la couche, sous forme d'un circuit et d'un ensemble d'étiquettes de qubits, ainsi que le PauliLindbladError pour le modèle de bruit appris pour cette couche donnée.

import numpy

print(
f"Noise learner result contains {len(noise_model.data)} entries"
f" and has the following type:\n {type(noise_model)}\n"
)
print(
f"Each element of `NoiseLearnerResult` then contains"
f" an object of type:\n {type(noise_model.data[0])}\n"
)
# Results are truncated
with numpy.printoptions(threshold=200):
print(
f"And each of these `LayerError` objects possess"
f" data on the generators for the error channel: \n{noise_model.data[0].error.generators}\n"
)
# Results are truncated
with numpy.printoptions(threshold=200):
print(
f"Along with the error rates: \n{noise_model.data[0].error.rates}\n"
)
Noise learner result contains 2 entries and has the following type:
<class 'qiskit_ibm_runtime.utils.noise_learner_result.NoiseLearnerResult'>

Each element of `NoiseLearnerResult` then contains an object of type:
<class 'qiskit_ibm_runtime.utils.noise_learner_result.LayerError'>

And each of these `LayerError` objects possess data on the generators for the error channel:
['IIIIIIIIIIIIIIIIIIIIIIIIIIX', 'IIIIIIIIIIIIIIIIIIIIIIIIIIY',
'IIIIIIIIIIIIIIIIIIIIIIIIIIZ', 'IIIIIIIIIIIIIIIIIIIIIIIIIXI',
'IIIIIIIIIIIIIIIIIIIIIIIIIXX', 'IIIIIIIIIIIIIIIIIIIIIIIIIXY',
'IIIIIIIIIIIIIIIIIIIIIIIIIXZ', 'IIIIIIIIIIIIIIIIIIIIIIIIIYI',
'IIIIIIIIIIIIIIIIIIIIIIIIIYX', 'IIIIIIIIIIIIIIIIIIIIIIIIIYY',
'IIIIIIIIIIIIIIIIIIIIIIIIIYZ', 'IIIIIIIIIIIIIIIIIIIIIIIIIZI',
'IIIIIIIIIIIIIIIIIIIIIIIIIZX', 'IIIIIIIIIIIIIIIIIIIIIIIIIZY',
'IIIIIIIIIIIIIIIIIIIIIIIIIZZ', 'IIIIIIIIIIIIIIIIIIIIIIIIXII',
'IIIIIIIIIIIIIIIIIIIIIIIIXIX', 'IIIIIIIIIIIIIIIIIIIIIIIIXIY',
'IIIIIIIIIIIIIIIIIIIIIIIIXIZ', 'IIIIIIIIIIIIIIIIIIIIIIIIYII',
'IIIIIIIIIIIIIIIIIIIIIIIIYIX', 'IIIIIIIIIIIIIIIIIIIIIIIIYIY',
'IIIIIIIIIIIIIIIIIIIIIIIIYIZ', 'IIIIIIIIIIIIIIIIIIIIIIIIZII',
'IIIIIIIIIIIIIIIIIIIIIIIIZIX', 'IIIIIIIIIIIIIIIIIIIIIIIIZIY',
'IIIIIIIIIIIIIIIIIIIIIIIIZIZ', 'IIIIIIIIIIIIIIIIIIIIIIIXIII',
'IIIIIIIIIIIIIIIIIIIIIIIYIII', 'IIIIIIIIIIIIIIIIIIIIIIIZIII',
'IIIIIIIIIIIIIIIIIIIIIIXIIII', 'IIIIIIIIIIIIIIIIIIIIIIXXIII',
'IIIIIIIIIIIIIIIIIIIIIIXYIII', 'IIIIIIIIIIIIIIIIIIIIIIXZIII',
'IIIIIIIIIIIIIIIIIIIIIIYIIII', 'IIIIIIIIIIIIIIIIIIIIIIYXIII',
'IIIIIIIIIIIIIIIIIIIIIIYYIII', 'IIIIIIIIIIIIIIIIIIIIIIYZIII',
'IIIIIIIIIIIIIIIIIIIIIIZIIII', 'IIIIIIIIIIIIIIIIIIIIIIZXIII',
'IIIIIIIIIIIIIIIIIIIIIIZYIII', 'IIIIIIIIIIIIIIIIIIIIIIZZIII',
'IIIIIIIIIIIIIIIIIIIIIXIIIII', 'IIIIIIIIIIIIIIIIIIIIIXXIIII',
'IIIIIIIIIIIIIIIIIIIIIXYIIII', 'IIIIIIIIIIIIIIIIIIIIIXZIIII',
'IIIIIIIIIIIIIIIIIIIIIYIIIII', 'IIIIIIIIIIIIIIIIIIIIIYXIIII',
'IIIIIIIIIIIIIIIIIIIIIYYIIII', 'IIIIIIIIIIIIIIIIIIIIIYZIIII',
'IIIIIIIIIIIIIIIIIIIIIZIIIII', 'IIIIIIIIIIIIIIIIIIIIIZXIIII',
'IIIIIIIIIIIIIIIIIIIIIZYIIII', 'IIIIIIIIIIIIIIIIIIIIIZZIIII',
'IIIIIIIIIIIIIIIIIIIIXIIIIII', 'IIIIIIIIIIIIIIIIIIIIXXIIIII',
'IIIIIIIIIIIIIIIIIIIIXYIIIII', 'IIIIIIIIIIIIIIIIIIIIXZIIIII',
'IIIIIIIIIIIIIIIIIIIIYIIIIII', 'IIIIIIIIIIIIIIIIIIIIYXIIIII',
'IIIIIIIIIIIIIIIIIIIIYYIIIII', 'IIIIIIIIIIIIIIIIIIIIYZIIIII',
'IIIIIIIIIIIIIIIIIIIIZIIIIII', 'IIIIIIIIIIIIIIIIIIIIZXIIIII',
'IIIIIIIIIIIIIIIIIIIIZYIIIII', 'IIIIIIIIIIIIIIIIIIIIZZIIIII',
'IIIIIIIIIIIIIIIIIIIXIIIIIII', 'IIIIIIIIIIIIIIIIIIIXXIIIIII',
'IIIIIIIIIIIIIIIIIIIXYIIIIII', 'IIIIIIIIIIIIIIIIIIIXZIIIIII',
'IIIIIIIIIIIIIIIIIIIYIIIIIII', 'IIIIIIIIIIIIIIIIIIIYXIIIIII',
'IIIIIIIIIIIIIIIIIIIYYIIIIII', 'IIIIIIIIIIIIIIIIIIIYZIIIIII', ...]

Along with the error rates:
[5.9e-04 5.3e-04 5.7e-04 ... 0.0e+00 1.0e-05 0.0e+00]

L'attribut LayerError.error du résultat d'apprentissage du bruit contient les générateurs et les taux d'erreur du modèle de Pauli-Lindblad ajusté, qui a la forme

Λ(ρ)=expjrj(PjρPjρ),\Lambda(\rho) = \exp{\sum_j r_j \left(P_j \rho P_j^\dagger - \rho\right)},

où les rjr_j sont les LayerError.rates et les PjP_j sont les opérateurs de Pauli spécifiés dans LayerError.generators.

Options d'apprentissage du bruit

Tu peux choisir parmi plusieurs options à fournir lors de l'instanciation d'un objet NoiseLearner. Ces options sont encapsulées par la classe qiskit_ibm_runtime.options.NoiseLearnerOptions et incluent notamment la possibilité de spécifier le nombre maximum de couches à apprendre, le nombre de randomisations et la stratégie de twirling. Consulte la documentation API NoiseLearnerOptions pour des informations détaillées.

Voici un exemple simple montrant comment utiliser NoiseLearnerOptions dans une expérience NoiseLearner :

# Build a GHZ circuit
circuit = QuantumCircuit(10)
circuit.h(0)
circuit.cx(range(0, 9), range(1, 10))
# Choose a backend to run on
service = QiskitRuntimeService()
backend = service.least_busy()

# Transpile the circuit for execution
pm = generate_preset_pass_manager(backend=backend, optimization_level=3)
circuit_to_run = pm.run(circuit_to_learn)

# Instantiate a NoiseLearnerOptions object
learner_options = NoiseLearnerOptions(
max_layers_to_learn=3, num_randomizations=32, twirling_strategy="all"
)

# Instantiate a NoiseLearner object and execute the noise learning program
learner = NoiseLearner(mode=backend, options=learner_options)
job = learner.run([circuit_to_run])
noise_model = job.result()

Passer un modèle de bruit à une primitive

Le modèle de bruit appris sur le circuit peut également être utilisé comme entrée pour la primitive EstimatorV2 implémentée dans Qiskit Runtime. Il peut être passé à la primitive de plusieurs façons différentes. Les trois exemples suivants montrent comment passer le modèle de bruit directement à l'attribut estimator.options, en utilisant un objet ResilienceOptionsV2 avant d'instancier une primitive Estimator, et en passant un dictionnaire au format approprié.

# Pass the noise model to the `estimator.options` attribute directly
estimator = EstimatorV2(mode=backend)
estimator.options.resilience.layer_noise_model = noise_model
# Specify options through a ResilienceOptionsV2 object
resilience_options = ResilienceOptionsV2(layer_noise_model=noise_model)
estimator_options = EstimatorOptions(resilience=resilience_options)
estimator = EstimatorV2(mode=backend, options=estimator_options)
# Specify options by using a dictionary
options_dict = {
"resilience_level": 2,
"resilience": {"layer_noise_model": noise_model},
}

estimator = EstimatorV2(mode=backend, options=options_dict)

Après avoir passé le modèle de bruit à l'objet EstimatorV2, il peut être utilisé pour exécuter des charges de travail et effectuer l'atténuation d'erreurs normalement.

NoiseLearnerV3

Vue d'ensemble

Similaire à NoiseLearner, la classe NoiseLearnerV3 effectue des expériences qui caractérisent les processus de bruit en se basant sur un modèle de bruit de Pauli-Lindblad pour un ou plusieurs circuits. Sa méthode run() prend une liste d'instructions, dont chacune doit être un BoxOp annoté par twirling contenant des opérations ISA.

Le résultat d'un job NoiseLearnerV3 contient une liste d'objets NoiseLearnerV3Result, un pour chaque instruction en entrée. Le code suivant montre comment utiliser cet assistant.

from qiskit import QuantumCircuit
from qiskit.transpiler import CouplingMap
from qiskit.transpiler import generate_preset_pass_manager

from qiskit_ibm_runtime import QiskitRuntimeService, Executor
from qiskit_ibm_runtime.noise_learner_v3 import NoiseLearnerV3
from samplomatic.transpiler import generate_boxing_pass_manager
from samplomatic.utils import find_unique_box_instructions

# Build a circuit with two entangling layers
num_qubits = 27
edges = list(CouplingMap.from_line(num_qubits, bidirectional=False))
even_edges = edges[::2]
odd_edges = edges[1::2]

circuit = QuantumCircuit(num_qubits)
for pair in even_edges:
circuit.cx(pair[0], pair[1])
for pair in odd_edges:
circuit.cx(pair[0], pair[1])

# Choose a backend to run on
service = QiskitRuntimeService()
backend = service.least_busy()

# Transpile the circuit for execution
pm = generate_preset_pass_manager(backend=backend, optimization_level=3)
isa_circuit = pm.run(circuit)

# Run the boxing pass manager to group instructions into annotated boxes
boxing_pm = generate_boxing_pass_manager(
enable_gates=True,
enable_measures=False,
inject_noise_targets="gates", # no measurement mitigation
inject_noise_strategy="uniform_modification",
)
boxed_circuit = boxing_pm.run(isa_circuit)

# Find unique boxed instructions
unique_box_instructions = find_unique_box_instructions(boxed_circuit.data)
print(f"Found {len(unique_box_instructions)} unique layers")
print(
f"Each instruction is of type {type(unique_box_instructions[0].operation)}"
)
print(
f"And has annotations: {unique_box_instructions[0].operation.annotations}"
)

# Instantiate a NoiseLearnerV3 object and execute the noise learning program
learner = NoiseLearnerV3(backend)
learner.options.shots_per_randomization = 128
learner.options.num_randomizations = 32
learner_job = learner.run(unique_box_instructions)
learner_result = learner_job.result()
Found 3 unique layers
Each instruction is of type <class 'qiskit.circuit.controlflow.box.BoxOp'>
And has annotations: [Twirl(group='pauli', dressing='left', decomposition='rzsx'), InjectNoise(ref='r789B', modifier_ref='', site='before')]

Le résultat du job est une liste d'objets NoiseLearnerV3Result, un pour chaque ensemble d'instructions en boîte en entrée. NoiseLearnerV3Result possède une méthode to_pauli_lindblad_map() qui retourne un objet PauliLindbladMap, qui dispose de méthodes pour extraire les générateurs, les taux d'erreur, et plus encore.

print(
f"The Noise learner V3 result contains {len(learner_result)} entries"
f" and each has the following type:\n {type(learner_result[0])}\n"
)
noise_map = learner_result[0].to_pauli_lindblad_map()
print(
f"After converting to PauliLindbladMap, you can extract data "
f" on the generators for the error channel (truncated to 3): \n{noise_map.generators()[:3]}\n"
)
with numpy.printoptions(threshold=20):
print(
f"Along with the error rates (truncated to 3): \n{noise_map.rates[:3]}\n"
)
The Noise learner V3 result contains 3 entries and each has the following type:
<class 'qiskit_ibm_runtime.results.noise_learner_v3.NoiseLearnerV3Result'>

After converting to PauliLindbladMap, you can extract data on the generators for the error channel (truncated to 3):
<QubitSparsePauliList with 3 elements on 27 qubits: [X_0, Y_0, Z_0]>

Along with the error rates (truncated to 3):
[0.00026 0.00032 0.00023]

Options d'apprentissage du bruit

NoiseLearnerV3 prend en charge plusieurs options, notamment le nombre de randomisations et la profondeur de paires de couches, entre autres. Similaire aux primitives, tu peux spécifier les options lors de l'instanciation ou après avoir instancié l'objet NoiseLearnerV3. L'exemple de code précédent a illustré comment définir les options shots_per_randomization et num_randomizations. Consulte la documentation API NoiseLearnerV3Options pour des informations détaillées.

Passer un modèle de bruit à Executor

Executor suit les intentions de conception spécifiées dans les annotations de circuit (sous forme de samplex) et les options. InjectNoise est l'annotation pour spécifier où injecter du bruit, et l'argument samplex pauli_lindblad_maps spécifie quelle carte de bruit utiliser.

Le circuit de l'exemple précédent est traité par le gestionnaire de passes boxing, qui regroupe les instructions dans des boîtes annotées. Le code pertinent est repris ici pour faciliter la compréhension.

  • inject_noise_targets="gates" spécifie d'ajouter les annotations InjectNoise aux boîtes contenant des portes d'intrication.
  • inject_noise_strategy="uniform_modification" spécifie d'attribuer le même ref et modifier_ref à toutes les boîtes équivalentes avec des annotations InjectNoise.
    • InjectNoise.ref est un identifiant unique utilisé pour attribuer un modèle de bruit à cette boîte.
    • InjectNoise.modifier_ref permet de mettre à l'échelle le modèle de bruit attribué à une boîte par des facteurs multiplicatifs.
boxing_pm = generate_boxing_pass_manager(
enable_gates=True,
enable_measures=False,
inject_noise_targets="gates", # no measurement mitigation
inject_noise_strategy="uniform_modification",
)

Le circuit de l'exemple précédent contient trois boîtes, dont deux contiennent des annotations InjectNoise avec des attributs ref différents (car elles ne sont pas équivalentes).

# box_circuit comes from the example above
for idx, instruction in enumerate(boxed_circuit):
# The `InjectNoise` annotation defines which boxes to inject noise.
print(f"Annotations of box #{idx}: {instruction.operation.annotations}\n")
Annotations of box #0: [Twirl(group='pauli', dressing='left', decomposition='rzsx'), InjectNoise(ref='r789B', modifier_ref='r789B', site='before')]

Annotations of box #1: [Twirl(group='pauli', dressing='left', decomposition='rzsx'), InjectNoise(ref='r054B', modifier_ref='r054B', site='before')]

Annotations of box #2: [Twirl(group='pauli', dressing='right', decomposition='rzsx')]

Le résultat du job NoiseLearnerV3 doit être converti en dictionnaire avant d'être passé à Executor. Les clés de ce dictionnaire sont les attributs InjectNoise.ref et les valeurs sont les cartes de bruit correspondantes. Ce mappage indique à Executor quels modèles de bruit injecter et où.

Le code suivant montre comment prendre le circuit et le résultat NoiseLearnerV3 de l'exemple précédent et les passer à Executor, qui va générer les variantes de circuit avec les modèles de bruit injectés et les exécuter sur le matériel.

from qiskit_ibm_runtime.quantum_program import QuantumProgram
from samplomatic import build

# Generate a quantum program
program = QuantumProgram(shots=1000)

# Build the template circuit and samplex pair
template_circuit, samplex = build(boxed_circuit)

# Convert the NoiseLearnerV3 result to a dictionary
noise_maps = learner_result.to_dict(
instructions=unique_box_instructions, require_refs=False
)

# Append the samplex item and execute
program.append_samplex_item(
template_circuit,
samplex=samplex,
samplex_arguments={
"pauli_lindblad_maps": noise_maps,
},
)

executor = Executor(backend)
executor_job = executor.run(program)

Prochaines étapes

Recommandations