음영 라이트콘을 사용한 확률적 오차 상쇄
사용 예상 시간: Heron 프로세서에서 10분 (참고: 이는 예상치일 뿐입니다. 실제 실행 시간은 다를 수 있습니다.)
학습 목표
-
확률적 오차 상쇄(PEC)가 무엇인지, 그리고 왜 샘플링 오버헤드 가 회로에 작용하는 총 노이즈에 따라 지수적으로 증가하는지
-
음영 라이트콘(SLC)이 각 노이즈 항이 목표 관측량에 미치는 기여를 어떻게 제한하여 완화 예산을 중요한 곳에 사용할 수 있게 하는지 — 그리고 순수하게 기하학적인 라이트콘이 이미 제공하는 무편향 오버헤드 감소를 넘어, 음영 처리가 어떻게 제한된 잔여 편향과 더 낮은 샘플링 오버헤드를 맞바꿀 수 있게 하는지
-
NoiseLearnerV3로 레이어 노이즈를 학습하고samplomatic과Executor프리미티브를 통해 반노이즈를 주입하는 방법 -
TREX 및 사후 선택과 함께 PEC와 PEC+SLC를 결합하여 하드웨어에서 기댓값을 추정하는 방법
사전 요구 사항
-
Qiskit 패턴 워크플로
-
관측량의 기댓값을 계산하기 위한 Estimator 프리미티브 사용
-
Estimator 프리미티브와 오차 완화 옵션 결합하기에서 다루는 Pauli 트와이링 및 TREX와 같은 오차 완화 기법
배경
이 튜토리얼은 음영 라이트콘(SLC) 애드온을 사용하여 오차를 완화하는 방법을 보여줍니다. 이 애드온은 확률적 오차 상쇄(PEC) 기법의 발전된 형태로, 사용자가 회로 내 고유한 레이어의 노이즈를 학습한 다음 단일 큐비트 게이트와 후처리 기법을 적용하여 노이즈를 상쇄합니다. 다른 방법들과 비교하여 PEC는 완화된 결과의 편향에 대해 더 견고한 경계를 제공하지만, QPU 시간 측면에서 더 높은 오버헤드를 겪는 경향이 있습니다. PEC 과정에서, 노이즈에 의한 기댓값의 감쇠를 보상하기 위해 평균 결과에 의 배율을 적용하여 재조정합니다. 여기서 는 회로의 레이어 에서 오차 Pauli 의 학습된 노이즈율입니다. 이 재조정은 분산을 배 증가시키며, 따라서 QPU에서 필요한 회로 실행 횟수도 배가 되는데, 이를 샘플링 비용 또는 샘플링 오버헤드라고 부릅니다. 가 지수적으로 증가하기 때문에 PEC는 종종 얕거나 소수의 큐비트 회로로 제한됩니다. PEC에 대해 더 알아보려면 노이즈가 있는 양자 프로세서에서의 희소 Pauli-Lindblad 모델을 사용한 확률적 오차 상쇄를 참고하세요.
완화할 필요가 없는 오차를 식별할 수 있다면, 이 샘플링 비용을 지수적으로 줄일 수 있습니다. 이 방향의 첫 단계는 지역 인식 오차 완화를 구현하는 것으로, 빠르게 계산할 수 있는 기존의 "라이트콘"을 사용하여 회로 전체에서 관측량의 오차에 대한 민감도를 제한함으로써 PEC 오버헤드를 줄이며, 이는 일부 문제에서 PEC의 실현 가능성을 더 큰 규모로 확장합니다. 이 라이트콘 밖의 오차는 측정된 결과에 영향을 줄 수 없으므로 오차 상쇄 절차에서 제외할 수 있습니다. 이러한 제외는 추가적인 편향을 도입하지 않으면서 샘플링 오버헤드를 (경우에 따라 상당히) 줄여줍니다. 특히, 고정 깊이 회로의 지역 관측량 를 측정할 때, 회로의 큐비트 수를 확장하면 필요한 샘플링 오버헤드는 결국 정체됩니다(양자 회로의 지역성과 오차 완화의 그림 2b 참조).
음영 라이트콘(SLC)은 여기서 더 나아가, 고전 시뮬레이션을 사용하여 회로 전체에서 오차에 대한 민감도를 더 엄격하게 제한합니다. 이는 편향을 재정규화하는 데 필요한 샘플링 오버헤드를 줄이는 대신 일부 QPU 시간을 CPU 시간으로 대체합니다. 엄격한 컷오프 대신, 회로 내 각 잠재적 오차에는 관측량의 기댓값이 해당 오차에 얼마나 민감한지를 상한으로 제한하는 등급별 "음영"이 할당됩니다. (아래에서 소개하는 역전파 경계의 경우, 이 민감도는 회로의 초기 상태에도 의존합니다 — 일반적인 워크플로와 이 튜토리얼에서는 올-제로 상태입니다.) 이러한 정제된 특성화는 분산을 줄이면서 더 효율적이고 표적화된 PEC 적용을 가능하게 하며, 동시에 사용자가 관측량 추정의 편향을 제어 가능하게 조정할 수 있는 능력을 제공합니다. 자세한 내용은 고전적으로 가속화된 양자 오차 완화를 위한 라이트콘 음영을 참고하세요.
SLC 애드온을 위한 우리의 워크플로는 qiskit-ibm-runtime v0.47.0에서 Qiskit Runtime에 추가된 QuantumProgram 및 Executor 클래스와 함께 samplomatic 라이브러리를 활용하여, 사용 편의성을 유지하면서도 오차 억제 및 완화를 위한 실행 설정을 더 모듈식으로 제어할 수 있게 합니다. 자세한 내용은 directed execution model 가이드에서 확인하세요.
한눈에 보는 SLC 오차 완화 워크플로
QPU의 노이즈를 모델링하기 위해, 디바이스의 각 큐비트와 엣지에서 지역적으로 생성된 1-큐비트 및 2-큐비트 Pauli 오차율을 갖는 희소 Pauli-Lindblad 노이즈 모델을 사용합니다. 이 선택에 따라, 이 튜토리얼에서 제시하는 SLC 오차 완화 워크플로는 다음과 같습니다.
a. CPU — 1-큐비트 및 2-큐비트 Pauli 오차의 오차별 영향 제한
-
순방향 전파(관측량에 대한 영향 제한). 각 오차를 회로 끝까지 전파하고 관측량과의 교환자를 계산합니다.
-
전개 중 연산자 항을 절단하여 계산을 다루기 쉽게 유지합니다.
-
양자 속도 한계에 기반한 느슨한 관측량의 역전파를 통해 이러한 경계를 더욱 좁힙니다.
-
-
역방향 전파(초기 상태에 대한 영향 제한). 각 오차를 회로 시작까지 전파하고 초기 상태와의 교환자를 계산합니다.
b. QPU — 노이즈율 학습. NoiseLearnerV3를 사용하여 Pauli-Lindblad 노이즈 모델의 비율을 추정합니다.
c. CPU — 완화 우선순위 지정
-
학습된 노이즈율로 병합된 경계 업데이트. 이전에 계산된 순방향 및 역방향 경계를 결합하고 학습된 노이즈율로 업데이트합니다.
-
계산된 경계와 학습된 비율을 사용하여 완화할 노이즈 성분의 순위를 매깁니다. 각 가능한 노이즈 오차를 편향에 미치는 예상 영향과 이를 수정하는 데 드는 관련 비용을 기준으로 우선순위를 정합니다.
d. QPU — 반노이즈 삽입 및 실행. Box 어노테이션을 사용해 지정한 반노이즈(역노이즈)로 관심 회로를 실행합니다.
e. CPU — 관측량 추정. 비-마르코프 노이즈의 영향을 줄이기 위해 측정 기반 사후 선택을 적용하여 기댓값을 계산합니다.
노이즈 학습 개요
노이즈 학습은 여러 오차 완화 방법에서 공통적인 단계이며, 노이즈 학습기에 의해 수행됩니다. 이는 확률적 오차 증폭 튜토리얼에도 등장합니다. NoiseLearnerV3에서는 학습할 노이즈 레이어를 CircuitInstruction 객체로 구체적으로 식별할 수 있어, 위에서 설명한 방식으로 각 레이어에 대해 원하는 SLC 노이즈 경계를 계산할 수 있습니다. 학습된 Pauli-Lindblad 모델은 PEC+SLC 우선순위 지정에 사용될 계수를 제공합니다. 게이트가 레이어로 수집되는 방식은 generate_boxing_pass_manager와 find_unique_box_instructions 편의 함수를 사용하여 결정할 수 있으며, 아래 2단계에서 설명하는 대로 이후 SLC 유틸리티 함수 generate_noise_model_paulis에 입력됩니다.
| 파트 1 | 파트 2 | 파트 3 |
|---|---|---|
| 2-큐비트 게이트 레이어 Pauli 트와이링 | 항등 레이어 쌍을 반복하고 노이즈 학습 | 충실도 도출(각 노이즈 채널의 오차) |
![]() | ![]() |
후처리 개요
samplomatic과 Executor 프레임워크를 사용해 양자 하드웨어에서 실행한 후, 비트스트링 측정값을 원하는 관측량 값으로 변환합니다. 이 튜토리얼에서는 (아래 1단계에서 구성되는) 미러링된 이징 회로에 대한 워크플로를 보여주는데, 이 회로에서는 회로 후반부가 전반부를 되돌리기 때문에 측정된 관측량의 이상적인 기댓값이 1입니다. executor_expectation_values 함수로 관측량 값을 계산할 때, 노이즈 영향을 줄이는 몇 가지 후처리 기법을 적용합니다. 여기에는 비-마르코프 노이즈의 영향을 받은 샷 제거, 판독 오차 완화, 그리고 우리의 PEC 구현 세부 사항 반영이 포함됩니다. 자세한 내용은 아래 4단계에서 설명합니다.
요구 사항
이 튜토리얼을 시작하기 전에 다음이 설치되어 있는지 확인하세요.
-
시각화 지원이 포함된 Qiskit SDK v2.2 이상
-
Qiskit Runtime v0.47 이상 (
pip install qiskit-ibm-runtime) -
Shaded lightcones Qiskit 애드온 v0.1 이상 (
pip install qiskit-addon-slc) -
Qiskit addon utils v0.3 이상 (
pip install qiskit-addon-utils) -
Samplomatic v0.13 이상 (
pip install samplomatic)
설정
먼저, 이 노트북을 실행하는 데 필요한 패키지와 함수를 가져옵니다.
# Added by doQumentation — required packages for this notebook
!pip install -q matplotlib numpy qiskit qiskit-addon-slc qiskit-addon-utils qiskit-ibm-runtime samplomatic
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
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
소규모 시뮬레이터 예제
다른 학습 기반 오차 완화 방법과 마찬가지로, 음영 라이트콘을 사용한 PEC는 특정 양자 프로세서의 물리적 노이즈를 완화하므로, 이상적인 시뮬레이터에서는 의미 있는 대응물이 없는 하드웨어 서비스에 의존합니다:
-
NoiseLearnerV3는 각 고유한 2-큐비트 레이어에서 희소 Pauli-Lindblad 노이즈 채널을 실험적으로 특성화합니다. 노이즈가 없는 시뮬레이터에는 상쇄할 노이즈가 없습니다. -
Executor프리미티브는 백엔드에서samplomatic이 생성한 트와이링되고 반노이즈가 주입된 회로를 샘플링합니다.
음영 라이트콘 경계 계산은 고전적이지만, 완화 예산과 샘플링 오버헤드를 결정하는 학습된 하드웨어 노이즈율과 관련해서만 의미가 있습니다. 이러한 이유로 소규모 시뮬레이터 예제는 건너뛰고, 아래에 Qiskit 패턴의 각 단계를 세분화하여 하드웨어에서 전체 PEC+SLC 워크플로를 직접 시연합니다.
대규모 하드웨어 예제
IBM Quantum® 하드웨어에서 실행되는 20-큐비트 미러링된 이징 회로에 대해 완전한 PEC+SLC 워크플로를 실행하며, Qiskit 패턴의 4단계를 따릅니다.
1단계: 문제 매핑
우리의 시연은 다음과 같이 설정됩니다:
-
1D 이징 사슬 해밀토니안 아래에서 시간 전개를 수행하기로 선택합니다. 1D 이징 사슬은 밀도가 적절히 높은 회로 구조를 제공하여 PEC 구현을 보여주기에 편리합니다.
-
관측량을 측정하기로 선택하는데, 이는 하나의 와 하나의 성분을 포함합니다. 이는 SLC 경계가 서로 다른 유형의 오차를 어떻게 다르게 다루는지를 강조하기에 편리합니다.
-
완화된 추정치의 품질을 쉽게 판단할 수 있도록, 이 관측량의 이상적인 기댓값이 정확히 1이 되기를 원합니다. 회로를 미러링하면 그 목표에 대부분 도달합니다. 회로 후반부의 모든 게이트에 대해 전반부에 역게이트가 존재하므로, 이상적인 회로는 항등원으로 작동합니다.
-
마지막으로, 이상적인 기댓값 1을 얻으려면 회로가 관측량의 +1 고유상태에서 시작해야 하며(따라서 미러링 덕분에 이상적으로는 그 상태에서 끝나야 합니다). 관측량이 가 아닌 성분을 포함하고
Executor프리미티브가 회로 끝에서 원하는 측정 기저를 고려하므로,prepare_basis함수를 사용해 해당 기저 준비 게이트를 미러 회로 시작 부분에 추가합니다. 어떤 게이트가 필요하고 어디에 배치해야 하는지 식별하기 위해get_measurement_bases함수를 사용하며, 정규 기저 측정 준비 섹션에서 논의하는 대로box어노테이션 규칙에서 발생하는 큐비트 인덱스의 미묘한 부분을 추적합니다.
# 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
)

