Cancelamento probabilístico de erros com cones de luz sombreados
Contexto
Este tutorial demonstra como mitigar erros usando o addon Shaded lightcone (SLC). Este addon é uma evolução da técnica de cancelamento probabilístico de erros (PEC), na qual o usuário aprende o ruído de camadas únicas em um circuito e, em seguida, cancela o ruído aplicando portas de qubit único e técnicas de pós-processamento. Comparado a outros métodos, o PEC oferece limites mais robustos sobre o viés do resultado mitigado, mas tende a sofrer com uma sobrecarga maior em termos de tempo de QPU. Durante o PEC, para compensar a atenuação do valor esperado pelo ruído, o resultado médio é reescalonado por um fator de , onde é a taxa de ruído aprendida do erro de Pauli na camada do circuito. Esse reescalonamento aumenta a variância por um fator de e, portanto, também multiplica o número de execuções de circuito necessárias na QPU por , o que chamamos de custo de amostragem ou sobrecarga de amostragem. Como cresce exponencialmente, o PEC é frequentemente limitado a circuitos rasos ou com poucos qubits. Saiba mais sobre o PEC em Probabilistic error cancellation with sparse Pauli-Lindblad models on noisy quantum processors.
Se conseguirmos identificar erros que não precisam ser mitigados, podemos diminuir esse custo de amostragem exponencialmente. Um primeiro passo nessa direção é implementar a mitigação de erros com consciência local, que usa um "cone de luz" convencional rapidamente computável para reduzir a sobrecarga do PEC, limitando a sensibilidade de um observável aos erros ao longo do circuito, estendendo 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 diminui a sobrecarga de amostragem, em alguns casos substancialmente, sem introduzir viés adicional. Em particular, para medir um observável local de um circuito de profundidade fixa, a sobrecarga de amostragem necessária eventualmente atinge um patamar ao escalar o número de qubits no circuito (veja a Fig. 2b em Locality and Error Mitigation of Quantum Circuits.)
Os cones de luz sombreados (SLC) vão além, usando simulações clássicas para limitar de forma mais rigorosa a sensibilidade aos erros ao longo do circuito. Isso troca algum tempo de QPU por tempo de CPU e reduz a sobrecarga de amostragem necessária para renormalizar o viés. Em vez de um corte rígido, cada erro potencial no circuito recebe uma "sombra" graduada que limita superiormente a suscetibilidade do observável a esse erro. Essa caracterização refinada permite aplicações mais eficientes e direcionadas do PEC com variância reduzida, ao mesmo tempo em que dá ao usuário a capacidade de ajustar de forma controlável o viés na estimativa do observável. Veja Lightcone shading for classically accelerated quantum error mitigation para mais detalhes.
Nosso fluxo de trabalho para o addon SLC aproveita o novo framework Samplomatic e Executor, permitindo aos usuários ter um controle mais modular sobre as configurações de execução para supressão e mitigação de erros, mantendo a facilidade de uso para usuários avançados. Para uma compreensão mais profunda dos benefícios desse framework e de seus recursos gerais, consulte o tutorial Hello samplomatic.
Fluxo de trabalho para sombreamento de cone de luz, aprendizado de ruído e injeção de antirruído
Para modelar o ruído da QPU, escolhemos usar um modelo de ruído Pauli-Lindblad esparso com taxas de erro de Pauli de 1 e 2 qubits, geradas localmente em cada qubit e aresta do dispositivo. Com essa escolha, o fluxo de trabalho de mitigação de erros SLC apresentado neste tutorial é o seguinte:
a. CPU — Limitar o impacto por erro de erros de Pauli de 1 e 2 qubits
- Propagação direta (limitar o efeito sobre o observável). Propagar cada erro até o final do circuito e calcular seu comutador com o observável.
- Truncar os termos do operador durante a evolução para manter a computação tratável.
- Apertar ainda mais esses limites por uma propagação reversa solta do observável com base em limites de velocidade quânticos.
- Propagação reversa (limitar o efeito sobre o estado inicial). Propagar cada erro até o início do circuito e calcular seu comutador com o estado inicial.
b. QPU — Aprender taxas de ruído. Use o NoiseLearner para estimar as taxas do modelo de ruído Pauli-Lindblad.
c. CPU — Priorizar a mitigação
- Atualizar os limites mesclados com as taxas de ruído aprendidas. Combinar os limites de propagação direta e reversa que foram previamente computados e atualizá-los com as taxas de ruído aprendidas.
- Classificar os componentes de ruído a serem mitigados usando os limites computados e as taxas aprendidas. Priorizar cada possível erro de ruído com base em seu impacto estimado no viés e na despesa associada para correção.
d. QPU — Inserir antirruído e executar. Executar o circuito de interesse com antirruído (ruído inverso) especificado usando anotações Box.
e. CPU — Estimar observável. Calcular o valor esperado, aplicando pós-seleção baseada em medição para reduzir o impacto do ruído não-Markoviano.
Visão geral do aprendizado de ruído
O aprendizado de ruído é uma etapa comum em vários métodos de mitigação de erros, realizada pelo NoiseLearner, e pode ser visto em nosso tutorial de mitigação de erros PEA, bem como em nosso tutorial de absorção de ruído propagado (PNA). No NoiseLearnerV3, um usuário pode identificar especificamente as camadas de ruído a serem aprendidas como objetos CircuitInstruction, o que permite aos usuários computar os limites de ruído SLC desejados para cada camada da maneira descrita acima. O modelo Pauli-Lindblad aprendido fornece coeficientes a serem usados na priorização PEC-SLC. A maneira como as portas são coletadas em camadas pode ser determinada usando as funções utilitárias generate_boxing_pass_manager e unique_2q_instructions, e em seguida alimentadas na função utilitária SLC generate_noise_model_paulis, conforme descrito na Etapa 2 abaixo.
| Parte 1 | Parte 2 | Parte 3 |
|---|---|---|
| Pauli-twirl em camadas de portas de dois qubits | Repetir pares de identidade de camadas e aprender o ruído | Derivar uma fidelidade (erro para cada canal de ruído) |
![]() | ![]() |
Visão geral do pós-processamento
Após executar no hardware quântico usando o framework Samplomatic e Executor, convertemos nossas medições de bitstring no valor de observável desejado. No caso do nosso circuito de Ising espelhado, idealmente obteremos um observável medido de 1, pois todos os qubits idealmente devem retornar ao seu ponto de partida . Ao calcular o valor do observável com nossa função expectation_values, aplicaremos algumas técnicas de pós-processamento que reduzem o impacto do ruído. Isso inclui a remoção de shots afetados por ruído não-Markoviano, mitigação de erros de leitura, bem como a contabilização de detalhes da nossa implementação PEC. Os detalhes são discutidos na Etapa 4 abaixo.
Requisitos
Antes de iniciar este tutorial, certifique-se de que você tenha os seguintes pacotes instalados:
- Qiskit IBM Runtime com a primitiva Executor (
pip install "qiskit-ibm-runtime @ git+https://github.com/Qiskit/qiskit-ibm-runtime.git") - Qiskit addon Shaded lightcone 0.1 (
pip install "qiskit-addon-slc~=0.1.0") - Qiskit addon utils (
pip install "qiskit-addon-utils~=0.3.0") - Samplomatic v0.16 ou mais recente (
pip install samplomatic) - Suporte a visualização do Qiskit (
pip install "qiskit[visualization]")
Etapa 0. Configuração
Primeiro, importe os pacotes e funções necessários para executar este notebook com sucesso.
# Added by doQumentation — required packages for this notebook
!pip install -q matplotlib numpy qiskit qiskit-addon-slc qiskit-addon-utils qiskit-ibm-runtime samplomatic
import logging
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(module)s %(message)s")
# Setting this value prevents itertools.starmap deadlock on UNIX systems
from multiprocessing import set_start_method
set_start_method("spawn")
# Needed to prevent PySCF from parallelizing internally (SLC only)
%set_env OMP_NUM_THREADS=1
env: OMP_NUM_THREADS=1
import pickle
import numpy as np
import samplomatic
from matplotlib import pyplot as plt
from qiskit import QuantumCircuit
from qiskit.quantum_info import SparsePauliOp
from qiskit.transpiler import PassManager, generate_preset_pass_manager
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
from qiskit_addon_utils.exp_vals.expectation_values import executor_expectation_values
from qiskit_addon_utils.exp_vals.measurement_bases import get_measurement_bases
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_ibm_runtime import Executor, QiskitRuntimeService, QuantumProgram
from qiskit_ibm_runtime.noise_learner_v3 import NoiseLearnerV3
from qiskit_ibm_runtime.options import NoiseLearnerV3Options
from samplomatic.transpiler import generate_boxing_pass_manager
from samplomatic.utils import find_unique_box_instructions
Etapa 1. Mapear o problema
Para facilitar a demonstração, selecionamos uma cadeia de Ising espelhada em 1D. A cadeia de Ising 1D fornece uma estrutura de circuito densa e agradável, o que é conveniente para mostrar implementações de PEC. Um circuito espelhado torna direto saber o resultado esperado (ou seja, devemos medir um observável de 1).
Além disso, queremos executar um circuito espelhado, então para cada porta na segunda metade do circuito, é necessária uma porta inversa na primeira metade. Como o observável medido tem medições em base não-Z, e o executor leva em conta a base desejada no final do circuito, fornecemos uma função prepare_basis que insere as portas apropriadas no início do circuito espelhado. Esse detalhe é específico para nossa demonstração de circuito espelhado. A função get_measurement_bases permite identificar facilmente quais portas são necessárias e onde anexá-las, bem como acompanhar as sutilezas do índice de qubits decorrentes das convenções na anotação box, conforme discutido na seção "Preparar medições de bases canônicas".
num_qubits = 20
target_obs_sparse = [("XZ", [6, 13], 1.0)]
observable = SparsePauliOp.from_sparse_list(target_obs_sparse, num_qubits=num_qubits)
bases_virt, reverser_virt = get_measurement_bases(observable)
num_trotter_steps = 10
rx_angle = np.pi / 4
def construct_ising_circuit(
num_qubits: int, num_trotter_steps: int, rx_angle: float, barrier: bool = True
) -> QuantumCircuit:
circuit = QuantumCircuit(num_qubits)
for _step in range(num_trotter_steps):
circuit.rx(rx_angle, range(num_qubits))
if barrier:
circuit.barrier()
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)
if barrier:
circuit.barrier()
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 in {0, 1}:
continue
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, *, inverse_first: bool = False) -> QuantumCircuit:
mirror_circ = circuit.copy_empty_like()
mirror_circ.compose(circuit.inverse() if inverse_first else circuit, inplace=True)
mirror_circ.barrier()
mirror_circ.compose(circuit if inverse_first else circuit.inverse(), inplace=True)
mirror_circ.measure_active()
return mirror_circ
# Instantiate circuit
circuit = construct_ising_circuit(num_qubits, num_trotter_steps, rx_angle, barrier=False)
mirrored_circuit = mirror_circuit(circuit, inverse_first=True)
mirrored_circuit = prepare_basis(mirrored_circuit, bases_virt[0])
mirrored_circuit.draw("mpl", fold=-1, scale=0.3, idle_wires=False, measure_arrows=False)

