Aller au contenu principal

Migrer de NoiseLearner vers NoiseLearnerV3

Ce guide t'accompagne dans la migration de NoiseLearner vers NoiseLearnerV3 d'IBM Quantum®. Les deux classes effectuent des expériences qui caractérisent les processus de bruit sur la base d'un modèle de bruit de Pauli-Lindblad, mais les entrées et les sorties sont légèrement différentes.

Contexte​

La classe NoiseLearner a été créée pour permettre aux utilisateurs d'effectuer un apprentissage explicite du bruit. Le modèle de bruit résultant peut ensuite être passé à Estimator d'IBM Quantum pour appliquer des techniques de mitigation d'erreurs telles que PEA et PEC.

NoiseLearner a été conçu pour fonctionner avec Estimator, et il utilise donc implicitement la même stratégie de recherche de couches qu'Estimator. Cette stratégie ne peut pas être modifiée ; sinon, les étapes de mitigation suivantes ne fonctionneraient pas correctement.

À partir de qiskit-ibm-runtime v0.47.0, il existe une nouvelle classe NoiseLearnerV3 compatible avec la primitive Executor et le modèle d'exécution dirigée. Ce nouveau modèle offre une expérience en boîte blanche en fournissant les éléments permettant de capturer l'intention de conception côté client, et une seule primitive côté serveur (Executor) traite ces entrées exactement comme indiqué — elle ne prend aucune décision implicite en ton nom. Contrairement à l'original NoiseLearner, tu contrôles la façon de stratifier tes circuits lorsque tu utilisesNoiseLearnerV3, et la classe prend simplement en entrée une liste d'instructions de circuit encadrées (par exemple, des couches uniques).

NoiseLearnerV3 prend également en charge l'apprentissage du bruit de mesure. Pour chaque instruction de la liste d'entrée, il exécute le protocole d'apprentissage de Pauli-Lindblad si l'encadré contient des portes à un et deux qubits, et le protocole TREX si l'encadré contient des mesures.

Dois-tu migrer ?​

NoiseLearner fonctionne uniquement avec l'ancien Estimator côté serveur, et NoiseLearnerV3 fonctionne uniquement avec Executor et l'Estimator côté client. Tu dois migrer vers NoiseLearnerV3 si tu utilises Executor ou l'Estimator côté client. L'ancien Estimator côté serveur est obsolète et remplacé par l'équivalent côté client dans qiskit-ibm-runtime v0.50.0.

remarque

Si tu utilises qiskit-ibm-runtime v0.50.0 ou une version ultérieure, lis d'abord le guide Migrer de Sampler et Estimator côté serveur vers côté client pour migrer vers les primitives côté client.

Étapes de migration​

Étape 1 : Changer les imports​

NoiseLearner:

from qiskit_ibm_runtime.noise_learner import NoiseLearner

NoiseLearnerV3:

from qiskit_ibm_runtime import NoiseLearnerV3

Étape 2 : Mettre à jour les entrées​

La méthode run() de NoiseLearner prend une liste de circuits ou de PUB, tandis que la méthode run() de NoiseLearnerV3 prend une liste d'instructions, chacune devant être un BoxOp annoté pour le twirling contenant des opérations ISA. Des méthodes de commodité sont disponibles pour générer les encadrés annotés, selon la primitive que tu prévois d'utiliser.

NoiseLearner:

from qiskit_ibm_runtime.noise_learner import NoiseLearner

learner = NoiseLearner(mode=backend)
# `circuits_to_learn` is a list of ISA QuantumCircuit
learner_job = learner.run(circuits_to_learn)

NoiseLearnerV3, en travaillant avec Estimator côté client :

Si tu prévois d'utiliser Estimator côté client pour l'exécution des circuits, tu peux utiliser la méthode find_unique_layers d'Estimator pour créer des encadrés annotés (couches) :

from qiskit_ibm_runtime.executor_estimator import Estimator
from qiskit_ibm_runtime import NoiseLearnerV3

pubs = [...] # Your PUBs
estimator = Estimator(backend)
estimator.options.resilience.pec_mitigation = True # or zne_mitigation + pea amplifier

# Identify the unique layers to learn.
layers = estimator.find_unique_layers(pubs)

# Learn the noise model for those layers (runs as a separate job).
learner = NoiseLearnerV3(backend)
learner_job = learner.run(layers)

NoiseLearnerV3, en travaillant avec Executor :

Si tu prévois d'utiliser Executor pour l'exécution des circuits, envisage d'utiliser la fonction generate_boxing_pass_manager de Samplomatic pour créer des encadrés annotés :

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

