Pular para o conteúdo principal

Auxiliar de aprendizado de ruído

Versões dos pacotes

O código desta página foi desenvolvido usando os seguintes requisitos. Recomendamos usar estas versões ou versões mais recentes.

qiskit[all]~=2.4.1
qiskit-ibm-runtime~=0.47.0
samplomatic~=0.18.0

As técnicas de mitigação de erros PEA e PEC utilizam um componente de aprendizado de ruído baseado em um modelo de ruído de Pauli-Lindblad, que normalmente é gerenciado durante a execução após o envio de um ou mais jobs pelo qiskit-ibm-runtime, sem acesso local ao modelo de ruído ajustado. No entanto, a partir da versão v0.27.1 do qiskit-ibm-runtime, foram criadas as classes NoiseLearner e NoiseLearnerOptions para obter os resultados desses experimentos de aprendizado de ruído. Esses resultados podem ser armazenados localmente como um NoiseLearnerResult e usados como entrada em experimentos posteriores. Esta página fornece uma visão geral do uso e das opções disponíveis.

Além disso, a partir do qiskit-ibm-runtime v0.47.0, há uma nova classe NoiseLearnerV3 compatível com a primitiva Executor. Essa nova versão, também parte do modelo de execução direcionada, permite especificar explicitamente as camadas que você deseja aprender.

nota

NoiseLearner funciona apenas com EstimatorV2 e NoiseLearnerV3 funciona apenas com Executor.

NoiseLearner

Visão geral

A classe NoiseLearner realiza experimentos que caracterizam processos de ruído com base em um modelo de ruído de Pauli-Lindblad para um (ou mais) circuits. Ela possui um método run() que executa os experimentos de aprendizado e recebe como entrada uma lista de circuits ou um PUB, retornando um NoiseLearnerResult contendo os canais de ruído aprendidos e metadados sobre os jobs enviados. Abaixo está um trecho de código demonstrando o uso do programa auxiliar.

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

O NoiseLearnerResult.data resultante é uma lista de objetos LayerError contendo o modelo de ruído para cada camada de entrelaçamento individual que pertence ao(s) circuit(s) alvo. Cada LayerError armazena as informações da camada, na forma de um circuit e um conjunto de rótulos de Qubits, juntamente com o PauliLindbladError para o modelo de ruído aprendido para a camada em questão.

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"
f"{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]

O atributo LayerError.error do resultado do aprendizado de ruído contém os geradores e as taxas de erro do modelo de Pauli Lindblad ajustado, que tem a forma

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

onde os rjr_j são os LayerError.rates e PjP_j são os operadores de Pauli especificados em LayerError.generators.

Opções de aprendizado de ruído

Você pode escolher entre várias opções ao instanciar um objeto NoiseLearner. Essas opções são encapsuladas pela classe qiskit_ibm_runtime.options.NoiseLearnerOptions e incluem a possibilidade de especificar o número máximo de camadas a aprender, o número de randomizações e a estratégia de twirling, entre outras. Consulte a documentação da API de NoiseLearnerOptions para informações detalhadas.

A seguir está um exemplo simples que mostra como usar o NoiseLearnerOptions em um experimento com 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()

Inserir modelo de ruído em uma primitiva

O modelo de ruído aprendido para o circuit também pode ser usado como entrada para a primitiva EstimatorV2 implementada no Qiskit Runtime. Ele pode ser passado para a primitiva de algumas formas diferentes. Os três próximos exemplos mostram como você pode passar o modelo de ruído para o atributo estimator.options diretamente, usando um objeto ResilienceOptionsV2 antes de instanciar uma primitiva Estimator, e passando um dicionário formatado adequadamente.

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

Depois que o modelo de ruído for passado para o objeto EstimatorV2, ele pode ser usado para executar cargas de trabalho e realizar a mitigação de erros normalmente.

NoiseLearnerV3

Visão geral

Semelhante ao NoiseLearner, a classe NoiseLearnerV3 realiza experimentos que caracterizam processos de ruído com base em um modelo de ruído de Pauli-Lindblad para um ou mais circuits. Seu método run() recebe uma lista de instruções, cada uma das quais deve ser uma BoxOp anotada com twirl contendo operações ISA.

O resultado de um job NoiseLearnerV3 contém uma lista de objetos NoiseLearnerV3Result, um para cada instrução de entrada. O código a seguir mostra como usar o programa auxiliar.

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')]

O resultado do job é uma lista de objetos NoiseLearnerV3Result, um para cada conjunto de instruções encapsuladas de entrada. NoiseLearnerV3Result possui um método to_pauli_lindblad_map() que retorna um objeto PauliLindbladMap, que tem métodos para extrair os geradores, taxas de erro e mais.

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 "
f"(truncated to 3): \n{noise_map.generators()[:3]}\n"
)
with numpy.printoptions(threshold=20):
print(
f"Along with the error rates "
f"(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]

Opções de aprendizado de ruído

NoiseLearnerV3 suporta várias opções, incluindo o número de randomizações e a profundidade dos pares de camadas, entre outras. De forma semelhante às primitivas, você pode especificar as opções durante ou após a instanciação do objeto NoiseLearnerV3. O exemplo de código anterior demonstrou como definir as opções shots_per_randomization e num_randomizations. Consulte a documentação da API de NoiseLearnerV3Options para informações detalhadas.

Inserir um modelo de ruído no Executor

O Executor segue as intenções de design especificadas nas anotações de circuito (na forma de um samplex) e nas opções. InjectNoise é a anotação para especificar onde injetar ruído, e o argumento samplex pauli_lindblad_maps especifica qual mapa de ruído usar.

O circuito no exemplo anterior passa pelo boxing pass manager, que agrupa instruções em caixas anotadas. O código relevante é adicionado aqui para facilitar a compreensão.

  • inject_noise_targets="gates" especifica que as anotações InjectNoise devem ser adicionadas às caixas que contêm portas de entrelaçamento.
  • inject_noise_strategy="uniform_modification" especifica que o mesmo ref e modifier_ref devem ser atribuídos a todas as caixas equivalentes com anotações InjectNoise.
    • InjectNoise.ref é um identificador único usado para atribuir um modelo de ruído a essa caixa.
    • InjectNoise.modifier_ref permite escalar o modelo de ruído atribuído a uma caixa por fatores multiplicativos.
boxing_pm = generate_boxing_pass_manager(
enable_gates=True,
enable_measures=False,
inject_noise_targets="gates", # no measurement mitigation
inject_noise_strategy="uniform_modification",
)

O circuito do exemplo anterior contém três caixas, duas das quais contêm anotações InjectNoise com atributos ref diferentes (pois não são equivalentes).

# 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')]

O resultado do job NoiseLearnerV3 deve ser convertido em um dicionário antes de ser passado para o Executor. As chaves desse dicionário são os atributos InjectNoise.ref e os valores são os mapas de ruído correspondentes. Esse mapeamento informa ao Executor quais modelos de ruído injetar e onde.

O código a seguir mostra como pegar o circuito e o resultado de NoiseLearnerV3 do exemplo anterior e passá-los para o Executor, que irá gerar as variantes do circuito com os modelos de ruído injetados e executá-las no hardware.

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)

Próximos passos

Recomendações