Etapa 2. Otimizar
Vamos otimizar os detalhes associados ao circuito a ser executado, ao observável a ser medido e aos parâmetros de aprendizado de ruído. Como ponto de partida, garantimos que instanciamos um backend com portas fracionárias ativadas como uma opção. Essas portas fracionárias permitirão maior sensibilidade em alguns dos nossos filtros de pós-seleção.
token = "<YOUR_TOKEN>"
instance = "<YOUR_INSTANCE>"
# This is used to retrieve shared results
shared_service = QiskitRuntimeService(
channel="ibm_quantum_platform",
token=token,
instance=instance,
)
# This is used to run on real hardware
service = service = QiskitRuntimeService()
qiskit_runtime_service._discover_account:WARNING:2025-11-10 11:19:40,108: Loading account with the given token. A saved account will not be used.
backend = service.backend("ibm_kingston", use_fractional_gates=True)
Primeiro, transpilaremos nosso circuito para instruções ISA, conforme exigido para execução em nossas QPUs. Para os dados coletados neste experimento, selecionamos manualmente nossos qubits com base na avaliação da cadeia de mais alta qualidade.
layout = [44, 45, 46, 47, 57, 67, 68, 69, 78, 89, 88, 87, 97, 107, 106, 105, 104, 103, 96, 83]
isa_pm = generate_preset_pass_manager(backend=backend, initial_layout=layout, optimization_level=0)
isa_circuit = isa_pm.run(mirrored_circuit)
assert isa_circuit.layout.final_index_layout() == layout
isa_observable = observable.apply_layout(layout, num_qubits=isa_circuit.num_qubits)
2025-11-10 11:19:57,810 INFO base_tasks Pass: ContainsInstruction - 0.00715 (ms)
2025-11-10 11:19:57,811 INFO base_tasks Pass: UnitarySynthesis - 0.00525 (ms)
2025-11-10 11:19:57,811 INFO base_tasks Pass: HighLevelSynthesis - 0.02599 (ms)
2025-11-10 11:19:57,811 INFO base_tasks Pass: BasisTranslator - 0.09131 (ms)
2025-11-10 11:19:57,811 INFO base_tasks Pass: SetLayout - 0.02623 (ms)
2025-11-10 11:19:57,812 INFO base_tasks Pass: FullAncillaAllocation - 0.14400 (ms)
2025-11-10 11:19:57,812 INFO base_tasks Pass: EnlargeWithAncilla - 0.06318 (ms)
2025-11-10 11:19:57,813 INFO base_tasks Pass: ApplyLayout - 0.29802 (ms)
2025-11-10 11:19:57,813 INFO base_tasks Pass: CheckMap - 0.07820 (ms)
2025-11-10 11:19:57,814 INFO base_tasks Pass: FilterOpNodes - 0.33283 (ms)
2025-11-10 11:19:57,814 INFO base_tasks Pass: UnitarySynthesis - 0.00691 (ms)
2025-11-10 11:19:57,814 INFO base_tasks Pass: HighLevelSynthesis - 0.13208 (ms)
2025-11-10 11:19:57,816 INFO base_tasks Pass: BasisTranslator - 1.00303 (ms)
2025-11-10 11:19:57,818 INFO base_tasks Pass: FoldRzzAngle - 1.78719 (ms)
2025-11-10 11:19:57,818 INFO base_tasks Pass: ContainsInstruction - 0.00691 (ms)
2025-11-10 11:19:57,818 INFO base_tasks Pass: InstructionDurationCheck - 0.00405 (ms)
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
)