# Run the boxing pass manager to group instructions into annotated boxes.
# `isa_circuit` is an ISA QuantumCircuit.
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)

# Instantiate a NoiseLearnerV3 object and execute the noise learning program.
learner = NoiseLearnerV3(backend)
learner_job = learner.run(unique_box_instructions)

Étape 3 : Convertir les options​

La plupart des champs de NoiseLearnerOptions sont directement mappés vers NoiseLearnerV3Options, à l'exception des suivants :

  • max_layers_to_learn : Avec NoiseLearnerV3, le nombre de couches à apprendre est basé sur le nombre de couches passées en entrée.

  • twirling_strategy : Avec NoiseLearnerV3, la stratégie de twirling est définie par la façon dont les instructions sont encadrées et annotées (par exemple lors de l'utilisation de generate_boxing_pass_manager()).

NoiseLearner:

from qiskit_ibm_runtime.noise_learner import NoiseLearner
from qiskit_ibm_runtime.options import NoiseLearnerOptions

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

learner = NoiseLearner(mode=backend, options=learner_options)
learner_job = learner.run(circuits_to_learn)

NoiseLearnerV3, en travaillant avec Estimator côté client :

Si tu prévois d'utiliser Estimator côté client pour l'exécution des circuits, tu peux définir l'option twirling.strategy d'Estimator :

from qiskit_ibm_runtime.executor_estimator import Estimator
from qiskit_ibm_runtime import NoiseLearnerV3
from qiskit_ibm_runtime.options_models import NoiseLearnerV3Options

pubs = [...] # Your PUBs
estimator = Estimator(backend)
estimator.options.resilience.pec_mitigation = True # or zne_mitigation + pea amplifier
estimator.options.twirling.strategy = "all" # set twirling strategy here

# Identify the unique layers to learn.
layers = estimator.find_unique_layers(pubs)

# Instantiate a NoiseLearnerV3 object and execute the noise learning program
learner_options = NoiseLearnerV3Options(num_randomizations=32)
learner = NoiseLearnerV3(backend, options=learner_options)

# Learn just the first 3 layers.
learner_job = learner.run(layers[:3])

NoiseLearnerV3, en travaillant avec Executor :

Si tu prévois d'utiliser Executor pour l'exécution des circuits, tu peux passer l'option twirling_strategy à la fonction generate_boxing_pass_manager.

Note qu'avec generate_boxing_pass_manager(), les valeurs de twirling_strategy utilisent des underscores ("active_accum", "active_circuit"), tandis que les valeurs de NoiseLearnerOptions.twirling_strategy utilisent des tirets ("active-accum", "active-circuit").

from qiskit_ibm_runtime.noise_learner_v3 import NoiseLearnerV3
from qiskit_ibm_runtime.options_models import NoiseLearnerV3Options
from samplomatic.transpiler import generate_boxing_pass_manager
from samplomatic.utils import find_unique_box_instructions

# Run the boxing pass manager to group instructions into annotated boxes
# `isa_circuit` is an ISA QuantumCircuit
boxing_pm = generate_boxing_pass_manager(
enable_gates=True,
enable_measures=False,
twirling_strategy="all", # twirling strategy can be specified here
inject_noise_targets="gates",
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)

learner_options = NoiseLearnerV3Options(num_randomizations=32)

# Instantiate a NoiseLearnerV3 object and execute the noise learning program
learner = NoiseLearnerV3(backend, options=learner_options)
# Learn just the first 3 layers.
learner_job = learner.run(unique_box_instructions[:3])

Étape 4 : Inspecter les résultats​

Les sorties de NoiseLearner et NoiseLearnerV3 contiennent des informations similaires mais dans des formats différents. Met à jour ton code s'il inspecte explicitement la sortie.

Mappage des attributs de résultat :

(learner_result est la sortie du job de learner)

AttributNoiseLearnerNoiseLearnerV3
Type de résultatNoiseLearnerResultNoiseLearnerV3Results, un conteneur de type séquence de NoiseLearnerV3Result
Nombre de couches appriseslen(learner_result.data)len(learner_result)
Données pour la première couchelayer_error = learner_result.data[0]noise_map = learner_result[0].to_pauli_lindblad_map()
Type de résultat de chaque coucheLayerError (type(layer_error))PauliLindbladMap (type(noise_map))
Générateurs pour le canal d'erreurlayer_error.error.generatorsnoise_map.generators()
Taux d'erreurlayer_error.error.ratesnoise_map.rates

Étape 5 : Fournir le modèle de bruit à une primitive​

NoiseLearner fonctionne uniquement avec l'ancien Estimator côté serveur, et NoiseLearnerV3 fonctionne uniquement avec Executor et l'Estimator côté client. La façon dont un modèle de bruit est spécifié varie légèrement selon la primitive utilisée.

NoiseLearner, en travaillant avec l'ancien Estimator côté serveur :

from qiskit_ibm_runtime import Estimator as LegacyEstimator

learner_result = learner_job.result()

# Pass the noise model to the `estimator.options` attribute directly
estimator = LegacyEstimator(mode=backend)
estimator.options.resilience.layer_noise_model = learner_result
job = estimator.run(pubs)

NoiseLearnerV3, en travaillant avec Estimator côté client :

Réutilise le même Estimator qui a produit layers à l'étape 2. Les cartes de bruit renvoyées par le learner sont associées positionnellement à ces couches, elles doivent donc être assignées à l'Estimator dont elles proviennent. PEA/PEC était déjà activé sur celui-ci à l'étape 2.

Note que bien que NoiseLearnerV3 prenne en charge les protocoles Pauli-Lindblad et TREX, Estimator n'accepte que les modèles de bruit pour les couches à deux qubits appris avec le protocole Pauli-Lindblad.

learner_result = learner_job.result()

# Convert results to Pauli-Lindblad noise maps.
pauli_lindblad_maps = learner_result.to_pauli_lindblad_maps()

# Assign the learned noise maps so PEA/PEC uses them.
estimator.options.resilience.layer_noise_model = zip(layers, pauli_lindblad_maps)

# Now execute the target PUBs.
job = estimator.run(pubs)

NoiseLearnerV3, en travaillant avec Executor :

from qiskit_ibm_runtime import Executor
from qiskit_ibm_runtime.quantum_program import QuantumProgram

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

# Convert the NoiseLearnerV3 result to a dictionary
learner_result = learner_job.result()
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)

