Pular para o conteúdo principal

Migrar de NoiseLearner para NoiseLearnerV3

Este guia orienta você na migração do NoiseLearner do IBM Quantum® para o NoiseLearnerV3. Ambas as classes realizam experimentos que caracterizam processos de ruído com base em um modelo de ruído de Pauli-Lindblad, mas as entradas e saídas são ligeiramente diferentes.

Contexto​

A classe NoiseLearner foi criada para permitir que os usuários realizem aprendizado explícito de ruído. O modelo de ruído resultante pode então ser passado ao Estimator do IBM Quantum para aplicar técnicas de mitigação de erros, como PEA e PEC.

NoiseLearner foi projetado para funcionar com Estimator e, portanto, emprega implicitamente a mesma estratégia de localização de camadas que o Estimator. Essa estratégia não pode ser alterada; caso contrário, as etapas de mitigação subsequentes não funcionariam corretamente.

A partir do qiskit-ibm-runtime v0.47.0, há uma nova classe NoiseLearnerV3 que é compatível com a primitiva Executor e o modelo de execução direcionada. Esse novo modelo oferece uma experiência de caixa-branca ao fornecer as peças para capturar a intenção de design no lado do cliente, e uma única primitiva do lado do servidor (Executor) processa essas entradas exatamente como direcionado — ela não toma decisões implícitas em seu nome. Diferente do NoiseLearner original, você controla como estratificar seus circuitos ao usar oNoiseLearnerV3, e a classe simplesmente recebe como entrada uma lista de instruções de circuito em caixas (por exemplo, camadas únicas).

NoiseLearnerV3 também suporta aprendizado de ruído de medição. Para cada instrução na lista de entrada, ele executa o protocolo de aprendizado de Pauli-Lindblad se a caixa contiver gates de um e dois qubits, e o protocolo TREX se a caixa contiver medições.

Você deve migrar?​

NoiseLearner só funciona com o Estimator legado do lado do servidor, e NoiseLearnerV3 só funciona com Executor e o Estimator do lado do cliente. Você deve migrar para NoiseLearnerV3 se estiver usando Executor ou o Estimator do lado do cliente. O Estimator legado do lado do servidor está obsoleto e é substituído pelo equivalente do lado do cliente no qiskit-ibm-runtime v0.50.0.

nota

Se você estiver usando qiskit-ibm-runtime v0.50.0 ou posterior, leia o guia Migrar do Sampler e Estimator do lado do servidor para o lado do cliente para migrar primeiro para as primitivas do lado do cliente.

Etapas de migração​

Etapa 1: alterar as importações​

NoiseLearner:

from qiskit_ibm_runtime.noise_learner import NoiseLearner

NoiseLearnerV3:

from qiskit_ibm_runtime import NoiseLearnerV3

Etapa 2: atualizar as entradas​

O método run() do NoiseLearner recebe uma lista de circuitos ou PUBs, enquanto o método run() do NoiseLearnerV3 recebe uma lista de instruções, cada uma das quais deve ser um BoxOp anotado com twirling contendo operações ISA. Métodos de conveniência estão disponíveis para gerar as caixas anotadas, dependendo de qual primitiva você planeja usar.

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, ao trabalhar com o Estimator do lado do cliente:

Se você planeja usar o Estimator do lado do cliente para execução de circuitos, pode usar o método find_unique_layers do Estimator para criar caixas anotadas (camadas):

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, ao trabalhar com o Executor:

Se você planeja usar o Executor para execução de circuitos, considere usar a função generate_boxing_pass_manager do Samplomatic para criar caixas anotadas:

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)

Etapa 3: converter as opções​

A maioria dos campos de NoiseLearnerOptions mapeiam diretamente para NoiseLearnerV3Options, exceto os seguintes:

  • max_layers_to_learn: com NoiseLearnerV3, o número de camadas a aprender é baseado no número de camadas passadas.

  • twirling_strategy: com NoiseLearnerV3, a estratégia de twirling é definida por como as instruções são colocadas em caixas e anotadas (como ao usar 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, ao trabalhar com o Estimator do lado do cliente:

Se você planeja usar o Estimator do lado do cliente para execução de circuitos, você pode definir a opção twirling.strategy do 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, ao trabalhar com o Executor:

Se você planeja usar o Executor para execução de circuitos, pode passar a opção twirling_strategy para a função generate_boxing_pass_manager.

Observe que, com generate_boxing_pass_manager(), os valores de twirling_strategy usam sublinhados ("active_accum", "active_circuit"), enquanto os valores de NoiseLearnerOptions.twirling_strategy usam hífens ("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])

Etapa 4: inspecionar os resultados​

As saídas de NoiseLearner e NoiseLearnerV3 contêm informações semelhantes, mas em formatos diferentes. Atualize seu código se ele inspecionar a saída explicitamente.

Mapeamento de atributos de resultado:

(learner_result é a saída do job de aprendizado)

AtributoNoiseLearnerNoiseLearnerV3
Tipo de resultadoNoiseLearnerResultNoiseLearnerV3Results, um contêiner do tipo sequência de NoiseLearnerV3Result
Número de camadas aprendidaslen(learner_result.data)len(learner_result)
Dados da primeira camadalayer_error = learner_result.data[0]noise_map = learner_result[0].to_pauli_lindblad_map()
Tipo de resultado de cada camadaLayerError (type(layer_error))PauliLindbladMap (type(noise_map))
Geradores para o canal de errolayer_error.error.generatorsnoise_map.generators()
Taxas de errolayer_error.error.ratesnoise_map.rates

Etapa 5: fornecer o modelo de ruído a uma primitiva​

NoiseLearner só funciona com o Estimator legado do lado do servidor, e NoiseLearnerV3 só funciona com Executor e o Estimator do lado do cliente. A forma como um modelo de ruído é especificado varia ligeiramente dependendo da primitiva usada.

NoiseLearner, ao trabalhar com o Estimator legado do lado do servidor:

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, ao trabalhar com o Estimator do lado do cliente:

Reutilize o mesmo Estimator que produziu layers na etapa 2. Os mapas de ruído retornados pelo aprendiz são correspondidos posicionalmente a essas camadas, então devem ser atribuídos ao Estimator do qual vieram. PEA/PEC já foi habilitado nele na etapa 2.

Observe que, embora NoiseLearnerV3 suporte os protocolos Pauli-Lindblad e TREX, Estimator só aceita modelos de ruído para camadas de dois qubits aprendidos com o protocolo 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, ao trabalhar com o 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)

Exemplos completos​

NoiseLearnerV3 e Estimator do lado do cliente​

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 e 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()

Referências​