Encaixotar o circuito
Para facilitar a implementação, utilizaremos o passe de transpilação generate_boxing_pass_manager, que coloca as instruções do circuito em caixas anotadas. Essas caixas indicam claramente onde, no caso do PEC, o antirruído deve ser injetado no circuito. Para detalhes sobre as configurações, consulte a documentação do Samplomatic.
Observe que o fluxo de trabalho SLC usa inject_noise_strategy="individual_modification" posteriormente no processo, pois isso nos permite identificar exclusivamente cada BoxOp no circuito.
A função find_unique_box_instructions itera pelo circuito encaixotado fornecido e identifica aqueles que possuem camadas 2Q ou medições únicas, com a finalidade de aprendizado 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_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
)
2025-11-10 11:20:01,088 INFO base_tasks Pass: RemoveBarriers - 0.02289 (ms)
2025-11-10 11:20:01,100 INFO base_tasks Pass: GroupGatesIntoBoxes - 12.38990 (ms)
2025-11-10 11:20:01,101 INFO base_tasks Pass: GroupMeasIntoBoxes - 0.47898 (ms)
2025-11-10 11:20:01,104 INFO base_tasks Pass: AddTerminalRightDressedBoxes - 2.88177 (ms)
2025-11-10 11:20:01,111 INFO base_tasks Pass: AddInjectNoise - 6.66904 (ms)
boxed_circuit.draw(
"mpl", fold=-1, scale=0.3, idle_wires=False, wire_order=wire_order, measure_arrows=False
)