Exemples complets​

NoiseLearnerV3 et Estimator côté client​

from qiskit import QuantumCircuit
from qiskit.quantum_info import SparsePauliOp
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager

from qiskit_ibm_runtime import QiskitRuntimeService, NoiseLearnerV3
from qiskit_ibm_runtime.executor_estimator import Estimator

# 1. Account + backend
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False)

# 2. Circuit + observable
circuit = QuantumCircuit(2)
circuit.h(0)
circuit.cx(0, 1)

observable = SparsePauliOp("ZZ")

# 3. Transpile to ISA
pm = generate_preset_pass_manager(backend=backend, optimization_level=1)
isa_circuit = pm.run(circuit)
isa_observable = observable.apply_layout(isa_circuit.layout)
pubs = [(isa_circuit, isa_observable)]

# 4. Initialize Estimator with options
estimator = Estimator(backend)
estimator.options.resilience.pec_mitigation = True

# 5. Extract the unique boxed layers from PUBs
layers = estimator.find_unique_layers(pubs)

# 6. Learn the noise model for those layers
learner = NoiseLearnerV3(backend)
learner_job = learner.run(layers)
learner_result = learner_job.result()

# 7. Convert the result to Pauli-Lindblad maps and pass them to Estimator
pauli_lindblad_maps = learner_result.to_pauli_lindblad_maps()
estimator.options.resilience.layer_noise_model = zip(layers, pauli_lindblad_maps)

# 8. Execute the target PUBs
job = estimator.run(pubs)
result = job.result()

NoiseLearnerV3 et Executor​

from qiskit import QuantumCircuit
from qiskit.transpiler import generate_preset_pass_manager

from qiskit_ibm_runtime import QiskitRuntimeService, Executor, NoiseLearnerV3
from qiskit_ibm_runtime.quantum_program import QuantumProgram

from samplomatic import build
from samplomatic.transpiler import generate_boxing_pass_manager
from samplomatic.utils import find_unique_box_instructions

# 1. Account + backend
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False)

# 2. Circuit + observable
circuit = QuantumCircuit(2)
circuit.h(0)
circuit.cx(0, 1)
circuit.measure_all()

# 3. Transpile to ISA
pm = generate_preset_pass_manager(backend=backend, optimization_level=1)
isa_circuit = pm.run(circuit)

# 4. 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)

# 5. Find unique boxed instructions (layers)
unique_box_instructions = find_unique_box_instructions(boxed_circuit.data)

# 6. Learn the noise model for those layers
learner = NoiseLearnerV3(backend)
learner_job = learner.run(unique_box_instructions)
learner_result = learner_job.result()

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

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

# 9. Prepare a quantum program
program = QuantumProgram(shots=1000)
program.append_samplex_item(
template_circuit,
samplex=samplex,
samplex_arguments={
"pauli_lindblad_maps": noise_maps,
},
)

executor = Executor(backend)
job = executor.run(program)
result = job.result()

Références​