2단계: 최적화
실행할 회로, 측정할 관측량, 노이즈 학습 매개변수와 관련된 세부 사항을 최적화합니다. 시작점으로, 가장 한산한 Heron 프로세서를 선택하고 분수 게이트를 켠 상태로 인스턴스화합니다. 이러한 분수 게이트는 일부 사후 선택 필터링에서 더 큰 민감도를 허용합니다.
# Initialize IBM Quantum Compute 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}")
Selected backend: ibm_kingston
먼저, QPU에서 실행하기 위해 필요한 대로 회로를 ISA 명령어로 트랜스파일합니다. 실행할 물리적 큐비트의 선택은 결과의 품질에 큰 영향을 미치는데, 디바이스 전체에서 결맞음 시간과 게이트 충실도가 다르고 시간에 따라 변동하기 때문입니다. 신중한 실험을 위해서는 빠른 벤치마킹 실험을 참고하여 큐비트 체인을 직접 선택하는 것이 도움이 될 수 있습니다(예를 들어, 큐비트 선택을 위한 실시간 벤치마킹 튜토리얼 참조). 이 튜토리얼의 목적을 위해서는 백엔드의 커플링 맵과 가장 최근의 보정 데이터를 기반으로 레이아웃을 선택하는 Qiskit 트랜스파일러에 의존합니다.
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
)
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,
)