Preparar medições de bases canônicas
Devido à forma como os qubits são rotulados ao identificar camadas 2Q únicas, é preciso ter um cuidado especial para acompanhar a ordenação dos qubits. Abaixo, introduzimos a noção de canonical_qubits como um meio de atualizar adequadamente a ordenação dos qubits ao fornecê-la ao executor, em decorrência de como a ordem dos qubits é capturada ao encaixotar circuitos e encontrar instruções únicas. Consulte a documentação Convenção de ordenação de qubits para 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 de cone de luz, aprendizado de ruído e injeção de antirruído
Nota: Para a implementação do SLC-PEC neste tutorial, executamos as computações de limites SLC antes de o aprendizado de ruído ser concluído, de modo que o circuito a ser mitigado seja executado o mais próximo possível no tempo do modelo de ruído aprendido. Em princípio, esse fluxo de trabalho pode ser aprimorado para ser executado simultaneamente. Ou seja, um trabalho de aprendizado de ruído é executado enquanto, em paralelo, os limites de ruído são estimados. Para um circuito quântico arbitrário, a computação de limites de ruído pode escalar com uma dependência fracamente exponencial. Por isso, pode ser prudente usar execução paralelizada ao tentar maximizar a eficiência do fluxo de trabalho. Para isso, demonstramos brevemente isso incluindo recursos baseados em cluster (128 threads) e mostrando como você pode obter um conjunto mais refinado de limites para um circuito fornecido quando restrito a limites de tempo de computação iguais, em comparação com nosso laptop (8 threads). Além disso, embora não implementado neste fluxo de trabalho, você pode paralelizar as execuções de QPU para aprendizado de ruído e computações de limites de ruído para alcançar o fluxo de trabalho mais eficiente.
Prever os Paulis do modelo de ruído a serem aprendidos
A função generate_noise_model_paulis percorre cada camada encaixotada do circuito fornecido e gera todos os termos de Pauli relevantes de peso um e peso dois, levando em consideração a conectividade dos qubits do circuito e selecionando os termos relevantes para os nós e arestas ativos. Esses termos são então usados para computar os limites de ruído de propagação direta e reversa.
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. Computar e apertar os limites de propagação direta
A função compute_forward_bounds avalia as relações de comutação entre as portas em cada camada e os termos de Pauli gerados acima em termos de como os erros de propagação direta afetam o observável desejado . Para portas que comutam com os termos de Pauli, nada é feito. Para portas Clifford, elas são empurradas em direção ao início do circuito. Para portas não-Clifford, aproximamos sua influência sobre os observáveis-alvo para serem posteriormente priorizados para cancelamento de ruído (após todos os limites terem sido mesclados). Esse limite é alcançado primeiro aplicando a norma 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 mais frouxo que usa a desigualdade triangular.
Recursos em nível de laptop
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,
)
2025-11-10 11:20:04,344 INFO forward Evolving Pauli error terms forwards through the circuit.
2025-11-10 11:20:04,344 INFO forward Modelling errors as though they happen *after* each noise layer.
2025-11-10 11:20:04,345 INFO remove_measure Removing ANY Measure operations from the provided circuit!
2025-11-10 11:20:04,453 INFO circuit_iter Noisy box 'm39'
2025-11-10 11:20:05,254 INFO circuit_iter Noisy box 'm38'
2025-11-10 11:20:05,304 INFO circuit_iter Noisy box 'm37'
2025-11-10 11:20:05,382 INFO circuit_iter Noisy box 'm36'
2025-11-10 11:20:05,467 INFO circuit_iter Noisy box 'm35'
2025-11-10 11:20:05,580 INFO circuit_iter Noisy box 'm34'
2025-11-10 11:20:05,705 INFO circuit_iter Noisy box 'm33'
2025-11-10 11:20:05,857 INFO circuit_iter Noisy box 'm32'
2025-11-10 11:20:06,034 INFO circuit_iter Noisy box 'm31'
2025-11-10 11:20:06,221 INFO circuit_iter Noisy box 'm30'
2025-11-10 11:20:06,449 INFO circuit_iter Noisy box 'm29'
2025-11-10 11:20:06,724 INFO circuit_iter Noisy box 'm28'
2025-11-10 11:20:07,628 INFO circuit_iter Noisy box 'm27'
2025-11-10 11:20:09,110 INFO circuit_iter Noisy box 'm26'
2025-11-10 11:20:11,696 INFO circuit_iter Noisy box 'm25'
2025-11-10 11:20:16,100 INFO circuit_iter Noisy box 'm24'
2025-11-10 11:20:21,781 INFO circuit_iter Noisy box 'm23'
2025-11-10 11:20:30,244 INFO circuit_iter Noisy box 'm22'
2025-11-10 11:20:40,416 INFO circuit_iter Noisy box 'm21'
2025-11-10 11:20:53,437 INFO circuit_iter Noisy box 'm20'
2025-11-10 11:21:06,038 INFO circuit_iter Noisy box 'm19'
2025-11-10 11:21:06,038 WARNING commutator_bounds Bounds computation timed out.
2025-11-10 11:21:06,039 INFO circuit_iter Noisy box 'm18'
2025-11-10 11:21:06,039 INFO circuit_iter Noisy box 'm17'
2025-11-10 11:21:06,039 INFO circuit_iter Noisy box 'm16'
2025-11-10 11:21:06,040 INFO circuit_iter Noisy box 'm15'
2025-11-10 11:21:06,040 INFO circuit_iter Noisy box 'm14'
2025-11-10 11:21:06,040 INFO circuit_iter Noisy box 'm13'
2025-11-10 11:21:06,040 INFO circuit_iter Noisy box 'm12'
2025-11-10 11:21:06,041 INFO circuit_iter Noisy box 'm11'
2025-11-10 11:21:06,041 INFO circuit_iter Noisy box 'm10'
2025-11-10 11:21:06,041 INFO circuit_iter Noisy box 'm9'
2025-11-10 11:21:06,042 INFO circuit_iter Noisy box 'm8'
2025-11-10 11:21:06,042 INFO circuit_iter Noisy box 'm7'
2025-11-10 11:21:06,042 INFO circuit_iter Noisy box 'm6'
2025-11-10 11:21:06,042 INFO circuit_iter Noisy box 'm5'
2025-11-10 11:21:06,043 INFO circuit_iter Noisy box 'm4'
2025-11-10 11:21:06,043 INFO circuit_iter Noisy box 'm3'
2025-11-10 11:21:06,043 INFO circuit_iter Noisy box 'm2'
2025-11-10 11:21:06,043 INFO circuit_iter Noisy box 'm1'
2025-11-10 11:21:06,044 INFO circuit_iter Noisy box 'm0'
Visualizar o SLC para inspeção manual
Você pode 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 deste problema de evolução temporal do Hamiltoniano de Ising chutado e também aparecem no artigo Lightcone Shading for Classically Accelerated Quantum Error Mitigation, com várias características reveladoras:
- Podemos distinguir claramente os dois cones decorrentes dos dois Paulis não-identidade no observável.
- Podemos ver que a medição X no qubit 6 comuta com o erro X na camada mais à direita.
- Podemos ver que o Pauli Z no qubit 13 comuta com o erro Z na camada mais à direita.
- Quando atingimos o tempo limite especificado acima, as camadas restantes à esquerda sã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,
)
)



