Skip to main content
IBM Quantum Platform

Cancelamento probabilístico de erros com cones de luz sombreados

Estimativa de tempo de execução: 10 minutos em um processador Heron (NOTA: Trata-se apenas de uma estimativa. (O tempo de execução pode variar.)


Resultados do aprendizado

Ao concluir este tutorial, os usuários deverão compreender:

  • O que é o cancelamento probabilístico de erros (PEC) e por que sua sobrecarga de amostragem γ2\gamma^2 cresce exponencialmente com o ruído total que atua no circuito
  • Como os cones de luz sombreados (SLC) limitam a contribuição de cada termo de ruído para o observável alvo, de modo que você possa alocar o orçamento de mitigação onde for mais importante — e como, além da redução da sobrecarga de viés zero já proporcionada por um cone de luz puramente geométrico, o sombreamento permite trocar um viés residual limitado por uma sobrecarga de amostragem ainda menor
  • Como aprender o ruído da camada com NoiseLearnerV3 e injetar antirruído por meio de samplomatic e da Executor primitiva
  • Como combinar PEC e PEC+SLC com TREX e pós-seleção para estimar um valor esperado em hardware

Pré-requisitos

Sugerimos que os usuários estejam familiarizados com os seguintes tópicos antes de seguir com este tutorial:


Segundo plano

Este tutorial mostra como mitigar erros usando o complemento Shaded Light Cone (SLC). Este complemento é uma evolução da técnica de cancelamento probabilístico de erros (PEC), na qual o usuário identifica o ruído de camadas específicas em um circuito e, em seguida, cancela esse ruído por meio da aplicação de portas de qubit único e técnicas de pós-processamento. Em comparação com outros métodos, o PEC oferece limites mais robustos para o viés do resultado mitigado, mas tende a apresentar uma sobrecarga maior em termos de tempo de processamento da QPU. Durante o PEC, para compensar a atenuação do valor esperado causada pelo ruído, o resultado médio é redimensionado por um fator de γ=exp(l,σ2λl,σ)\gamma = \exp(\sum_{l,\sigma} 2\lambda_{l,\sigma}), em que λl,σ\lambda_{l,\sigma} é a taxa de erro de ruído aprendida de Pauli σ\sigma na camada ll do circuito. Esse redimensionamento aumenta a variância em um fator de γ2\gamma^2 e, portanto, também multiplica o número de execuções de circuitos necessárias na QPU por γ2\gamma^2, o que chamamos de custo de amostragem ou sobrecarga de amostragem. Como o número de qubits ( γ\gamma ) cresce exponencialmente, o PEC costuma se limitar a circuitos rasos ou com poucos qubits. Saiba mais sobre o PEC no artigo “Cancelamento probabilístico de erros com modelos esparsos de Pauli-Lindblad em processadores quânticos sujeitos a ruído ”.

Se conseguirmos identificar erros que não precisam ser mitigados, poderemos reduzir esse custo de amostragem exponencialmente. Um primeiro passo nessa direção é a implementação de uma mitigação de erros com consciência local, que utiliza um “cone de luz” convencional de cálculo rápido para reduzir a sobrecarga do PEC, limitando a sensibilidade de um observável aos erros em todo o circuito, ampliando assim a viabilidade do PEC para escalas maiores em alguns problemas. Erros fora desse cone de luz não podem afetar o resultado medido e, portanto, podem ser excluídos do procedimento de cancelamento de erros. Essa exclusão reduz a sobrecarga da amostragem, em alguns casos de forma substancial, sem introduzir viés adicional. Em particular, para medir um observável local OO de um circuito de profundidade fixa, a sobrecarga de amostragem necessária acaba se estabilizando ao aumentar o número de qubits no circuito (ver Fig. 2b em “Locality and error mitigation of quantum circuits ”).

Os cones de luz sombreados (SLC) vão além, utilizando simulações clássicas para restringir de forma mais precisa a sensibilidade a erros em todo o circuito. Isso troca um pouco do tempo da QPU pelo tempo da CPU e reduz a sobrecarga de amostragem necessária para renormalizar o viés. Em vez de um limite rígido, a cada erro potencial no circuito é atribuído um “grau” que define o limite superior da suscetibilidade do valor esperado da variável observável a esse erro. (Para os limites propagados para trás apresentados a seguir, essa suscetibilidade também depende do estado inicial do circuito — o estado em que todos os valores são zero em fluxos de trabalho típicos e neste tutorial.) Essa caracterização refinada permite aplicações mais eficientes e direcionadas da PEC, com menor variância, ao mesmo tempo em que oferece ao usuário a capacidade de ajustar de forma controlada o viés na estimativa do observável. Consulte “Sombreamento Lightcone para mitigação de erros quânticos com aceleração clássica” para obter mais detalhes.

Nosso fluxo de trabalho para o complemento SLC utiliza a samplomatic biblioteca em conjunto com as QuantumProgram classes e Executor adicionadas ao Qiskit Runtime na página qiskit-ibm-runtimev0.47.0, permitindo que os usuários tenham um controle mais modular das configurações de execução para supressão e mitigação de erros, mantendo a facilidade de uso. Leia mais no guia do modelo de execução direcionada.

Visão geral do fluxo de trabalho de mitigação de erros do SLC

Para modelar o ruído da QPU, utilizamos um modelo de ruído esparso de Pauli-Lindblad com taxas de erro de Pauli de um e dois qubits, geradas localmente em cada qubit e em cada borda do dispositivo. Com essa escolha, o fluxo de trabalho de mitigação de erros do SLC apresentado neste tutorial é o seguinte:

a. CPU — Impacto por erro limitado de erros de Pauli de um e dois qubits

  1. Propagação direta (efeito de ligação na grande quantidade observável). Propague cada erro até o final do circuito e calcule seu comutador com o observável.
    • Truncar os termos do operador durante a evolução para manter o cálculo gerenciável.
    • Restrinja ainda mais esses limites por meio de uma retropropagação aproximada da observável, com base nos limites de velocidade quântica.
  2. Propagação para trás (efeito de ligação no estado inicial). Propague cada erro até o início do circuito e calcule seu comutador com o estado inicial.

b. QPU — Conheça os índices de ruído. Use NoiseLearnerV3 para estimar as taxas do modelo de ruído de Pauli-Lindblad.

c. CPU — Priorizar medidas de mitigação

  1. Atualizar os limites combinados com as taxas de ruído aprendidas. Combinar os limites de avanço e retrocesso calculados anteriormente e atualizá-los com as taxas de ruído aprendidas.
  2. Classifique os componentes de ruído a serem mitigados utilizando os limites calculados e as taxas aprendidas. Priorize cada possível erro de ruído com base em seu impacto estimado no viés e no custo associado à sua correção.

d. QPU — Insira o filtro antirruído e execute. Execute o circuito de interesse com o antirruído (ruído inverso) especificado por meio de Box anotações.

e. CPU — Estimativa observável. Calcule o valor esperado, aplicando uma pós-seleção baseada em medições para reduzir o impacto do ruído não markoviano.

Visão geral do aprendizado com ruído

O aprendizado de ruído é uma etapa comum em vários métodos de mitigação de erros, realizada pelo “noise learner ”; ele também aparece no tutorial sobre amplificação probabilística de erros. NoiseLearnerV3Nesse contexto, é possível identificar especificamente as camadas de ruído a serem aprendidas como CircuitInstruction objetos, de modo que se possa calcular os limites de ruído SLC desejados para cada camada, conforme descrito acima. O modelo de Pauli-Lindblad fornece coeficientes a serem utilizados na priorização PEC+SLC. generate_noise_model_paulisA forma como os portões são agrupados em camadas pode ser determinada por meio das generate_boxing_pass_manager funções de conveniência e find_unique_box_instructions , e, em seguida, inserida na função utilidade SLC, conforme descrito na Etapa 2 abaixo.

Parte 1
Parte 2
Parte 3
Camadas de portas de dois qubits com giro de PauliRepita pares de camadas idênticas e aprenda o ruídoCalcular a fidelidade (erro para cada canal de ruído)
Giro de PauliAprenda sobre camadasAjuste de curva

Visão geral do pós-processamento

Após executar o código no hardware quântico utilizando as samplomatic estruturas e Executor , convertemos nossas medições de sequência de bits no valor observável desejado. Neste tutorial, demonstramos o fluxo de trabalho em um circuito de Ising espelhado (construído na Etapa 1 abaixo), no qual o valor esperado ideal da observável medida é 1, pois a segunda metade do circuito anula a primeira. Ao calcular o valor observável com a executor_expectation_values função, aplicamos algumas técnicas de pós-processamento que reduzem o impacto do ruído. Isso inclui a remoção de imagens afetadas por ruído não markoviano, a mitigação de erros de leitura e a consideração dos detalhes da nossa implementação do PEC. Os detalhes são abordados na Etapa 4 abaixo.


Requisitos

Antes de iniciar este tutorial, certifique-se de ter os seguintes itens instalados:

  • Qiskit SDK v2.2 ou versão posterior, com suporte à visualização
  • Qiskit Runtime v0.47 ou posterior (pip install qiskit-ibm-runtime)
  • Complemento “Shaded lightcones” do Qiskit v0.1 ou versão posterior (pip install qiskit-addon-slc)
  • Complemento Qiskit utils v0.3 ou versão posterior (pip install qiskit-addon-utils)
  • Samplomatic v0.13 ou versão posterior (pip install samplomatic)

Instalação

Primeiro, importe os pacotes e as funções necessárias para executar este notebook.

from multiprocessing import set_start_method

# Setting this value prevents itertools.starmap deadlock on UNIX systems
set_start_method("spawn")

# Needed to prevent PySCF from parallelizing internally (SLC only)
%set_env OMP_NUM_THREADS=1

Output:

env: OMP_NUM_THREADS=1
import numpy as np
from matplotlib import pyplot as plt

from qiskit import QuantumCircuit
from qiskit.quantum_info import SparsePauliOp
from qiskit.transpiler import generate_preset_pass_manager, PassManager

from qiskit_ibm_runtime import (
    QiskitRuntimeService,
    QuantumProgram,
    Executor,
    NoiseLearnerV3,
)

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

from qiskit_addon_utils.exp_vals.measurement_bases import (
    get_measurement_bases,
)
from qiskit_addon_utils.exp_vals.expectation_values import (
    executor_expectation_values,
)
from qiskit_addon_utils.noise_management import (
    gamma_from_noisy_boxes,
    trex_factors,
)
from qiskit_addon_utils.noise_management.post_selection import PostSelector
from qiskit_addon_utils.noise_management.post_selection.transpiler.passes import (
    AddPostSelectionMeasures,
    AddSpectatorMeasures,
)

from qiskit_addon_slc.bounds import (
    compute_backward_bounds,
    compute_forward_bounds,
    compute_local_scales,
    merge_bounds,
    tighten_with_speed_limit,
)
from qiskit_addon_slc.utils import (
    generate_noise_model_paulis,
    map_modifier_ref_to_ref,
)
from qiskit_addon_slc.visualization import draw_shaded_lightcone

Exemplo de simulador em pequena escala

Assim como outros métodos de mitigação de erros baseados em aprendizado, o PEC com cones de luz sombreados mitiga o ruído físico de um processador quântico específico; portanto, depende de recursos de hardware que não têm equivalente significativo em um simulador ideal:

  • NoiseLearnerV3 caracteriza experimentalmente o canal de ruído esparso de Pauli-Lindblad em cada camada única de dois qubits. Em um simulador silencioso, não há ruído para ser cancelado.
  • O Executor primitivo amostra os circuitos girados e com injeção de antirruído gerados por samplomatic em um backend.

O cálculo do limite do cone de luz sombreado é clássico, mas só faz sentido em relação às taxas de ruído do hardware aprendidas, que definem o orçamento de mitigação e a sobrecarga de amostragem. Por esses motivos, deixamos de lado o exemplo do simulador em pequena escala e demonstramos o fluxo de trabalho completo do PEC+SLC diretamente no hardware, com cada etapa do padrão do Qiskit detalhada a seguir.


Exemplo de hardware em grande escala

Executamos o fluxo de trabalho completo do PEC+SLC em um circuito de Ising espelhado de 20 qubits, executado no hardwar IBM Quantum®, seguindo as quatro etapas de um padrão do Qiskit.

Etapa 1: Identificar o problema

Nossa demonstração está organizada da seguinte forma:

  1. Optamos por realizar a evolução temporal sob um hamiltoniano de cadeia de Ising do tipo “ 1D ”. A cadeia de Ising do modelo “ 1D ” apresenta uma estrutura de circuito bem densa, o que é conveniente para demonstrar implementações do PEC.
  2. Optamos por medir a grandeza observável X6Z13\langle X_6 Z_{13}\rangle, que contém um componente XX e um componente ZZ. Isso é útil para destacar como os limites do SLC tratam diferentes tipos de erros de maneiras distintas.
  3. Queremos que o valor esperado ideal dessa observável seja exatamente 1, para que seja fácil avaliar a qualidade das estimativas mitigadas. Ao espelhar o circuito, já estamos quase lá: para cada porta na segunda metade do circuito, há uma porta inversa na primeira metade, de modo que o circuito ideal atua como a identidade.
  4. Por fim, para que o valor esperado ideal seja 1, é necessário que o circuito comece — e, portanto, graças ao espelhamento, idealmente também termine — em um estado próprio +1 do observável. Como o observável contém um componente não- ZZ, e a Executor primitiva representa a base de medição desejada no final do circuito, inserimos as portas de preparação de base correspondentes no início do circuito espelho por meio de uma prepare_basis função. Utilizamos a get_measurement_bases função para identificar quais portas são necessárias e onde posicioná-las, ao mesmo tempo em que acompanhamos as sutilezas relacionadas ao índice dos qubits decorrentes das box convenções de anotação, conforme discutido na seção sobre a preparação de medições em base canônica.
# Width of the Ising chain (a free parameter of this demonstration)
num_qubits = 20

# The observable to measure: one X component (on qubit 6) and one Z component
# (on qubit 13), as motivated in "Step 1: Map the problem" above
target_obs_sparse = [("XZ", [6, 13], 1.0)]
observable = SparsePauliOp.from_sparse_list(
    target_obs_sparse, num_qubits=num_qubits
)
# Determine the measurement basis each qubit requires for this observable.
# The reverser is used during post-processing in Step 4.
bases_virt, reverser_virt = get_measurement_bases(observable)
# Free parameters of the demonstration circuit: the number of Trotter steps
# sets the circuit depth, and rx_angle is the transverse-field rotation angle
# applied in each step (pi/4 makes the dynamics non-Clifford)
num_trotter_steps = 10
rx_angle = np.pi / 4
def construct_ising_circuit(
    num_qubits: int,
    num_trotter_steps: int,
    rx_angle: float,
) -> QuantumCircuit:
    circuit = QuantumCircuit(num_qubits)

    for _ in range(num_trotter_steps):
        circuit.rx(rx_angle, range(num_qubits))
        for first_qubit in (1, 2):
            for idx in range(first_qubit, num_qubits, 2):
                # equivalent to Rzz(-pi/2):
                circuit.sdg([idx - 1, idx])
                circuit.cz(idx - 1, idx)

    return circuit


def prepare_basis(
    circuit: QuantumCircuit, basis: list[int]
) -> QuantumCircuit:
    # basis is a list of integer values from 0 to 3. These map to the basis measurement as:
    # 0 = I; 1 = Z; 2 = X; 3 = Y
    assert len(basis) == circuit.num_qubits

    out_circ = circuit.copy_empty_like()
    for qb, bas in enumerate(basis):
        if bas == 2:
            out_circ.h(qb)
        elif bas == 3:
            out_circ.rx(-np.pi / 2, qb)

    out_circ.barrier()
    out_circ.compose(circuit, inplace=True)
    return out_circ


def mirror_circuit(circuit: QuantumCircuit) -> QuantumCircuit:
    mirror_circ = circuit.copy_empty_like()
    mirror_circ.compose(circuit.inverse(), inplace=True)
    mirror_circ.barrier()
    mirror_circ.compose(circuit, inplace=True)
    mirror_circ.measure_active()
    return mirror_circ
# Instantiate the mirrored circuit and prepend the basis-preparation gates
circuit = construct_ising_circuit(num_qubits, num_trotter_steps, rx_angle)
mirrored_circuit = mirror_circuit(circuit)
mirrored_circuit = prepare_basis(mirrored_circuit, bases_virt[0])
mirrored_circuit.draw(
    "mpl", fold=-1, scale=0.3, idle_wires=False, measure_arrows=False
)

Output:

Output of the previous code cell

Etapa 2: Otimizar

Otimizamos os detalhes relacionados ao circuito a ser executado, à variável observável a ser medida e aos parâmetros de aprendizado de ruído. Como ponto de partida, selecionamos o processador Heron menos ocupado e o instanciamos com as portas fracionárias ativadas. Essas portas fracionárias permitem uma maior sensibilidade em algumas de nossas filtragens pós-seleção.

# Initialize the Qiskit Runtime service using your saved credentials
service = QiskitRuntimeService()

# Select the least busy Heron device. This tutorial targets Heron processors:
# the usage estimate and the non-Markovian-noise post-selection discussed in
# Step 4 are specific to this processor family.
backend = service.least_busy(
    operational=True,
    simulator=False,
    filters=lambda b: b.processor_type["family"] == "Heron",
)
# Re-fetch with fractional gates enabled (least_busy does not forward this)
# Fractional gates are enabled so the non-Clifford Rx rotations are supported natively.
backend = service.backend(backend.name, use_fractional_gates=True)
print(f"Selected backend: {backend.name}")

Output:

Selected backend: ibm_kingston

Primeiro, vamos transpilá-lo para instruções ISA, conforme necessário para a execução em nossas QPUs. A escolha dos qubits físicos a serem utilizados tem um grande impacto na qualidade dos resultados, pois os tempos de coerência e as fidelidades das portas variam dentro de um mesmo dispositivo e sofrem desvios ao longo do tempo. Para um experimento bem planejado, pode valer a pena selecionar manualmente uma cadeia de qubits com base em experimentos rápidos de benchmarking (veja, por exemplo, o tutorial “Benchmarking em tempo real para seleção de qubits ”). Para os fins deste tutorial, utilizamos o transpiler do Qiskit para selecionar um layout com base no mapa de acoplamento do backend e em seus dados de calibração mais recentes.

isa_pm = generate_preset_pass_manager(backend=backend, optimization_level=1)

isa_circuit = isa_pm.run(mirrored_circuit)

# The chain of physical qubits selected by the transpiler
layout = isa_circuit.layout.final_index_layout()
print(f"Selected layout: {layout}")

isa_observable = observable.apply_layout(
    layout, num_qubits=isa_circuit.num_qubits
)

Output:

Selected layout: [71, 58, 51, 50, 49, 48, 47, 46, 45, 44, 43, 42, 41, 36, 21, 22, 23, 16, 3, 2]
wire_order = layout + [
    q for q in range(isa_circuit.num_qubits) if q not in layout
]
isa_circuit.draw(
    "mpl",
    fold=-1,
    scale=0.3,
    idle_wires=False,
    wire_order=wire_order,
    measure_arrows=False,
)

Output:

Output of the previous code cell

Enquadrar o circuito

Para facilitar a implementação, utilizamos a generate_boxing_pass_manager etapa de transpilação, que coloca as instruções do circuito em caixas anotadas. Essas caixas indicam claramente onde, no caso do PEC, o sinal antirruído deve ser injetado no circuito. Para obter mais detalhes sobre as configurações, consulte a documentação do Samplomatic.

Observe que o fluxo de trabalho do SLC exige o uso do inject_noise_strategy="individual_modification" mais adiante no processo, pois isso nos permite identificar de forma exclusiva as InjectNoise anotações das BoxOp instruções no circuito. (As caixas sem nenhuma InjectNoise anotação não podem ser identificadas dessa forma.)

Caution

Atualmente, os cálculos de ligação do SLC partem do pressuposto de que as InjectNoise anotações são colocadas com site="after" (consulte qiskit-addon-slc#33 ). Como o valor padrão dessa configuração mudou nas versões mais recentes de samplomatic (consulte samplomatic#372 ), definimos inject_noise_site="after" explicitamente a seguir.

A find_unique_box_instructions função percorre o circuito encapsulado fornecido e identifica aqueles que possuem camadas ou medições exclusivas de dois qubits ( 2Q ), com o objetivo de aprendizado de ruído e injeção de ruído.

# Box circuit with Twirl and InjectNoise annotations
boxes_pm = generate_boxing_pass_manager(
    twirling_strategy="active",
    inject_noise_strategy="individual_modification",
    inject_noise_site="after",
    inject_noise_targets="gates",
    measure_annotations="all",
)


boxed_circuit = boxes_pm.run(isa_circuit)

# Find the unique instructions (layers) from boxed circuit
unique_2q_instructions = find_unique_box_instructions(
    boxed_circuit, normalize_annotations=None, undress_boxes=True
)
boxed_circuit.draw(
    "mpl",
    fold=-1,
    scale=0.3,
    idle_wires=False,
    wire_order=wire_order,
    measure_arrows=False,
)

Output:

Output of the previous code cell

Preparar medições de bases canônicas

Observe que devemos ter um cuidado especial para acompanhar a ordem dos qubits. A seguir, apresentamos o conceito de canonical_qubits como forma de atualizar adequadamente a ordem dos qubits ao fornecê-la ao Executor, tendo em vista a maneira como a ordem dos qubits é capturada durante o encapsulamento de circuitos e a identificação de instruções únicas. Consulte a documentação sobre a convenção de ordenação de qubits para obter mais detalhes.

# Determine the canonical qubits order
meas_box = boxed_circuit.data[-1]
canonical_qubits = [
    idx
    for idx, qubit in enumerate(boxed_circuit.qubits)
    if qubit in meas_box.qubits
]

# map canonical qubit to physical (isa) qubit
c_2_p = {c: p for c, p in enumerate(canonical_qubits)}
# map physical (isa) qubit to virtual qubit (index in original circuit)
p_2_v = {p: v for v, p in enumerate(layout)}
# compute map between virtual and canonical qubit indices.
c_2_v = {c: p_2_v[p] for c, p in c_2_p.items()}

assert len(c_2_v) == num_qubits

bases_canon = [
    np.array([base_i[c_2_v[c]] for c in range(num_qubits)], dtype=np.uint8)
    for base_i in bases_virt
]

Fluxo de trabalho para sombreamento por cone de luz, aprendizado de ruído e injeção antirruído

Note

Neste tutorial, executamos os cálculos de limite SLC antes que o aprendizado do ruído seja concluído; assim, o circuito a ser mitigado é executado o mais próximo possível, em termos de tempo, do modelo de ruído aprendido. Em princípio, esse fluxo de trabalho pode ser ainda mais paralelizado: uma tarefa de aprendizado de ruído pode ser executada enquanto, paralelamente, os limites de ruído são estimados. Para um circuito quântico arbitrário, o cálculo limitado pelo ruído pode variar com uma dependência exponencial fraca; assim, a paralelização desse cálculo (por exemplo, em vários núcleos de CPU) resulta em limites mais restritos para um determinado orçamento de tempo de computação, e as execuções na QPU e os cálculos limitados podem, por sua vez, ser paralelizados para se obter o fluxo de trabalho mais eficiente.

Prever os Paulis do modelo de ruído a ser aprendido

Antes de executar qualquer aprendizado com ruído no hardware, primeiro enumeramos quais termos de erro de Pauli nosso modelo de ruído conterá. Lembre-se de que modelamos o ruído de cada camada específica como um modelo esparso de Pauli-Lindblad, com uma taxa de erro para cada Pauli de peso um ( XX, YY ou ZZ ) em cada qubit ativo, e uma para cada Pauli de peso dois em cada par conectado de qubits ativos. A generate_noise_model_paulis função percorre cada camada em caixa única do circuito fornecido e gera exatamente essa lista de termos de Pauli, levando em consideração a conectividade dos qubits do circuito.

Em outras palavras, essa etapa prevê a estrutura do modelo de ruído que NoiseLearnerV3 , posteriormente, será preenchida com as taxas aprendidas. Conhecer essa estrutura com antecedência é o que nos permite calcular um limite superior e um limite inferior para cada erro potencial antes mesmo de a aprendizagem com ruído ser executada.

noise_model_paulis = generate_noise_model_paulis(
    unique_2q_instructions, backend.coupling_map, boxed_circuit
)
noise_model_rates = {ref: None for ref in noise_model_paulis}
a. Calcular os limites para a frente

A compute_forward_bounds função avalia as relações de comutação entre as portas em cada camada e os termos de Pauli gerados anteriormente, considerando como os erros de propagação para frente afetam o observável desejado AA. Para as portas que comutam com os termos de Pauli, nada é feito. No caso das portas de Clifford, elas são deslocadas para o início do circuito. Para portas que não sejam do tipo Clifford, calculamos uma aproximação de sua influência sobre as observáveis-alvo, a fim de que estas sejam posteriormente priorizadas para o cancelamento de ruído (após a fusão de todos os limites). Esse limite é obtido aplicando-se, em primeiro lugar, a norma de L2 (ou seja, a raiz quadrada da soma dos quadrados dos coeficientes dos termos de Pauli relevantes). Quando há muitos termos de qubits envolvidos, recorremos a um limite menos restritivo que utiliza a desigualdade triangular.

Definir os parâmetros de cálculo de intervalos

As configurações a seguir controlam os cálculos clássicos de limites:

  • slc_atol: a tolerância absoluta dos cálculos. Os termos do operador com coeficientes abaixo desse limite são considerados insignificantes e descartados.
  • slc_eigval_max_qubits: o número máximo de qubits para o qual é avaliado o limite mais restrito, baseado em valores próprios ( L2-norm ). Os operadores suportados por um número de qubits superior a esse voltam a seguir o limite mais flexível da desigualdade triangular descrito acima.
  • slc_evolution_max_terms: o número máximo de termos de Pauli mantidos durante a propagação dos operadores pelo circuito. O truncamento dos termos menores mantém o cálculo viável, em troca de limites ligeiramente menos precisos.
  • slc_num_processes: o número de processos da CPU entre os quais o cálculo limitante é paralelizado.
  • slc_timeout: o tempo previsto (em segundos) para o cálculo do limite. A qualquer camada que não seja alcançada dentro desse limite é atribuído o valor trivial de 2.
Note

Neste tutorial, o tempo limite é atingido propositalmente : mantemos esse tempo slc_timeout deliberadamente curto para que o notebook seja executado rapidamente e para demonstrar que atingir o tempo limite é seguro. Os tons calculados são limites superiores em cada etapa; portanto, atingir o tempo limite apenas torna os limites mais amplos (triviais) para as camadas que não foram alcançadas; isso nunca subestima o efeito de um erro. Isso aparece nas visualizações abaixo como as camadas mais à esquerda sendo preenchidas inteiramente pelo limite trivial. Aumentar o tempo limite (ou o número de processos) estreita os limites e reduz ainda mais a sobrecarga da amostragem.

slc_atol = 1e-8
slc_eigval_max_qubits = 18
slc_evolution_max_terms = 1000
slc_num_processes = 8
slc_timeout = 60
forward_bounds = compute_forward_bounds(
    boxed_circuit,
    noise_model_paulis,
    isa_observable,
    evolution_max_terms=slc_evolution_max_terms,
    eigval_max_qubits=slc_eigval_max_qubits,
    atol=slc_atol,
    num_processes=slc_num_processes,
    timeout=slc_timeout,
)

Output:

Bounds computation timed out.

Visualize o SLC para inspeção manual

É possível interpretar o comportamento dos limites sombreados examinando como as medições e os termos de Pauli interagem com os erros locais. Esses padrões são característicos desse problema de evolução temporal do hamiltoniano de Ising com “kick” e também aparecem no artigo “Lightcone Shading for Classically Accelerated Quantum Error Mitigation”, apresentando várias características reveladoras:

  • Podemos distinguir claramente os dois cones decorrentes dos dois Paulis não identitários no observável.
  • Podemos observar que a medição X no qubit 6 comuta com o erro X na camada mais à direita.
  • Podemos observar que o Z de Pauli no qubit 13 comuta com o erro Z na camada mais à direita.
  • Quando atingirmos o tempo limite especificado acima, as camadas restantes à esquerda serão preenchidas inteiramente com limites triviais de dois.
for p in "XYZ":
    display(
        draw_shaded_lightcone(
            boxed_circuit,
            forward_bounds,
            noise_model_paulis,
            pauli_filter=p,
            scale=0.15,
            fold=-1,
            idle_wires=False,
            wire_order=wire_order,
            measure_arrows=False,
        )
    )

Output:

Output of the previous code cell Output of the previous code cell Output of the previous code cell

b. Aperfeiçoar os limites superiores com os limites de velocidade quântica

Em seguida, restringimos os limites utilizando a tighten_with_speed_limit função. A extensão geométrica do cone de luz do observável já está totalmente considerada nos limites para a frente calculados acima. O que essa função acrescenta é uma etapa de autoconsistência que aproveita a velocidade limitada com que a informação pode fluir pelo circuito — o cone de luz cresce, no máximo, um qubit por camada de portas de dois qubits — para estreitar os limites calculados anteriormente em relação uns aos outros. A informação flui nos dois sentidos:

  1. Dos limites do comutador aos limites do observável retropropagado: se o observável comuta com um erro de “ ZZ ” em um qubit, então ele não pode ter uma componente significativa de “ XX ” ou “ YY ” nesse qubit.
  2. Dos limites do observável retropropagado aos limites do comutador: se o observável tiver componentes XX e YY insignificantes em um qubit, então ele deve quase comutar com ZZ nesse qubit.
forward_bounds_tighter = tighten_with_speed_limit(
    forward_bounds, boxed_circuit, noise_model_paulis, isa_observable
)

Visualize o SLC para inspeção manual

Em princípio, esse ajuste proporciona uma transição mais suave entre os limites calculados e os limites triviais atribuídos às camadas após o tempo limite. Neste exemplo, o efeito é quase imperceptível, pois os cones de luz já se espalharam até as bordas do circuito quando o tempo limite é atingido.

for p in "XYZ":
    display(
        draw_shaded_lightcone(
            boxed_circuit,
            forward_bounds_tighter,
            noise_model_paulis,
            pauli_filter=p,
            scale=0.15,
            fold=-1,
            idle_wires=False,
            wire_order=wire_order,
            measure_arrows=False,
        )
    )

Output:

Output of the previous code cell Output of the previous code cell Output of the previous code cell

c. Calcular os limites posteriores

Esta parte da previsão de ruído avalia como um erro em uma camada específica pode afetar o estado de entrada ρ\rho. A compute_backward_bounds função primeiro inverte o circuito e remove as portas de medição e, em seguida, prossegue com uma análise semelhante à realizada para os cálculos no sentido direto.

backward_bounds = compute_backward_bounds(
    boxed_circuit,
    noise_model_paulis,
    evolution_max_terms=slc_evolution_max_terms,
    num_processes=slc_num_processes,
    timeout=slc_timeout,
)

Visualize o SLC para inspeção manual

Ao calcular os limites posteriores, podemos observar como a estrutura do estado inicial determina o comportamento inicial da propagação de erros:

  • Podemos ver claramente como os erros Z inicialmente comutam com o estado inicial |0⟩.
  • Somente no qubit 6, onde inicializamos o estado próprio +1 da base X, um erro Z não comuta, enquanto um erro X sim comuta.
for p in "XYZ":
    display(
        draw_shaded_lightcone(
            boxed_circuit,
            backward_bounds,
            noise_model_paulis,
            pauli_filter=p,
            scale=0.15,
            fold=-1,
            idle_wires=False,
            wire_order=wire_order,
            measure_arrows=False,
        )
    )

Output:

Output of the previous code cell Output of the previous code cell Output of the previous code cell

Visualizar limites mesclados sem taxas de ruído aprendidas

A merged_bounds função determina o ponto no circuito em que a mudança dos limites de retrocesso para os limites de avanço minimiza o viés total estimado na variável observável desejada. Esse viés é calculado como a soma das contribuições do sentido reverso para todos os pontos de ruído anteriores a esse ponto, mais as contribuições do sentido direto para todos os pontos de ruído posteriores a ele. Atualmente, isso é feito de maneira uniforme para todos os qubits.

Nota importante

O ponto ideal para mudar do limite traseiro para o limite dianteiro depende das taxas de ruído aprendidas. Nesta fase, ainda não definimos nenhuma taxa: noise_model_rates foi inicializado em None para cada camada, o que merge_bounds interpreta como taxas de ruído uniformes. Os limites combinados mostrados aqui são, portanto, apenas uma prévia; nós os recalculamos com as taxas reais aprendidas na Etapa 3.

merged_bounds = merge_bounds(
    boxed_circuit,
    forward_bounds_tighter,
    backward_bounds,
    noise_model_rates,
)

Output:

Missing noise rates. Partitioning backward/forward commutator bounds by assuming uniform error rates.
Optimal spacetime partitioning not implemented!Just partitioning list of noisy boxes.

Visualize o SLC para inspeção manual

Após a fusão dos limites traseiro e dianteiro restrito, o comportamento dos SLCs combinados fica claro:

  • A função acima nos indica que é escolhida uma partição na qual ocorre a transição de limites traseiros para limites dianteiros mais restritos.
  • Podemos observar abaixo que os SLCs agora contêm limites superiores parciais para trás e limites inferiores parciais mais restritos para a frente.
for p in "XYZ":
    display(
        draw_shaded_lightcone(
            boxed_circuit,
            merged_bounds,
            noise_model_paulis,
            pauli_filter=p,
            scale=0.15,
            fold=-1,
            idle_wires=False,
            wire_order=wire_order,
            measure_arrows=False,
        )
    )

Output:

Output of the previous code cell Output of the previous code cell Output of the previous code cell

Etapa 3: Executar

Nesta seção, iniciamos a parte do fluxo de trabalho que utiliza um dispositivo quântico real. Para esse método de mitigação de erros baseado em aprendizado, há duas etapas:

  1. Aprenda o som usando NoiseLearnerV3.
  2. Executar um circuito de mitigação de erros utilizando as samplomatic estruturas e Executor .

Com os erros limitados do nosso circuito quântico, determinamos as taxas de ruído associadas para priorizar nosso orçamento de erros, calcular a sobrecarga de amostragem e executar o código em uma QPU.

a. Conheça os índices de ruído

O alvo de aprendizado de ruído caracteriza os processos de ruído que afetam os portões em um ou mais circuitos de interesse, com base no modelo de ruído esparso de Pauli-Lindblad. O run() método inicia um trabalho de aprendizado com ruído para as camadas exclusivas de dois qubits fornecidas, utilizando as opções especificadas na configuração do noise-learner. Essas opções controlam a estratégia de “Pauli-twirling”, o número de randomizações e tentativas, as profundidades de aprendizado e a pós-seleção.

Também escolhemos deliberadamente o nível de aprofundamento do aprendizado. Uma constatação prática para a mitigação baseada em aprendizado com samplomatic é que é altamente benéfico que a profundidade máxima do aprendizado corresponda à profundidade do circuito que se deseja mitigar. Como os NLv3layer_pair_depths são medidos em pares de camadas (uma camada mais sua inversa), definimos o valor mais profundo como metade da profundidade da camada de dois qubits do circuito.

post_selection_enabled = True
# Match the deepest noise-learning depth to the depth of the circuit being
# mitigated. NLv3 ``layer_pair_depths`` are measured in layer pairs (a layer
# plus its inverse), so the deepest value is half the circuit's two-qubit-layer
# depth. Learning to this depth markedly improves the quality of the mitigation.
#
# We measure the two-qubit-layer depth on the pre-boxed ISA circuit: after
# boxing, every two-qubit gate is hidden inside a full-width ``BoxOp``, so a
# ``num_qubits == 2`` filter on ``boxed_circuit`` matches nothing (and
# ``QuantumCircuit.depth`` does not recurse into boxes).
depth_2q = isa_circuit.depth(lambda instr: instr.operation.num_qubits == 2)
max_layer_pair_depth = depth_2q // 2  # dividing by 2 since we want pairs

# Use a fixed schedule of learning depths, but drop any that exceed the circuit's
# depth and always cap the deepest value at ``max_layer_pair_depth`` so we never
# learn deeper than the circuit being mitigated.
candidate_depths = [1, 2, 4, 8, 12, 16, 24, 32, 40, 48]
layer_pair_depths = sorted(
    {d for d in candidate_depths if d < max_layer_pair_depth}
    | {max_layer_pair_depth}
)

noise_learner_options = {
    "num_randomizations": 64,
    "shots_per_randomization": 128,
    "layer_pair_depths": layer_pair_depths,
    "post_selection": {
        "enable": post_selection_enabled,
        "strategy": "edge",
        "x_pulse_type": "rx",
    },
    "environment": {"job_tags": ["TUT_SLC"]},
}

noise_learner = NoiseLearnerV3(backend, noise_learner_options)
noise_learner_job = noise_learner.run(unique_2q_instructions)
noise_learner_result = noise_learner_job.result()
if post_selection_enabled:
    print(
        "Minimum fraction of shots kept for noise learning experiments: ",
        end="",
    )
    print(
        f"{min([min(d.values()) for d in [nlr.metadata['post_selection']['fraction_kept'] for nlr in noise_learner_result[:2]]]):.2f}"
    )

Output:

Minimum fraction of shots kept for noise learning experiments: 0.71
# Get a dict mapping each InjectNoise.ref to its learned PauliLindbladMap
refs_2_plm = noise_learner_result.to_dict(
    unique_2q_instructions, require_refs=False
)

b.i. Atualizar os limites combinados com as taxas de ruído reais aprendidas

Agora que o modelo específico de ruído foi aprendido, podemos aplicar as taxas de ruído aprendidas aos limites de ruído previstos e obter uma determinação final de quais limites têm maior impacto na minimização do viés.

merged_bounds = merge_bounds(
    boxed_circuit,
    forward_bounds_tighter,
    backward_bounds,
    refs_2_plm,
)

Output:

Optimal spacetime partitioning not implemented!Just partitioning list of noisy boxes.

b.ii. Calcule o local_scales para a execução em hardware

compute_local_scales analisa cada possível erro de ruído no circuito e estima em que medida esse erro poderia distorcer a medição final, bem como quanto custaria corrigi-lo. Em seguida, ele classifica os erros de acordo com a importância de mitigá-los e seleciona o subconjunto que reduz o viés ao máximo, mantendo-se dentro do orçamento permitido para o custo de amostragem (ou atingindo a precisão desejada). O resultado é um conjunto de fatores de escala que indicam quais erros serão ativamente mitigados e quais não serão mitigados (local_scales), juntamente com a sobrecarga total prevista do custo de amostragem (sampling_costs) e o viés remanescente (residual_bias_bound).

A capacidade de controlar a polarização residual desejada é uma característica fundamental da implementação do PEC no SLC. Enquanto na implementação original a sobrecarga de amostragem sempre visava um viés nulo, podemos ajustar a sobrecarga de amostragem necessária, em troca de um viés residual esperado. Isso ajuda o usuário a permanecer dentro de um orçamento fixo de amostragem, o que pode ser particularmente útil na fase inicial de prototipagem de um fluxo de trabalho.

id_map = map_modifier_ref_to_ref(boxed_circuit)
summed_rates = 0.0
for box_id, noise_id in id_map.items():
    learned_plm = refs_2_plm[noise_id]
    summed_rates += np.sum(learned_plm.rates)
    # print(f"{box_id}:\tgamma = {np.exp(2 * summed_rates):1.6e}\tsampling cost = {np.exp(4 * summed_rates):1.6e}")
total_gamma = np.exp(2 * summed_rates)
print(
    f"Full PEC gamma={total_gamma}, sampling cost (gamma^2) = {total_gamma**2}"
)

Output:

Full PEC gamma=12.750876317157042, sampling cost (gamma^2) = 162.58484685543633
biases = []
costs = []
for bias in [0.0] + np.arange(0.001, 0.102, 0.01).tolist():
    _, cost_, bias_ = compute_local_scales(
        boxed_circuit,
        merged_bounds,
        refs_2_plm,
        sampling_cost_budget=np.inf,
        bias_tolerance=bias,
    )
    biases.append(bias_)
    costs.append(cost_)

Equilibrar a sobrecarga da amostragem com o viés residual

xticks = np.arange(0, 11)

fig, ax = plt.subplots()
ax.scatter(
    [0], [total_gamma**2], marker="D", c="tab:orange", label="full PEC"
)
ax.plot(
    100 * np.array(biases),
    np.array(costs),
    "o-",
    c="tab:blue",
    label="PEC+SLC",
)
ax.set_yscale("log")
ax.set_xticks(xticks, [f"{x:.1f}" for x in xticks])

ax.set_xlabel("Remaining bias [%]")
ax.set_ylabel(r"Sampling overhead, $\gamma^2$")
ax.grid()
ax.legend()
fig.suptitle("PEC sampling overhead reduction due to SLC")

Output:

Text(0.5, 0.98, 'PEC sampling overhead reduction due to SLC')
Output of the previous code cell
chosen_bias_thres = 0.1
local_scales, sampling_cost, residual_bias_bound = compute_local_scales(
    boxed_circuit,
    merged_bounds,
    refs_2_plm,
    sampling_cost_budget=np.inf,
    bias_tolerance=chosen_bias_thres,
)
print(
    f"PEC+SLC sampling cost (gamma^2) = {sampling_cost} "
    f"w/ remaining bias = {100 * residual_bias_bound:.1f}%"
)

Output:

PEC+SLC sampling cost (gamma^2) = 48.13646509622307 w/ remaining bias = 10.0%

c. Execute o circuito em questão com o antinoise

c.i. Prepare o circuito modelo utilizando samplex

O samplex é um resultado do build método do Samplomatic, que codifica todas as informações necessárias para gerar parâmetros aleatórios para template_circuit. Esses dados são então utilizados para configurar os QuantumProgram objetos, que, por sua vez, são executados em uma QPU com a Executor primitiva. Cada um deles QuantumProgram pode conter vários itens, que você pode considerar como um par de template e samplex.

Consulte o tutorial “Hello samplomatic” para obter mais detalhes.

# Build template circuit and samplex for later use with the "Executor"
template_circuit, samplex = samplomatic.build(boxed_circuit)
# Set up postselection if it's been enabled
if post_selection_enabled:
    # Set up post selection PM (to add PS instructions)
    post_selection_pm = PassManager(
        [
            AddSpectatorMeasures(backend.coupling_map),
            AddPostSelectionMeasures(x_pulse_type="rx"),
        ]
    )
    final_template_circuit = post_selection_pm.run(template_circuit)
else:
    final_template_circuit = template_circuit

c.ii. Configure o QuantumProgram

num_randomizations = 4096
shots_per_randomization = 64
chunk_size = 256
# Set up QuantumProgram
program = QuantumProgram(shots=shots_per_randomization, noise_maps=refs_2_plm)

# no EM

# Collect up a dict of the other arguments that need to be bound to samplex_inputs
samplex_inputs = {
    f"noise_scales.{ref}": float(0) for ref in local_scales.keys()
}
samplex_inputs |= {"basis_changes": {"basis0": bases_canon[0]}}

# Convert samplex_inputs into a dict to pass to QuantumProgram
samplex_arguments = (
    samplex.inputs().bind(**samplex_inputs).make_broadcastable()
)

program.append_samplex_item(
    circuit=final_template_circuit,
    samplex=samplex,
    samplex_arguments=samplex_arguments,
    shape=(num_randomizations,),
    chunk_size=chunk_size,
)

# plain PEC

# Collect a dict of the other arguments that need to be bound to samplex_inputs
samplex_inputs = {
    f"noise_scales.{ref}": float(-1) for ref in local_scales.keys()
}
samplex_inputs |= {"basis_changes": {"basis0": bases_canon[0]}}

# Convert samplex_inputs into a dict to pass to QuantumProgram
samplex_arguments = (
    samplex.inputs().bind(**samplex_inputs).make_broadcastable()
)

program.append_samplex_item(
    circuit=final_template_circuit,
    samplex=samplex,
    samplex_arguments=samplex_arguments,
    shape=(num_randomizations,),
    chunk_size=chunk_size,
)

# PEC+SLC

# Collect a dict of the other arguments that need to be bound to samplex_inputs
samplex_inputs = {
    f"noise_scales.{ref}": float(-1) for ref in local_scales.keys()
}
samplex_inputs |= {"basis_changes": {"basis0": bases_canon[0]}}
samplex_inputs |= {"local_scales": local_scales}

# Convert samplex_inputs into a dict to pass to QuantumProgram
samplex_arguments = (
    samplex.inputs().bind(**samplex_inputs).make_broadcastable()
)

program.append_samplex_item(
    circuit=final_template_circuit,
    samplex=samplex,
    samplex_arguments=samplex_arguments,
    shape=(num_randomizations,),
    chunk_size=chunk_size,
)

c.iii. Executar o programa com a Executor primitiva

executor = Executor(backend)
job_exec = executor.run(program)
results_exec = job_exec.result()

Etapa 4: Pós-processamento

Ao calcularmos o valor esperado final de interesse utilizando executor_expectation_values, implementamos algumas técnicas de pós-processamento para ajudar a garantir que obtenhamos resultados da mais alta qualidade possível. Primeiro, aplicamos nosso método de extinção de erros de leitura com rotação (TREX), que leva em conta quaisquer erros que ocorram durante o processo de leitura. Em seguida, corrigimos os erros decorrentes do ruído não markoviano em nossos backends Heron utilizando um método de pós-seleção. Esse método mede os qubits ativos e os qubits espectadores, em seguida aplica uma rotação lenta a cada qubit e, por fim, realiza uma nova medição. Nos casos em que as duas medições não confirmam a inversão do qubit, conforme esperado, esses experimentos são descartados mediante a aplicação de um mask do PostSelector. No cálculo da máscara, é possível definir uma estratégia específica para filtrar com base em nós de qubit único ou arestas de observador vizinhas, o que pode influenciar tanto o número de tentativas filtradas quanto a qualidade dos resultados.

measurement_noise_map = noise_learner_result[2].to_pauli_lindblad_map()
trex_scale_factors = trex_factors(measurement_noise_map, reverser_virt)
post_selection_strategy = "node"
def post_process_conv(datum, steps=16, gamma=None, ps=False, trex=False):
    meas = datum["meas"]
    flips = datum["measurement_flips.meas"]
    signs = datum.get("pauli_signs", None)

    meas_basis_axis = None
    avg_axis = 0

    mask = None
    if ps and post_selection_enabled:
        # Post-select the results
        post_selector = PostSelector.from_circuit(
            circuit=final_template_circuit, coupling_map=backend.coupling_map
        )

        # Compute the ps mask for filtering results
        mask = post_selector.compute_mask(
            datum, strategy=post_selection_strategy
        )

        # Compute fraction of shots kept from post selection
        total_num_shots = num_randomizations * shots_per_randomization
        ps_ratio = np.sum(mask) * 100 / total_num_shots / len(bases_canon)
        print(
            f"With {post_selection_strategy}-based post selection ({ps_ratio:.1f}% of shots kept):"
        )

    results = []
    for i in range(steps, num_randomizations + 1, steps):
        # Compute mitigated expvals w/out post-selection
        res = executor_expectation_values(
            meas[:i],
            reverser_virt,
            meas_basis_axis,
            avg_axis=avg_axis,
            measurement_flips=flips[:i],
            pauli_signs=signs[:i] if signs is not None else None,
            postselect_mask=mask[:i] if mask is not None else None,
            rescale_factors=trex_scale_factors if trex else None,
            gamma_factor=gamma,
        )
        results.append(res[0])
    return results
gamma_pec = gamma_from_noisy_boxes(refs_2_plm, id_map)
gamma_slc = gamma_from_noisy_boxes(refs_2_plm, id_map, local_scales)
steps = 16
results = {}

for label, result_idx, gamma, use_ps, use_trex in [
    ("PEC", 1, gamma_pec, True, True),
    ("PEC+SLC", 2, gamma_slc, True, True),
    ("Unmitigated", 0, None, False, False),
]:
    res = post_process_conv(
        results_exec[result_idx],
        steps=steps,
        gamma=gamma,
        ps=use_ps,
        trex=use_trex,
    )
    results[label] = res

Output:

With node-based post selection (24.1% of shots kept):
With node-based post selection (24.2% of shots kept):

A partir da análise dos resultados experimentais, podemos comparar diretamente o comportamento das diferentes abordagens: PEC, PEC combinada com SLC e a linha de base sem medidas de mitigação. Alguns detalhes específicos a serem destacados:

  • O resultado não ajustado fica bem fora da faixa de viés de 10% (consulte 0.35 ) e não é afetado pelo número de randomizações.
  • Neste dispositivo, o PEC completo acarreta uma sobrecarga de amostragem de γ21.6×102\gamma^2 \approx 1.6\times10^2. Após grandes oscilações iniciais, a estimativa do PEC simples recupera o sinal e se estabiliza em torno de 1.1, ultrapassando ligeiramente o valor exato. Observe que a faixa de viés de 10% se aplica apenas ao estimador PEC+SLC: o PEC simples é, em princípio, insesgado; portanto, esse desvio residual não é coberto por uma garantia de viés, mas reflete, ao contrário, flutuações estatísticas e imperfeições no modelo de ruído aprendido, o que é razoável para um problema dessa magnitude.
  • O SLC reduz ainda mais a sobrecarga ~3.4-fold (para γ248\gamma^2 \approx 48, resultando em um limite de viés residual de cerca de 10%). O PEC+SLC também apresenta fortes flutuações com poucas randomizações, chegando inicialmente até mesmo à região não física, mas sua estimativa converge de forma constante para o valor exato, estabilizando-se próximo a 1.0, bem dentro da banda. Ela iguala ou supera a precisão do PEC simples a cerca de um terço do custo de amostragem, demonstrando a vantagem do sombreamento do cone de luz.
  • As barras de erro de ambas as estimativas mitigadas se estreitam à medida que as randomizações se acumulam; para um número fixo de randomizações, o estimador PEC+SLC apresenta a menor variância estatística, o que é consistente com seu menor valor de “ γ2\gamma^2 ”.

Observe que as taxas de ruído aprendidas (e, portanto, as sobrecargas e as estimativas mitigadas) dependem do backend e de sua calibração no momento da execução; portanto, executar novamente este notebook pode gerar valores quantitativamente diferentes.

fig, ax = plt.subplots(1, 1, figsize=(12, 6))

ax.axhline(1.0, color="black", label="Exact")
ax.fill_between(
    [-50, 4100], -10, 0, color="grey", alpha=0.25, label="Unphysical"
)
ax.fill_between([-50, 4100], 1, 10, color="grey", alpha=0.25)
ax.fill_between(
    [-50, 4100], 0.9, 1.1, color="red", alpha=0.25, label="10% bias"
)

for label, res in results.items():
    ax.errorbar(
        list(range(steps, num_randomizations + 1, steps)),
        [r[0] for r in res],
        yerr=[r[1] for r in res],
        alpha=0.75,
        marker="o",
        linestyle="",
        markerfacecolor="none",
        label=label,
    )

ax.set_ylabel(r"$\langle X_{6}Z_{13}\rangle$")
ax.set_xlabel("# randomizations")
ax.grid()

ax.legend(ncols=2)
ax.set_ylim([-0.1, 2.0])
ax.set_xlim([-50, 4100])

Output:

(-50.0, 4100.0)
Output of the previous code cell

Próximas etapas

Se você achou este trabalho interessante, talvez se interesse pelo material a seguir:

Esta página foi útil?
Relate um bug, erro de digitação ou solicite conteúdo no GitHub.