회로 박스화
구현의 편의를 위해 generate_boxing_pass_manager 트랜스파일 패스를 활용하며, 이는 회로 명령어를 주석이 달린 박스에 배치합니다. 이러한 박스는 PEC의 경우 회로에서 반노이즈를 주입해야 할 위치를 명확히 나타냅니다. 설정에 대한 자세한 내용은 Samplomatic 문서를 참고하세요.
SLC 워크플로는 이후 과정에서 inject_noise_strategy="individual_modification" 사용을 요구한다는 점에 유의하세요. 이는 회로 내 BoxOp 명령어의 InjectNoise 어노테이션을 고유하게 식별할 수 있게 해주기 때문입니다. (InjectNoise 어노테이션이 없는 박스는 이러한 방식으로 식별할 수 없습니다.)
SLC 경계 계산은 현재 InjectNoise 어노테이션이 site="after"로 배치되어 있다고 가정합니다(qiskit-addon-slc#33 참조). 최근 버전의 samplomatic에서 이 설정의 기본값이 변경되었으므로(samplomatic#372 참조), 아래에서 inject_noise_site="after"를 명시적으로 설정합니다.
find_unique_box_instructions 함수는 제공된 박스화된 회로를 순회하며, 노이즈 학습 및 노이즈 주입을 위해 고유한 2-큐비트(2Q) 레이어 또는 측정을 갖는 것들을 식별합니다.
# 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,
)