b. Computar e apertar os limites de propagação direta
Em seguida, apertamos os limites usando a função tighten_with_speed_limit, que rastreia como o observável se espalha para trás através do circuito e usa esse espalhamento para colocar limites superiores no efeito de cada operador de ruído, tomando o menor entre o limite de propagação direta recém-computado e o limite de propagação reversa.
forward_bounds_tighter = tighten_with_speed_limit(
forward_bounds, boxed_circuit, noise_model_paulis, isa_observable
)
2025-11-10 11:21:08,270 INFO speed_limit Tighting bounds using information propagation speed limits
2025-11-10 11:21:08,270 INFO speed_limit Modelling errors as though they happen *after* each noise layer.
2025-11-10 11:21:08,298 INFO remove_measure Removing ANY Measure operations from the provided circuit!
2025-11-10 11:21:08,310 INFO circuit_iter Noisy box 'm39'
2025-11-10 11:21:08,314 INFO circuit_iter Noisy box 'm38'
2025-11-10 11:21:08,317 INFO circuit_iter Noisy box 'm37'
2025-11-10 11:21:08,319 INFO circuit_iter Noisy box 'm36'
2025-11-10 11:21:08,323 INFO circuit_iter Noisy box 'm35'
2025-11-10 11:21:08,325 INFO circuit_iter Noisy box 'm34'
2025-11-10 11:21:08,328 INFO circuit_iter Noisy box 'm33'
2025-11-10 11:21:08,330 INFO circuit_iter Noisy box 'm32'
2025-11-10 11:21:08,334 INFO circuit_iter Noisy box 'm31'
2025-11-10 11:21:08,336 INFO circuit_iter Noisy box 'm30'
2025-11-10 11:21:08,338 INFO circuit_iter Noisy box 'm29'
2025-11-10 11:21:08,340 INFO circuit_iter Noisy box 'm28'
2025-11-10 11:21:08,344 INFO circuit_iter Noisy box 'm27'
2025-11-10 11:21:08,346 INFO circuit_iter Noisy box 'm26'
2025-11-10 11:21:08,349 INFO circuit_iter Noisy box 'm25'
2025-11-10 11:21:08,351 INFO circuit_iter Noisy box 'm24'
2025-11-10 11:21:08,355 INFO circuit_iter Noisy box 'm23'
2025-11-10 11:21:08,357 INFO circuit_iter Noisy box 'm22'
2025-11-10 11:21:08,360 INFO circuit_iter Noisy box 'm21'
2025-11-10 11:21:08,362 INFO circuit_iter Noisy box 'm20'
2025-11-10 11:21:08,367 INFO circuit_iter Noisy box 'm19'
2025-11-10 11:21:08,369 INFO circuit_iter Noisy box 'm18'
2025-11-10 11:21:08,372 INFO circuit_iter Noisy box 'm17'
2025-11-10 11:21:08,375 INFO circuit_iter Noisy box 'm16'
2025-11-10 11:21:08,378 INFO circuit_iter Noisy box 'm15'
2025-11-10 11:21:08,380 INFO circuit_iter Noisy box 'm14'
2025-11-10 11:21:08,383 INFO circuit_iter Noisy box 'm13'
2025-11-10 11:21:08,386 INFO circuit_iter Noisy box 'm12'
2025-11-10 11:21:08,389 INFO circuit_iter Noisy box 'm11'
2025-11-10 11:21:08,391 INFO circuit_iter Noisy box 'm10'
2025-11-10 11:21:08,394 INFO circuit_iter Noisy box 'm9'
2025-11-10 11:21:08,396 INFO circuit_iter Noisy box 'm8'
2025-11-10 11:21:08,399 INFO circuit_iter Noisy box 'm7'
2025-11-10 11:21:08,401 INFO circuit_iter Noisy box 'm6'
2025-11-10 11:21:08,404 INFO circuit_iter Noisy box 'm5'
2025-11-10 11:21:08,406 INFO circuit_iter Noisy box 'm4'
2025-11-10 11:21:08,410 INFO circuit_iter Noisy box 'm3'
2025-11-10 11:21:08,412 INFO circuit_iter Noisy box 'm2'
2025-11-10 11:21:08,415 INFO circuit_iter Noisy box 'm1'
2025-11-10 11:21:08,417 INFO circuit_iter Noisy box 'm0'
Visualizar o SLC para inspeção manual
Podemos apertar ainda mais os limites contabilizando a limitação do cone de luz. Em princípio, isso nos dá uma transição mais suave dos limites computados para os limites triviais estabelecidos após o tempo limite ter sido atingido. Aqui, o efeito não é tão visível porque os cones de luz já alcançaram a borda do circuito.
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,
)
)



c. Computar limites de propagação reversa
Esta parte da predição de ruído avalia como um erro em uma camada específica pode afetar o estado de entrada . A função compute_backward_bounds primeiro inverte o circuito, remove as portas de medição e, em seguida, prossegue com uma análise semelhante à que foi feita para as computações de limites de propagação direta.
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,
)
2025-11-10 11:21:10,666 INFO backward Evolving Pauli error terms backwards through the circuit.
2025-11-10 11:21:10,666 INFO backward Modelling errors as though they happen *after* each noise layer.
2025-11-10 11:21:10,667 INFO remove_measure Removing ANY Measure operations from the provided circuit!
2025-11-10 11:21:10,774 INFO circuit_iter Noisy box 'm0'
2025-11-10 11:21:11,640 INFO circuit_iter Noisy box 'm1'
2025-11-10 11:21:11,681 INFO circuit_iter Noisy box 'm2'
2025-11-10 11:21:11,867 INFO circuit_iter Noisy box 'm3'
2025-11-10 11:21:12,078 INFO circuit_iter Noisy box 'm4'
2025-11-10 11:21:12,329 INFO circuit_iter Noisy box 'm5'
2025-11-10 11:21:12,637 INFO circuit_iter Noisy box 'm6'
2025-11-10 11:21:13,110 INFO circuit_iter Noisy box 'm7'
2025-11-10 11:21:13,705 INFO circuit_iter Noisy box 'm8'
2025-11-10 11:21:14,384 INFO circuit_iter Noisy box 'm9'
2025-11-10 11:21:15,213 INFO circuit_iter Noisy box 'm10'
2025-11-10 11:21:15,946 INFO circuit_iter Noisy box 'm11'
2025-11-10 11:21:16,754 INFO circuit_iter Noisy box 'm12'
2025-11-10 11:21:17,557 INFO circuit_iter Noisy box 'm13'
2025-11-10 11:21:18,447 INFO circuit_iter Noisy box 'm14'
2025-11-10 11:21:19,453 INFO circuit_iter Noisy box 'm15'
2025-11-10 11:21:20,472 INFO circuit_iter Noisy box 'm16'
2025-11-10 11:21:21,479 INFO circuit_iter Noisy box 'm17'
2025-11-10 11:21:22,660 INFO circuit_iter Noisy box 'm18'
2025-11-10 11:21:23,705 INFO circuit_iter Noisy box 'm19'
2025-11-10 11:21:24,849 INFO circuit_iter Noisy box 'm20'
2025-11-10 11:21:26,030 INFO circuit_iter Noisy box 'm21'
2025-11-10 11:21:27,111 INFO circuit_iter Noisy box 'm22'
2025-11-10 11:21:28,354 INFO circuit_iter Noisy box 'm23'
2025-11-10 11:21:29,554 INFO circuit_iter Noisy box 'm24'
2025-11-10 11:21:30,897 INFO circuit_iter Noisy box 'm25'
2025-11-10 11:21:32,113 INFO circuit_iter Noisy box 'm26'
2025-11-10 11:21:33,622 INFO circuit_iter Noisy box 'm27'
2025-11-10 11:21:34,962 INFO circuit_iter Noisy box 'm28'
2025-11-10 11:21:36,504 INFO circuit_iter Noisy box 'm29'
2025-11-10 11:21:38,021 INFO circuit_iter Noisy box 'm30'
2025-11-10 11:21:39,750 INFO circuit_iter Noisy box 'm31'
2025-11-10 11:21:41,237 INFO circuit_iter Noisy box 'm32'
2025-11-10 11:21:42,974 INFO circuit_iter Noisy box 'm33'
2025-11-10 11:21:44,527 INFO circuit_iter Noisy box 'm34'
2025-11-10 11:21:46,535 INFO circuit_iter Noisy box 'm35'
2025-11-10 11:21:48,152 INFO circuit_iter Noisy box 'm36'
2025-11-10 11:21:50,074 INFO circuit_iter Noisy box 'm37'
2025-11-10 11:21:51,814 INFO circuit_iter Noisy box 'm38'
2025-11-10 11:21:53,943 INFO circuit_iter Noisy box 'm39'
Visualizar o SLC para inspeção manual
Ao computar os limites de propagação reversa, podemos ver como a estrutura do estado inicial governa o comportamento inicial da propagação de erros:
- Podemos ver claramente como os erros Z inicialmente comutam com o estado inicial |0⟩.
- Apenas no qubit 6, onde inicializamos o autoestado +1 da base X, um erro Z deixa de comutar, enquanto um erro X 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,
)
)



Pré-visualizar os limites mesclados sem taxas de ruído aprendidas
A função merged_bounds determina o ponto no circuito onde a mudança dos limites de propagação reversa para os limites de propagação direta minimiza o viés total estimado sobre o observável desejado. Esse viés é calculado como a soma das contribuições do limite de propagação reversa para todas as localizações de ruído antes desse ponto, mais as contribuições do limite de propagação direta para todas as localizações de ruído depois dele. Atualmente, isso é feito uniformemente para todos os qubits.
Nota Importante: O ponto para mudar dos limites de propagação direta para os de propagação reversa depende das taxas de ruído aprendidas.
merged_bounds = merge_bounds(
boxed_circuit,
forward_bounds_tighter,
backward_bounds,
noise_model_rates,
)
2025-11-10 11:21:58,304 WARNING merge Missing noise rates. Partitioning backward/forward commutator bounds by assuming uniform error rates.
2025-11-10 11:21:58,305 WARNING merge Optimal spacetime partitioning not implemented!Just partitioning list of noisy boxes.
2025-11-10 11:21:58,305 INFO merge Determined Box idx for partitioning to be 20.
Visualizar o SLC para inspeção manual
Após mesclar os limites de propagação reversa e os limites de propagação direta apertados, o comportamento dos SLCs combinados torna-se claro:
- A função acima nos diz que uma partição é escolhida na qual ocorre a mudança dos limites de propagação reversa para os limites de propagação direta apertados.
- Podemos ver abaixo que os SLCs agora contêm limites parciais de propagação reversa e limites parciais de propagação direta apertados.
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,
)
)



Recursos em nível de cluster
Aqui, demonstramos como utilizar 128 threads em um cluster nos permite propagar através de uma porção mais substancial deste circuito maior quando limitados ao mesmo tempo de computação que nosso laptop.
with open("exp_data/merged_bounds_cluster.pickle", "rb") as file:
merged_bounds_cluster = pickle.load(file)
for p in "XYZ":
display(
draw_shaded_lightcone(
boxed_circuit,
merged_bounds_cluster,
noise_model_paulis,
pauli_filter=p,
scale=0.15,
fold=-1,
idle_wires=False,
wire_order=wire_order,
measure_arrows=False,
)
)



Etapa 3. Executar
Nesta seção, iniciamos a parte do fluxo de trabalho que utiliza um dispositivo quântico real. Para este método de mitigação de erros baseado em aprendizado, há duas etapas:
- Aprenda o ruído usando
NoiseLeanerV3. - Execute um circuito de mitigação de erros com a nova estrutura Samplomatic e Estimator.
Com os limites de erro do nosso circuito quântico, devemos aprender as taxas de ruído associadas para priorizar nosso orçamento de erros, determinar o overhead de amostragem e executar em uma QPU. Além disso, com essas informações sobre a taxa de ruído, também podemos destacar como, ao utilizar os fortes recursos de computação do nosso cluster, reduzimos o overhead de amostragem mantendo o mesmo viés residual.
a. Aprender as taxas de ruído
O noise learner permite a caracterização dos processos de ruído que afetam as portas em um ou mais circuitos de interesse, com base no modelo de ruído Pauli-Lindblad descrito no artigo Probabilistic error cancellation with sparse Pauli-Lindblad models on noisy quantum processors. O método run() inicia uma tarefa de aprendizado de ruído para as camadas únicas de 2 qubits fornecidas, com base nas opções especificadas na configuração do noise learner. Nessas opções, você pode ajustar a estratégia de Pauli-twirling, que ajuda a garantir que o hardware seja bem descrito pelo modelo de ruído Pauli-Lindblad.
Os detalhes do seu modelo de ruído correm o risco de mudar com o tempo. Por isso, definimos um parâmetro para garantir que o modelo de ruído aprendido seja recalculado para experimentos com mais de quatro horas. Esta é uma regra prática aproximada e deve ser considerada cuidadosamente ao aplicá-la ao seu próprio trabalho.
post_selection_enabled = True
load_cached_noise_results = True
noise_learner_options = NoiseLearnerV3Options(
num_randomizations=64,
shots_per_randomization=128,
layer_pair_depths=[1, 2, 4, 8, 12, 16, 24, 32, 40, 48],
post_selection={
"enable": post_selection_enabled,
"strategy": "edge",
"x_pulse_type": "rx",
},
)
noise_learner = NoiseLearnerV3(backend, noise_learner_options)
if load_cached_noise_results:
noise_learner_job = shared_service.job("d46ssf71gh7s7398k9a0")
else:
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}"
)
Minimum fraction of shots kept for noise learning experiments: 0.58
# Get a dict mapping InjectNoise.ref to QubitSparsePaulilist
refs_2_plm = noise_learner_result.to_dict(unique_2q_instructions, require_refs=False)
b.i. Atualizar limites mesclados com 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 o maior impacto na minimização do viés.
merged_bounds = merge_bounds(
boxed_circuit,
forward_bounds_tighter,
backward_bounds,
refs_2_plm,
)
2025-11-10 11:22:03,755 WARNING merge Optimal spacetime partitioning not implemented!Just partitioning list of noisy boxes.
2025-11-10 11:22:03,756 INFO merge Determined Box idx for partitioning to be 20.
b.ii. Calcular os local_scales para a execução em hardware
compute_local_scales analisa cada possível erro de ruído no circuito e estima o quanto esse erro poderia enviesar a medição final, bem como o quanto seria caro corrigi-lo. Em seguida, classifica os erros pelo quão valiosos são para mitigar e seleciona o subconjunto que reduz o viés tanto quanto possível, mantendo-se dentro do orçamento de custo de amostragem permitido (ou alcançando uma precisão desejada). O resultado é um conjunto de fatores de escala indicando quais erros serão mitigados ativamente e quais serão deixados sem mitigação (local_scales), juntamente com o overhead total previsto de custo de amostragem (sampling_costs) e o viés remanescente (residual_bias_bound).
A capacidade de controlar o viés remanescente desejado é uma característica crítica da implementação SLC do PEC. Enquanto na implementação original o overhead de amostragem sempre visava viés zero, podemos ajustar o overhead de amostragem necessário com uma compensação no viés remanescente esperado. Isso ajuda o usuário a permanecer dentro de um orçamento fixo de amostragem, o que pode ser particularmente útil ao prototipar inicialmente 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}")
Full PEC gamma=128.56055005423153, sampling cost (gamma^2) = 16527.81503024657
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_)
biases_cluster = []
costs_cluster = []
for bias in [0.0, *np.arange(0.001, 0.102, 0.01).tolist()]:
_, cost_, bias_ = compute_local_scales(
boxed_circuit,
merged_bounds_cluster,
refs_2_plm,
sampling_cost_budget=np.inf,
bias_tolerance=bias,
)
biases_cluster.append(bias_)
costs_cluster.append(cost_)
Benefícios dos clusters para reduzir o overhead de amostragem para um determinado tempo de computação clássica
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="local PEC+SLC")
ax.plot(
100 * np.array(biases_cluster),
np.array(costs_cluster),
"o-",
c="tab:green",
label="cluster PEC+SLC",
)
ax.set_yscale("log")
ax.set_ylim([100, 50000])
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")
Text(0.5, 0.98, 'PEC sampling overhead reduction due to SLC')