정규 기저 측정 준비
큐비트 순서를 추적하는 데 특별한 주의가 필요하다는 점에 유의하세요. 아래에서는 회로를 박스화하고 고유 명령어를 찾는 방식에 따라 큐비트 순서가 캡처되는 결과로, Executor에 제공할 때 큐비트 순서를 적절히 업데이트하는 수단으로 canonical_qubits라는 개념을 소개합니다. 자세한 내용은 큐비트 순서 규칙 문서를 참고하세요.
# 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
]
라이트콘 음영 처리, 노이즈 학습, 반노이즈 주입을 위한 워크플로
이 튜토리얼에서는 노이즈 학습이 완료되기 전에 SLC 경계 계산을 실행하므로, 완화 대상 회로가 학습된 노이즈 모델과 시간적으로 최대한 가깝게 실행됩니다. 원칙적으로 이 워크플로우는 더 병렬화할 수 있습니다. 노이즈 학습 작업이 실행되는 동안 병렬로 노이즈 경계를 추정할 수 있습니다. 임의의 양자 회로에 대해 노이즈 경계 계산은 약한 지수적 의존성으로 확장될 수 있으므로, (예를 들어 여러 CPU 코어에 걸쳐) 경계 계산을 병렬화하면 주어진 계산 시간 예산 내에서 더 타이트한 경계를 얻을 수 있으며, QPU 실행과 경계 계산 자체도 가장 효율적인 워크플로우를 위해 병렬화할 수 있습니다.
학습할 노이즈 모델 Pauli 예측
하드웨어에서 노이즈 학습을 실행하기 전에, 먼저 노이즈 모델이 포함할 Pauli 오차 항을 열거합니다. 각 고유한 레이어의 노이즈를, 활성 큐비트마다 가중치-1 Pauli(, , )당 하나의 오차율과 연결된 활성 큐비트 쌍마다 가중치-2 Pauli당 하나의 오차율을 갖는 희소 Pauli-Lindblad 모델로 모델링한다는 점을 상기하세요. generate_noise_model_paulis 함수는 제공된 회로의 고유한 박스화된 레이어를 각각 순회하며, 회로의 큐비트 연결성을 고려하여 정확히 이 Pauli 항 목록을 생성합니다.
다시 말해, 이 단계는 NoiseLearnerV3가 나중에 학습된 비율로 채울 노이즈 모델의 구조를 예측합니다. 이 구조를 미리 아는 것이 노이즈 학습이 실행되기도 전에 모든 잠재적 오차에 대한 순방향 및 역방향 경계를 계산할 수 있게 해줍니다.
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. 순방향 경계 계산
compute_forward_bounds 함수는 각 레이어의 게이트와 위에서 생성된 Pauli 항 사이의 교환 관계를, 순방향 전파 오차가 원하는 관측량 에 어떻게 영향을 미치는지의 관점에서 평가합니다. Pauli 항과 교환하는 게이트에 대해서는 아무 작업도 수행되지 않습니다. Clifford 게이트의 경우 회로 시작 쪽으로 밀어냅니다. 비Clifford 게이트의 경우, 목표 관측량에 대한 영향을 근사하여 (모든 경계가 병합된 후) 노이즈 상쇄를 위해 우선순위를 매길 수 있도록 합니다. 이 경계는 먼저 L2 노름(즉, 관련 Pauli 항 계수 제곱합의 제곱근)을 적용하여 얻습니다. 관련된 큐비트 항이 너무 많은 경우, 삼각 부등식을 사용하는 더 느슨한 경계로 되돌아갑니다.
경계 계산 매개변수 설정
다음 설정은 고전적 경계 계산을 제어합니다:
-
slc_atol: 계산의 절대 허용 오차입니다. 이 임계값보다 낮은 계수를 가진 연산자 항은 무시할 수 있는 것으로 간주되어 폐기됩니다. -
slc_eigval_max_qubits: 더 엄격한, 고윳값 기반(L2 노름) 경계가 평가되는 최대 큐비트 수입니다. 이보다 많은 큐비트에서 지원되는 연산자는 위에서 설명한 더 느슨한 삼각 부등식 경계로 대체됩니다. -
slc_evolution_max_terms: 회로를 통해 연산자를 전파하는 동안 유지되는 최대 Pauli 항 수입니다. 가장 작은 항을 잘라내면 약간 더 느슨한 경계를 감수하는 대신 계산을 다루기 쉽게 유지할 수 있습니다. -
slc_num_processes: 경계 계산이 병렬화되는 CPU 프로세스 수입니다. -
slc_timeout: 경계 계산에 할당된 시간 예산(초 단위)입니다. 이 예산 내에 도달하지 못한 레이어에는 2라는 자명한 경계가 할당됩니다.
이 튜토리얼에서는 타임아웃이 의도적으로 발생합니다. 노트북이 빠르게 실행되도록 하고 타임아웃에 도달하는 것이 안전하다는 것을 보여주기 위해 slc_timeout을 의도적으로 작게 설정했습니다. 계산된 음영은 모든 단계에서 상한(upper bound)이므로, 타임아웃에 도달하면 도달하지 못한 레이어에 대해서만 경계가 더 느슨해질(trivial) 뿐이며, 오차의 영향을 과소평가하는 일은 절대 없습니다. 이는 아래 시각화에서 가장 왼쪽 레이어가 자명한 경계로 완전히 채워지는 형태로 나타납니다. 타임아웃(또는 프로세스 수)을 늘리면 경계가 더 타이트해지고 샘플링 오버헤드가 더 줄어듭니다.
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,
)
Bounds computation timed out.
수동 검사를 위한 SLC 시각화
측정값과 Pauli 항이 로컬 오차와 어떻게 상호 작용하는지 살펴봄으로써 음영 처리된 경계의 동작을 해석할 수 있습니다. 이러한 패턴은 이 킥된(kicked) 이징 해밀토니안 시간 전개 문제의 특징적인 패턴이며, 논문 고전적으로 가속화된 양자 오차 완화를 위한 라이트콘 음영에도 나타나는, 몇 가지 특징적인 특성이 있습니다:
-
관측량에 있는 두 개의 항등원이 아닌 Pauli에서 발생하는 두 개의 원뿔을 명확하게 구별할 수 있습니다.
-
큐비트 6에서의 X 측정이 가장 오른쪽 레이어의 X 오차와 교환함을 알 수 있습니다.
-
큐비트 13의 Z Pauli가 가장 오른쪽 레이어의 Z 오차와 교환함을 알 수 있습니다.
-
위에서 지정한 시간 제한에 도달하면, 왼쪽에 남은 레이어들은 전부 2라는 자명한 경계로 채워집니다.
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. 양자 속도 한계를 이용해 순방향 경계 조이기
다음으로 tighten_with_speed_limit 함수를 사용해 경계를 조입니다. 관측량 라이트콘의 기하학적 확산은 위에서 계산한 순방향 경계에 이미 완전히 반영되어 있습니다. 이 함수가 추가하는 것은 정보가 회로를 통해 흐를 수 있는 제한된 속도—라이트콘은 2-큐비트 게이트 레이어당 최대 한 큐비트씩 확장됩니다—를 활용해, 이전에 계산된 경계들을 서로에 대해 조이는 자기 일관성 패스입니다. 정보는 양방향으로 흐릅니다:
-
교환자 경계에서 역전파된 관측량의 경계로: 관측량이 어떤 큐비트에서 오차와 교환한다면, 그 큐비트에서 또는 성분을 많이 가질 수 없습니다.
-
역전파된 관측량의 경계에서 교환자 경계로: 관측량이 어떤 큐비트에서 및 성분을 거의 갖지 않는다면, 그 큐비트에서 와 거의 교환해야 합니다.
forward_bounds_tighter = tighten_with_speed_limit(
forward_bounds, boxed_circuit, noise_model_paulis, isa_observable
)
수동 검사를 위한 SLC 시각화
원칙적으로 이러한 조임(tightening)은 계산된 경계와 타임아웃 이후 레이어에 할당된 자명한 경계 사이의 더 매끄러운 전환을 제공합니다. 이 예제에서는 타임아웃에 도달할 때쯤 라이트콘이 이미 회로 가장자리까지 확산되어 있기 때문에 그 효과가 거의 눈에 띄지 않습니다.
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. 역방향 경계 계산
노이즈 예측의 이 부분은 특정 레이어에서 발생한 오차가 입력 상태 에 어떻게 영향을 미치는지를 평가합니다. compute_backward_bounds 함수는 먼저 회로를 반전시키고 측정 게이트를 제거한 다음, 순방향 경계 계산에서 수행한 것과 유사한 분석을 진행합니다.
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,
)
수동 검사를 위한 SLC 시각화
역방향 경계 계산을 통해, 초기 상태 구조가 오차 전파의 초기 동작을 어떻게 지배하는지 확인할 수 있습니다:
-
Z 오차가 초기에 |0⟩ 초기 상태와 어떻게 교환하는지 명확하게 볼 수 있습니다.
-
X 기저의 +1 고유 상태로 초기화한 큐비트 6에서만, Z 오차가 교환하지 못하는 반면 X 오차는 교환합니다.
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,
)
)