chosen_bias_thres = 0.1
local_scales, sampling_cost, residual_bias_bound = compute_local_scales(
boxed_circuit,
merged_bounds_cluster,
refs_2_plm,
sampling_cost_budget=np.inf,
bias_tolerance=chosen_bias_thres,
)
print(
f"PEC+SLC sampling cost (gamma^2) = {sampling_cost} w/ remaining bias = {100 * residual_bias_bound:.1f}%"
)
PEC+SLC sampling cost (gamma^2) = 563.1803982530477 w/ remaining bias = 9.3%
c. Executar o circuito de interesse com antinoise
c.i. Preparar o circuito de modelo usando samplex
O samplex é uma saída do método build do Samplomatic, que codifica todas as informações necessárias para gerar parâmetros aleatórios para o template_circuit. Eles são então usados para configurar os objetos QuantumProgram, que por sua vez são executados em uma QPU com o primitivo Executor. Cada QuantumProgram pode conter vários itens, que você pode pensar como um par de template e samplex.
Consulte o tutorial Hello samplomatic para 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
2025-11-10 11:22:04,839 INFO base_tasks Pass: AddSpectatorMeasures - 3.41392 (ms)
2025-11-10 11:22:04,843 INFO base_tasks Pass: AddPostSelectionMeasures - 2.88510 (ms)
c.ii. Configurar 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}
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(
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}
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(
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}
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(
circuit=final_template_circuit,
samplex=samplex,
samplex_arguments=samplex_arguments,
shape=(num_randomizations,),
chunk_size=chunk_size,
)
c.iii. Executar o programa com o primitivo Executor
executor = Executor(backend)
load_cached_executor_results = True
if load_cached_executor_results:
job_exec = shared_service.job("d46t1q6qsa9s73cb28g0")
else:
job_exec = executor.run(program)
results_exec = job_exec.result()
Etapa 4. Pós-processamento
Ao calcularmos o valor esperado final de interesse usando expectation_values, implementaremos algumas técnicas benéficas de pós-processamento para ajudar a garantir que obteremos os resultados de mais alta qualidade possível. Primeiro, aplicamos nossa mitigação de leitura twirled, TREX, que considera quaisquer erros que ocorram durante o processo de leitura. Em seguida, corrigimos erros devido a ruído não-Markoviano em nossos backends Heron usando um método de pós-seleção. Esse método mede qubits ativos e espectadores, em seguida aplica uma rotação lenta a cada qubit e, em seguida, mede novamente. Em casos em que as duas medições não confirmam um qubit invertido como esperado, esses shots são descartados aplicando-se uma mask da função PostSelector. Dentro do cálculo da máscara, uma estratégia específica pode ser definida para filtrar com base em nós de qubit único ou arestas de espectadores vizinhos, o que pode influenciar tanto o número de shots filtrados 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 postselectoion
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
With node-based post selection (27.0% of shots kept):
With node-based post selection (26.8% of shots kept):
A partir do exame dos resultados experimentais, podemos comparar diretamente o comportamento de diferentes abordagens: PEC, PEC combinado com SLC e a linha de base de resultados não mitigados. Alguns detalhes específicos a destacar:
- Os resultados não mitigados permanecem fora do intervalo de viés desejado e não são afetados pelo overhead de amostragem.
- Dado o alto custo de amostragem calculado acima (~10k), o PEC sozinho não converge dentro dos limites de aleatorização utilizados.
- O PEC + SLC, em contraste, converge muito mais rapidamente.
- Os limites de erro também diminuem significativamente mais rápido para PEC + SLC do que para PEC simples.
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])
(-50.0, 4100.0)