학습된 노이즈율 없이 병합된 경계 미리 보기
merged_bounds 함수는 역방향 경계에서 순방향 경계로 전환하는 회로 상의 지점을 결정하여 원하는 관측량에 대한 총 추정 편향을 최소화합니다. 이 편향은 해당 지점 이전의 모든 노이즈 위치에 대한 역방향 경계 기여분과, 그 이후의 모든 노이즈 위치에 대한 순방향 경계 기여분의 합으로 계산됩니다. 현재 이는 모든 큐비트에 대해 균일하게 수행됩니다.
역방향 경계에서 순방향 경계로 전환하는 최적 지점은 학습된 노이즈율에 따라 달라집니다. 이 단계에서는 아직 어떤 비율도 학습하지 않았습니다: noise_model_rates는 모든 레이어에 대해 None으로 초기화되었으며, merge_bounds는 이를 균일한 노이즈율로 취급합니다. 따라서 여기에 표시된 병합 경계는 미리 보기에 불과합니다. 3단계에서 실제 학습된 비율로 재계산합니다.
merged_bounds = merge_bounds(
boxed_circuit,
forward_bounds_tighter,
backward_bounds,
noise_model_rates,
)
Missing noise rates. Partitioning backward/forward commutator bounds by assuming uniform error rates.
Optimal spacetime partitioning not implemented!Just partitioning list of noisy boxes.
수동 검사를 위한 SLC 시각화
역방향 경계와 조여진 순방향 경계를 병합한 후, 결합된 SLC의 동작이 명확해집니다:
-
위 함수는 역방향 경계에서 조여진 순방향 경계로의 전환이 일어나는 분할 지점을 선택함을 알려줍니다.
-
아래에서 SLC가 이제 부분적인 역방향 경계와 부분적인 조여진 순방향 경계를 포함하고 있음을 확인할 수 있습니다.
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,
)
)



3단계: 실행
이 섹션에서는 실제 양자 장치를 사용하는 워크플로우 부분을 시작합니다. 이 학습 기반 오류 완화 방법에는 두 단계가 있습니다:
-
NoiseLearnerV3를 사용하여 노이즈를 학습합니다. -
samplomatic및Executor프레임워크를 사용하여 오류 완화 회로를 실행합니다.
양자 회로에서 계산된, 경계가 지어진 오류를 바탕으로 관련 노이즈율을 학습하여 오류 예산의 우선순위를 정하고, 샘플링 오버헤드를 결정한 다음 QPU에서 실행합니다.
a. 노이즈율 학습
노이즈 학습기는 희소 Pauli-Lindblad 노이즈 모델을 기반으로, 관심 있는 하나 이상의 회로에서 게이트에 영향을 미치는 노이즈 프로세스를 특성화합니다. run() 메서드는 노이즈 학습기 설정에 지정된 옵션을 사용하여, 제공된 고유한 이중 큐비트 레이어에 대한 노이즈 학습 작업을 시작합니다. 이러한 옵션은 Pauli 트월링 전략, 무작위화 및 샷 수, 학습 깊이, 그리고 후선택을 제어합니다.
학습 깊이도 신중하게 선택합니다. samplomatic을 이용한 학습 기반 완화에서 실용적으로 발견된 사실은, 가장 깊은 학습 깊이를 완화하고자 하는 회로의 깊이와 일치시키는 것이 매우 유리하다는 것입니다. NLv3의 layer_pair_depths는 레이어 쌍(레이어와 그 역)으로 측정되므로, 가장 깊은 값을 회로의 이중 큐비트 레이어 깊이의 절반으로 설정합니다.
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}"
)
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. 실제 학습된 노이즈율로 병합 경계 업데이트
이제 구체적인 노이즈 모델이 학습되었으므로, 학습된 노이즈율을 예측된 노이즈 경계에 적용하여 편향을 최소화하는 데 가장 큰 영향을 미치는 경계가 무엇인지 최종적으로 판단할 수 있습니다.
merged_bounds = merge_bounds(
boxed_circuit,
forward_bounds_tighter,
backward_bounds,
refs_2_plm,
)
Optimal spacetime partitioning not implemented!Just partitioning list of noisy boxes.
b.ii. 하드웨어 실행을 위한 local_scales 계산
compute_local_scales는 회로에서 발생 가능한 각 노이즈 오류를 살펴보고, 그 오류가 최종 측정값을 얼마나 편향시킬 수 있는지, 그리고 이를 보정하는 데 비용이 얼마나 들지를 추정합니다. 그런 다음 완화할 가치가 있는 정도에 따라 오류의 순위를 매기고, 허용된 샘플링 비용 예산 내에서(또는 원하는 정확도를 달성하면서) 편향을 최대한 줄이는 부분집합을 선택합니다. 그 결과는 어떤 오류가 실제로 완화될지와 완화되지 않고 남을지를 나타내는 스케일링 계수 집합(local_scales)과, 예측된 총 샘플링 비용 오버헤드(sampling_costs) 및 잔여 편향(residual_bias_bound)입니다.
원하는 잔여 편향을 제어할 수 있다는 점은 PEC의 SLC 구현에서 매우 중요한 특징입니다. 원래 구현에서는 샘플링 오버헤드가 항상 0 편향을 목표로 했지만, 여기서는 예상되는 잔여 편향과의 절충을 통해 필요한 샘플링 오버헤드를 조정할 수 있습니다. 이를 통해 사용자는 고정된 샘플링 예산 내에서 작업을 유지할 수 있으며, 이는 워크플로우를 처음 프로토타이핑할 때 특히 유용할 수 있습니다.
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=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_)
샘플링 오버헤드와 잔여 편향 간의 절충
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")
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,
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}%"
)
PEC+SLC sampling cost (gamma^2) = 48.13646509622307 w/ remaining bias = 10.0%
c. 안티노이즈를 이용해 관심 회로 실행
c.i. samplex를 이용한 템플릿 회로 준비
samplex는 Samplomatic의 build 메서드의 출력으로, template_circuit에 대한 무작위화된 매개변수를 생성하는 데 필요한 모든 정보를 인코딩합니다. 이는 그다음 QuantumProgram 객체를 설정하는 데 사용되며, 이 객체는 다시 Executor 프리미티브를 통해 QPU에서 실행됩니다. 각 QuantumProgram은 template과 samplex의 쌍이라고 생각할 수 있는 여러 항목을 포함할 수 있습니다.
자세한 내용은 Hello samplomatic 튜토리얼을 참조하세요.
# 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. 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. Executor 프리미티브로 프로그램 실행
executor = Executor(backend)
job_exec = executor.run(program)
results_exec = job_exec.result()
4단계: 후처리
executor_expectation_values를 사용하여 관심 있는 최종 기댓값을 계산할 때, 가능한 가장 높은 품질의 결과를 얻는 데 도움이 되는 몇 가지 후처리 기법을 적용합니다. 먼저, 판독 과정에서 발생하는 오류를 보정하는 트월링된 판독 오류 소거(TREX)를 적용합니다. 그런 다음, 후선택 방법을 사용하여 Heron 백엔드에서의 비마르코프 노이즈로 인한 오류를 수정합니다. 이 방법은 활성 큐비트와 관망자(spectator) 큐비트를 측정한 다음, 각 큐비트에 느린 회전을 적용하고, 다시 측정합니다. 두 측정값이 예상된 큐비트 플립을 확인하지 못하는 경우, PostSelector의 mask를 적용하여 해당 샷을 폐기합니다. 마스크 계산 내에서, 단일 큐비트 노드 또는 인접한 관망자 엣지를 기준으로 필터링하는 특정 전략을 설정할 수 있으며, 이는 필터링되는 샷 수와 결과 품질 모두에 영향을 미칠 수 있습니다.
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
With node-based post selection (24.1% of shots kept):
With node-based post selection (24.2% of shots kept):
실험 결과를 살펴보면, 서로 다른 접근 방식(PEC, PEC와 SLC 결합, 그리고 완화되지 않은 기준선)의 동작을 직접 비교할 수 있습니다. 강조할 만한 몇 가지 구체적인 세부 사항은 다음과 같습니다:
-
완화되지 않은 결과는 10% 편향 대역을 훨씬 벗어난(0.35 부근) 곳에 위치하며, 무작위화 횟수에 영향을 받지 않습니다.
-
이 장치에서 전체 PEC는 의 샘플링 오버헤드를 갖습니다. 초기에 큰 변동을 보인 후, 순수 PEC 추정치는 신호를 회복하여 약 1.1 부근에 안착하며, 정확한 값을 약간 초과합니다. 10% 편향 대역은 PEC+SLC 추정기에만 적용된다는 점에 유의하세요: 순수 PEC는 원칙적으로 편향이 없으므로, 이 잔여 편차는 편향 보장의 대상이 아니라 통계적 변동과 학습된 노이즈 모델의 불완전성을 반영하며, 이는 이 문제 규모에서는 합리적인 수준입니다.
-
SLC는 오버헤드를 추가로 약 3.4배 더 줄여줍니다(, 잔여 편향 경계는 약 10%). PEC+SLC 또한 무작위화 횟수가 적을 때는 크게 변동하며, 처음에는 비물리적 영역까지 도달하기도 하지만, 그 추정치는 정확한 값으로 꾸준히 수렴하여 대역 안쪽으로 잘 들어온 1.0 부근에 안착합니다. 이는 순수 PEC와 비슷하거나 더 나은 정확도를 대략 3분의 1의 샘플링 비용으로 달성하며, 라이트콘 셰이딩의 이점을 보여줍니다.
-
두 완화된 추정치의 오차 막대는 무작위화가 누적됨에 따라 축소됩니다. 고정된 무작위화 횟수에서는 PEC+SLC 추정기가 더 작은 에 부합하는 더 작은 통계적 분산을 가집니다.
학습된 노이즈율(따라서 오버헤드와 완화된 추정치)은 실행 시점의 백엔드와 그 교정 상태에 따라 달라지므로, 이 노트북을 다시 실행하면 정량적으로 다른 값이 나올 수 있다는 점에 유의하세요.
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)

다음 단계
이 작업에 흥미를 느끼셨다면 다음 자료에도 관심이 있을 수 있습니다:

