전파형 소음 흡수(PNA)를 통한 기대값 개선
예상 소요 시간: 헤론 프로세서에서 10분 (참고: 이는 단지 추정치일 뿐입니다.) (실행 시간은 다를 수 있습니다.)
학습 성과
- 전파된 소음 흡수(PNA)란 무엇이며, 학습된 역소음 채널을 측정된 관측량에 흡수함으로써 2-큐비트 게이트 소음을 어떻게 완화하는가
- 회로 레이어를 박스 처리하고 주석을 달아 회전, 기준 변경 및 노이즈 주입을 수행하는
samplomatic방법 [NoiseLearnerV3( /docs/guides/directed-execution-model#noiselearnerv3 )을 사용하여 레이어 노이즈를 학습하고, 이를 노이즈 완화 관측량으로 전파하는 방법은 다음과 같습니다.qiskit-addon-pna- 에서 및
QuantumProgram클래스를Executor사용하여 무작위 회로를 표본 추출하고qiskit-ibm-runtime, PNA를 TREX 및 사후 선별과 결합하는 방법
전제조건
- Qiskit 패턴 워크플로우
- Estimator 프리미티브를 사용하여 관측 가능한 변수의 기대값 계산하기
- ‘추정기(Estimator) 프리미티브와 오류 완화 옵션 결합 ’에서 다룬 파울리 트위링(Pauli twirling) 및 TREX와 같은 오류 완화 기법
배경
이 튜토리얼에서는 Qiskit의 고급 오류 완화 도구를 활용하여 잡음이 있는 양자 실험에서 기대값 추정을 개선하는 방법을 보여드립니다.
전파 소음 흡수(PNA)란 무엇인가요?
전파형 잡음 흡수(Propagated noise absorption)는 2-큐비트 게이트에 영향을 미치는 역잡음 채널을 통해 관측량을 전파함으로써 게이트 오류를 완화하는 기법으로, 이를 통해 잡음 완화 관측량을 생성한다.
양자 오류 완화 기법을 활용하면, 완전한 내결함성을 요구하지 않으면서도 잡음이 있는 양자 하드웨어로부터 유용한 기대값을 도출할 수 있다. PNA는 회로 동작을 수정하는 대신, 잡음의 영향을 관측량 자체에 흡수시키는 데 특히 중점을 둡니다.
양자 회로에 포함된 각 잡음이 있는 게이트는 이상적인 게이트 뒤에 잡음 채널이 이어지는 형태로 모델링할 수 있다. PNA는 이러한 노이즈 채널을 학습하거나 특성 분석하고, 그 역함수를 정의합니다. PNA는 역연산을 하드웨어 실행 과정에 삽입하는 대신(이는 일반적으로 실현 불가능함), 역잡음 채널을 회로를 통해 전방으로 전파시켜 관측 가능 변수에 적용합니다. 이 과정은 관측 가능한 양자 연산 을 새로운 연산자 로 변환하며, 이로 인해 잡음이 있는 회로에서 를 측정했을 때, 잡음이 없는 이상적인 회로에서 를 측정했을 때와 동일한 기대값을 얻게 된다.
이를 다음과 같이 요약할 수 있습니다:
- 각 잡음이 있는 게이트 를 이상적인 연산 뒤에 잡음 채널 이 뒤따르는 형태로 모델링한다.
- 노이즈 특성 분석 도구를 사용하여 각 의 값을 파악하거나 추정하십시오.
- 파울리 전달 기법을 사용하여 역 잡음 맵 을 정의하고, 이를 회로를 통해 전방으로 전파시킵니다.
- 이러한 역연산자들을 관측 가능 연산자에 흡수함으로써, 잡음이 완화된 연산자 를 얻게 된다.
이 잡음을 파울리 채널(또는 더 일반적으로 희소 파울리-린드블라드 채널)로 표현할 때, 이러한 전파는 파울리 전파를 사용하여 효율적으로 수행할 수 있다. 파울리 전파(Pauli propagation)는 계산 복잡도를 제어하면서, 역잡음 채널이 클리포드 연산과 비클리포드 연산 층을 통과함에 따라 어떻게 변환되는지를 근사적으로 파악할 수 있는 프레임워크를 제공합니다.
PNA는 완화 처리를 관측 가능한 영역으로 전환함으로써, 회로에 물리적 보정 연산을 삽입할 경우 발생할 수 있는 막대한 샘플링 오버헤드를 피합니다. 대신, 원래의 잡음이 포함된 회로가 실행되는 한편, 관측량은 잡음의 영향을 상쇄하는 기대값을 갖는 새로운 연산자 로 변환된다.
PNA 워크플로우
이 과정은 다음과 같은 개념적 단계를 통해 이해할 수 있다. 첫 번째 회로도는 일반적인 잡음이 있는 실험을 보여줍니다.
노이즈 모델을 학습하면, 그 역모델을 적용하여 노이즈를 제거할 수 있습니다.
확률적 오차 제거(PEC)에서와 같이 QPU에서 역 잡음 채널을 샘플링하여 구현하는 대신, 우리는 파울리 전파를 이용하여 측정된 관측량에 이를 고전적인 방식으로 적용한다. 측정 결과, 이 관측량은 학습된 게이트 노이즈를 효과적으로 완화합니다.
Samplomatic 및 Executor를 활용한 모듈식 오류 완화
PNA 접근 방식은 Qiskit의 모듈형 오류 완화 아키텍처를 기반으로 합니다. 이 아키텍처는 라이브러리와 및 Executor 클래스( v0.47.0 에서 qiskit-ibm-runtimeQiskit Runtime 에 추가됨)를 QuantumProgramsamplomatic 함께 사용하여, 전파형 잡음 흡수 및 파울리 트위링과 같은 기법들이 서로 다른 실험 간에 조합 가능하고 재사용될 수 있도록 합니다.
Executor완화 로직을 회로 정의 자체에 내장하는 대신, 완화 처리는 어노테이션을 통해 samplomatic 선언적으로 표현되며, 무작위 회로의 생성, 실행 및 사후 처리 방식을 제어하는 를 통해 프로그래밍 방식으로 관리됩니다.
이 튜토리얼에서는 Qiskit 패턴을 구현하여, PNA가 역 파울리 노이즈 채널을 전파하고 이에 따라 관측량을 수정함으로써, 노이즈가 있는 QPU에서 기대값의 추정 정확도를 향상시키는 방법을 시연합니다.
작업 흐름 개요
- 1단계: 양자 문제로 매핑하기
- 대칭화된 트로터화 킥-이징 모델과 목표 관측량을 구성한다.
- 2단계: 잡음 특성 분석 및 전파
- 를
samplomatic사용하여 회로 내의 고유한 2-큐비트 레이어와 측정 항목을 식별하고 주석을 달아주세요. NoiseLearnerV3.을 사용하여 각 고유한 레이어에 영향을 미치는 노이즈를 파악하세요.- 각
InjectNoise주석을 해당 학습된 노이즈 모델에 매핑합니다. - 를
qiskit-addon-pna사용하여 역 잡음 채널을 회로를 통해 전방으로 전파시키고, 이를 대상 관측량에 흡수시킵니다.
- 를
- 3단계: 양자 실험 수행
Executor를QuantumProgram정의하여 를 통한samplex무작위 표본 추출을 지정하고, 을 사용하여 백엔드에서 실험을 실행합니다.
- 4단계: 결과 재구성 및 분석
- 완화 전략(PNA, PNA+TREX, PNA+PS, PNA+PS+TREX)을 비교하고, 완화 조치가 적용되지 않은 결과에 비해 얼마나 개선되었는지 시각화합니다.
요구사항
이 튜토리얼을 시작하기 전에 다음 항목이 설치되어 있는지 확인하십시오:
- Qiskit SDK v2.2 또는 그 이후 버전, 시각화 기능 지원
- Qiskit Runtime v0.47 또는 그 이후 (
pip install qiskit-ibm-runtime) - Samplomatic v0.13 이상 (
pip install samplomatic) - PNA Qiskit 애드온 (
pip install qiskit-addon-pna) - Qiskit 애드온 유틸리티 (
pip install qiskit-addon-utils)
설정
from qiskit import QuantumCircuit
from qiskit.quantum_info import Pauli, SparsePauliOp
from qiskit.transpiler import generate_preset_pass_manager, PassManager
from qiskit_ibm_runtime import (
QiskitRuntimeService,
QuantumProgram,
Executor,
NoiseLearnerV3,
)
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 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_pna import generate_noise_mitigating_observable
import samplomatic
from samplomatic.transpiler import generate_boxing_pass_manager
from samplomatic.annotations import InjectNoise
from samplomatic.utils import get_annotation, find_unique_box_instructions
import numpy as np
import matplotlib.pyplot as plt
# Selects a connected chain of low-error qubits on the target backend.
# The mirrored kicked-Ising circuit is a 1D chain, so we only need a line
# of connected physical qubits; the helper walks the backend's coupling map
# and grows a chain along the lowest-error two-qubit edges, so it works for
# any backend rather than relying on a hardcoded layout.
def find_qubit_chain(backend, length):
"""Find a connected chain of ``length`` physical qubits on ``backend``.
The chain is grown greedily along the lowest-error two-qubit edges, so it
favors better-performing qubits. Because the mirrored kicked-Ising circuit
is a 1D chain, a connected line is all we need.
"""
target = backend.target
# Identify the native two-qubit gate and build a per-edge error lookup.
two_qubit_gate = next(
name
for name in target.operation_names
if target[name]
and all(q is not None and len(q) == 2 for q in target[name])
)
edge_error = {
frozenset(qargs): (
1.0 if props is None or props.error is None else props.error
)
for qargs, props in target[two_qubit_gate].items()
}
graph = backend.coupling_map.graph.to_undirected(multigraph=False)
neighbors = {n: list(graph.neighbors(n)) for n in graph.node_indices()}
def first_chain_from(start):
path, visited = [start], {start}
def grow():
if len(path) == length:
return True
node = path[-1]
order = sorted(
neighbors[node],
key=lambda m: edge_error.get(frozenset((node, m)), 1.0),
)
for nxt in order:
if nxt not in visited:
visited.add(nxt)
path.append(nxt)
if grow():
return True
path.pop()
visited.remove(nxt)
return False
return path if grow() else None
def chain_cost(path):
return sum(
edge_error.get(frozenset((path[i], path[i + 1])), 1.0)
for i in range(len(path) - 1)
)
# Try low-degree qubits first (the natural ends of long chains) and keep
# the lowest-error chain found.
best_path, best_cost = None, float("inf")
for start in sorted(neighbors, key=lambda n: len(neighbors[n])):
chain = first_chain_from(start)
if chain is not None and (cost := chain_cost(chain)) < best_cost:
best_path, best_cost = chain, cost
if best_path is None:
raise ValueError(
f"Could not find a connected chain of {length} qubits "
f"on '{backend.name}'."
)
return best_path소규모 시뮬레이터 예시
PNA는 특정 양자 프로세서의 물리적 2-큐비트 게이트 노이즈를 완화합니다. 이 워크플로는 이상적인 시뮬레이터에서는 이에 상응하는 기능이 없는 두 가지 하드웨어 서비스에 의존합니다:
NoiseLearnerV3트랜스파일된 회로의 각 고유한 2-큐비트 층에 연결된 스파스 파울리-린드블라드 잡음 채널을 실험적으로 특성화한다. 잡음이 없는 시뮬레이터에서는 학습할 잡음이 없으며, 전파된 역채널은 항등 채널이 될 것입니다.- 이
Executor프라이미티브는 백엔드에서samplomatic생성된, 뒤틀리고 무작위화된 회로들을 샘플링합니다.
원칙적으로 합성 잡음 모델로 대체할 수 있습니다. generate_noise_mitigating_observable예를 들어, Qiskit Aer를 사용하여 지침을 첨부하고 PauliLindbladError , 그 결과로 생성된 노이즈가 포함된 회로를 직접 에 전달할 수 있습니다. 그러나 이는 단지 여러분이 직접 주입한 잡음에 대해 고전적인 부기 방식을 검증해 줄 뿐이며, 이 기법의 핵심을 흐리게 합니다. 따라서 시뮬레이터 예제는 생략하고, 하드웨어에서 직접 전체 PNA 워크플로를 시연하며, Qiskit 패턴의 각 단계를 아래에 상세히 설명합니다.
대규모 하드웨어 예시
이제 Qiskit 패턴의 네 단계를 따라, IBM Quantum® 하드웨어에서 실행되는 30개 사이트의 킥(kicked) 이징(Ising) 모델에 대해 전체 PNA 워크플로를 실행합니다.
1단계: 양자 문제로 매핑하기
대칭 트로터 회로와 관측량을 생성합니다.
이번 실험에서는 1D 스핀 사슬 위에서 30개 사이트로 구성된 킥(kicked) 이징 모델의 시간적 동역학을 연구할 것입니다. 고려되는 해밀토니안은 다음과 같습니다:
,
여기서 는 가장 가까운 이웃 스핀 간의 결합을 나타내며, , 전역 횡자장 은 로 설정된다. 가 클리포드 각도(즉, )에서 멀어질수록, 회로를 통해 반잡음 생성기를 전파시키는 것이 더 어려워진다.
관측량으로, 평균 단일 사이트 자화량인 를 고려할 것이며, 여기서 는 사이트의 수이다.
num_qubits = 30
num_trotter_steps = 10
rx_angle = np.pi / 8
# Avg single-site magnetization
id_pauli = Pauli("I" * num_qubits)
observable = (
SparsePauliOp([id_pauli.dot(Pauli("Z"), [i]) for i in range(num_qubits)])
/ num_qubits
)
# Implement Trotterized kicked-Ising model
circuit = QuantumCircuit(num_qubits)
for _step 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)
# Append the inverse circuit to complete the mirroring
circuit.compose(circuit.inverse(), inplace=True)
circuit.measure_active()
circuit.draw("mpl", fold=-1)Output:
2단계: 하드웨어 실행에 맞게 문제를 최적화합니다
다음 단계는 미러링된 트로터 회로를 실제 IBM Quantum 하드웨어에서 실행할 수 있도록 준비하는 것입니다. QPU에서 프로그램을 실행하려면 단순히 추상 회로를 구성하는 것만으로는 부족하며, 다음과 같은 조건을 충족하도록 회로를 최적화해야 합니다:
-
백엔드의 기본 게이트 설정과 연결성을 존중합니다. 트랜스파일레이션은 논리 회로를 대상 백엔드에서 지원하는 ISA 회로로 매핑합니다. 이를 통해 모든 게이트와 큐비트 간의 상호작용이 물리적으로 구현 가능함을 보장합니다.
-
우리는 회로 계층 수준에서 노이즈를 특성화할 수 있다. PNA는 역잡음 채널을 학습하고 전파하는 방식에 기반을 두고 있습니다. 이를 효율적으로 수행하기 위해, 트랜스파일링된 회로를 서로 다른 두 큐비트로 구성된 “박스형” 레이어로 분할합니다. 이 상자들을 통해 회로의 각 레이어를 각각 학습된 노이즈 모델과 연관 지을 수 있습니다.
-
PNA에 사실적인 노이즈 모델을 입력할 수 있습니다. 회로가 박스 처리되면, 이
NoiseLearnerV3서비스를 사용하여 각 고유한 2-큐비트 층에 영향을 미치는 파울리 노이즈 채널을 실험적으로 파악합니다. 이러한 학습된 모델들은 이후 Samplomatic의 어노테이션을 통해 회로로 다시 연결됩니다.
종합해 보면, 이 단계는 이상화된 미러 회로와 학습된 노이즈 모델을 적용한 하드웨어 구현 가능 회로 사이의 격차를 해소합니다. 이러한 구성에서 PNA는 회로를 통해 역잡음 채널을 전파하고, 이에 따라 관측량을 조정할 수 있다.
백엔드에 연결하고 ISA 회로로 변환합니다
먼저 ‘ IBM Quantum ’ 컴퓨트 서비스를 초기화하고 백엔드를 선택합니다. 지침 에 따라 본인 계정으로 인증하고 로그인 정보를 저장하면, 이후 해당 정보가 자동으로 불러와집니다 QiskitRuntimeService() .
# Initialize IBM Quantum Compute Service using your saved credentials
service = QiskitRuntimeService()
backend = service.least_busy(
operational=True, simulator=False, min_num_qubits=num_qubits
)
# 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_fez
다음으로, 백엔드에서 연결된 큐비트 체인을 선택하고 회로를 해당 체인에 트랜스파일합니다.
‘설정’ 섹션에서 정의된 헬퍼를 find_qubit_chain 사용하여, 서로 연결된 물리적 큐비트들로 num_qubits 이루어진 한 줄을 선택합니다. initial_layout그런 다음 를 사용하여 optimization_level=0 트랜스파일링하고, 이 체인을 로 고정합니다. 이렇게 하면 미러링된 회로의 2-큐비트 게이트 계층 구조가 정확히 보존됩니다. 이 구조는 복싱 단계와 노이즈 학습 단계가 의존하는 기반이므로, 여기서는 (대칭 게이트를 무효화하게 될) 더 높은 최적화 수준은 피해야 합니다.
# Find a connected, low-error chain of qubits on the chosen backend
layout = find_qubit_chain(backend, num_qubits)
# Transpile the circuit for the target backend, pinning the chain as the layout.
# optimization_level=0 preserves the mirrored two-qubit-gate layers that the
# boxing and noise-learning steps rely on.
pm = generate_preset_pass_manager(
backend=backend, optimization_level=0, initial_layout=layout
)
isa_circuit = pm.run(circuit)
isa_observable = observable.apply_layout(isa_circuit.layout)
isa_circuit.draw("mpl", fold=-1)Output:
2-큐비트 게이트 레이어와 측정 레이어를 회전시켜 보고, 고유한 레이어를 찾아보세요
우리는 를 samplomatic 사용하여 회로를 상자 안에 담고 고유한 2-큐비트 층을 식별합니다. 박스(box)는 명령어들을 한데 묶어, 나중에 특정 의도나 주석을 동일한 박스 내의 모든 게이트에 일관되게 적용할 수 있도록 하는 구조입니다.
여기서는 단순히 두 큐비트 층을 식별하는 것을 넘어서는 메서드를 generate_boxing_pass_manager 호출합니다. 이 기능은 다음과 같은 몇 가지 주요 작업을 수행합니다:
- 회로 내의 모든 2-큐비트 레이어를 그룹화하고,
- 해당 레이어에 및
ChangeBasis어노테이션을Twirl적용하고, - 측정 작업을 별도의 박스형 섹션으로 묶어 배치하고,
- 각 2-큐비트 레이어에 주석을
InjectNoise적용합니다.
이 주석들은 회로 전반에 걸쳐 노이즈, 기저 변환 및 트위를링이 어떻게 처리되는지를 정의합니다. 또한 이들은 이후 학습과 잡음 완화에 활용되는 구조를 확립합니다.
주요 구성 옵션은 다음과 같습니다:
enable_gates/enable_measures: True: 모든 2-큐비트 게이트 계층과 종단 측정을 상자 처리하십시오. 상자 안에서는 단일 큐비트 게이트가 왼쪽 방향으로 배치되어 있습니다.measure_annotations: all: 측정 상자에 및ChangeBasis주석을 포함하십시오Twirl.twirling_strategy: active: 얽힘 게이트가 포함된 각 상자 안의 모든 활성 큐비트를 회전시킨다.inject_noise_targets: gates: 얽힘 게이트를 포함하는 모든Twirl주석이 달린 상자에 주석을 추가합니다InjectNoise.inject_noise_strategy: uniform_modification: 회로 전체에 걸쳐 모든 노이즈 레이어의 크기를 동일하게 조정합니다.
# Box up circuit with Twirl and InjectNoise annotations
pm = generate_boxing_pass_manager(
enable_gates=True,
enable_measures=True,
measure_annotations="all",
twirling_strategy="active",
inject_noise_targets="gates",
inject_noise_strategy="uniform_modification",
)
boxed_circuit = pm.run(isa_circuit)draw_circ = QuantumCircuit(boxed_circuit.num_qubits)
draw_circ.append(boxed_circuit.data[0], qargs=boxed_circuit.data[0].qubits)
draw_circ.append(boxed_circuit.data[1], qargs=boxed_circuit.data[1].qubits)
draw_circ.draw("mpl", fold=-1, scale=0.3, idle_wires=False)Output:
회로 샘플링 방식을 정의하는 템플릿 회로와 samplex를 생성합니다.
Executor여기서는 또한.에서 출력된 표본에 대해 사후 선별을 수행하는 데 필요한 관찰자 측정값과 사후 선별 측정값을 추가합니다.
# Build template circuit and samplex for later use with the "Executor"
template_circuit, samplex = samplomatic.build(boxed_circuit)
# Add post-selection instructions to the template circuit
post_selection_pm = PassManager(
[
AddSpectatorMeasures(backend.coupling_map),
AddPostSelectionMeasures(x_pulse_type="rx"),
]
)
template_circuit = post_selection_pm.run(template_circuit)draw_circ = template_circuit.copy_empty_like()
draw_circ.data = template_circuit.data[:324]
draw_circ.draw("mpl", fold=-1, scale=0.3, idle_wires=False)Output:
다음 방법을 통해 소음을 파악하세요 NoiseLearnerV3
오류 완화를 위해 PNA를 적용하기 전에, 먼저 회로 내의 각 고유한 2-큐비트 층과 측정 층에 작용하는 잡음을 특성화해야 합니다. 이를 위해, 우리는 앞서 식별한 각 층에 대한 노이즈 모델을 실험적으로 학습하기 위해 이 NoiseLearnerV3 프로그램을 사용합니다. 학습기는 각 레이어에 영향을 미치는 노이즈 채널을 추정하는 벤치마킹 방식의 실험을 수행하고, 학습된 모델이 포함된 결과 객체를 반환합니다.
samplomatic먼저, 에서 제공된 find_unique_box_instructions 정보를 활용하여 회로 내의 고유한 층을 식별하는 것으로 시작합니다. 이를 통해 서로 다른 레이어 유형마다 노이즈를 한 번만 학습하게 되어, 실험 횟수와 총 샷 비용을 최소화할 수 있습니다. 이렇게 생성된 레이어 목록은 노이즈 학습기에 전달됩니다.
노이즈가 학습되는 방식을 제어하는 몇 가지 주요 매개변수가 있습니다:
num_randomizations: 학습 구성당 사용되는 무작위 회로의 수.shots_per_randomization: 무작위 학습 회로당 촬영 횟수.layer_pair_depths: 학습 실험에 사용할 회로 깊이(쌍 수로 측정).post_selection: 게이트를 사용하여rx측정 후 펄스를 적용하는 에지 기반 사후 선택을 활성화합니다.
# Noise learning parameters
num_randomizations_nl = 64
shots_per_randomization_nl = 128
# Match the deepest noise-learning depth to the depth of the circuit being
# mitigated. ``layer_pair_depths`` are measured in layer pairs (a layer + its
# inverse), so the deepest value is half the circuits's two-qubit-layer depth.
# Learning to this depth improves the quality of the mitigation.
depth_2q = isa_circuit.depth(lambda instr: instr.operation.num_qubits == 2)
max_layer_pair_depth = depth_2q // 2
# 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}
)
# Find the unique instructions (layers) from the boxed-up circuit
unique_2q_layers_and_meas = find_unique_box_instructions(
boxed_circuit, normalize_annotations=None, undress_boxes=True
)
# Configure and run the noise learner on the unique layers.
# Options can be passed directly as a dictionary.
noise_learner_options = {
"num_randomizations": num_randomizations_nl,
"shots_per_randomization": shots_per_randomization_nl,
"layer_pair_depths": layer_pair_depths,
"post_selection": {
"enable": True,
"strategy": "edge",
"x_pulse_type": "rx",
},
"environment": {"job_tags": ["TUT_PNA"]},
}
noise_learner = NoiseLearnerV3(backend, noise_learner_options)
noise_learner_job = noise_learner.run(unique_2q_layers_and_meas)
noise_learner_result = noise_learner_job.result()학습된 노이즈 발생률을 시각화하기
잡음 모델을 학습한 후, 1-큐비트 연산과 2-큐비트 연산 모두에 대해 추론된 오류율의 분포를 살펴볼 수 있습니다. 아래 코드는 학습된 노이즈 결과에서 파울리-린드블라드 표현을 추출하고, 이에 상응하는 노이즈 발생률을 수집합니다.
학습된 각 레이어에 대해:
- 우리는 노이즈 모델을
(pstr, qubits, rate)튜플의 스파스 리스트로 변환하는데, 여기서pstr는 주어진 큐비트에 작용하는 파울리 문자열이고,rate는 이에 대응하는 오류율이다. - 우리는 계수를 1-큐비트 (
len(pstr) == 1) 항과 2-큐비트 (len(pstr) == 2) 항으로 나눕니다. - 그런 다음 요금 목록을 정렬한 뒤, 그 중앙값을 구합니다.
1-큐비트(빨간색)와 2-큐비트(파란색)의 잡음률 분포를 로그 척도로 그래프에 표시하고, 각 분포의 중앙값을 수직선으로 표시하여, 학습된 파울리-린드블라드 생성기의 상대적 크기를 비교할 수 있도록 하였다. 1큐비트 및 2큐비트 발생률의 순서는 장치와 특성 분석 대상인 특정 층에 따라 달라집니다. 이번 실험에서는 단일 큐비트( weight-1 ) 발생기가 더 높은 중앙값 발생률을 보였습니다.
hw_rates_1q = []
hw_rates_2q = []
for nlr in noise_learner_result[:2]:
plm_list = nlr.to_pauli_lindblad_map().to_sparse_list()
hw_rates_1q += [
rate for (pstr, qubits, rate) in plm_list if len(pstr) == 1
]
hw_rates_2q += [
rate for (pstr, qubits, rate) in plm_list if len(pstr) == 2
]
hw_rates_1q = sorted(hw_rates_1q)
hw_rates_2q = sorted(hw_rates_2q)
median_1q = hw_rates_1q[len(hw_rates_1q) // 2]
median_2q = hw_rates_2q[len(hw_rates_2q) // 2]
fig, ax = plt.subplots(1, 1, figsize=(14, 5))
ax.scatter(
(hw_rates_1q),
[(i) / (len(hw_rates_1q) - 1) for i in range(len(hw_rates_1q))],
color="red",
label="1q rates",
)
ax.set_xscale("log")
ax.set_ylim(0, 1.1)
ax.vlines(median_1q, 0, 1, color="red")
ax.text(median_1q * 1.1, 0.1, f"{median_1q:.2e}")
ax.scatter(
(hw_rates_2q),
[(i) / (len(hw_rates_2q) - 1) for i in range(len(hw_rates_2q))],
color="blue",
label="2q rates",
)
ax.set_xscale("log")
ax.set_ylim(0, 1.1)
ax.vlines(median_2q, 0, 1, color="blue")
ax.text(median_2q * 1.1, 0.2, f"{median_2q:.2e}")
ax.set_title("Learned noise rates")
ax.set_xlabel("Noise rate")
ax.set_yticks([])
plt.legend()Output:
<matplotlib.legend.Legend at 0x125336120>
학습된 잡음과 회로 박스를 연관 짓기
각 고유한 2-큐비트 레이어에 대한 노이즈 모델을 확보한 후에는, 이를 상자 안에 표시된 회로 내의 해당 InjectNoise 주석과 연결해야 합니다.
이 InjectNoise 지시어는 단일 큐비트 드레서를 사용하여 제어 가능하고 구성 가능한 방식으로 회로에 노이즈를 주입하는 어노테이션입니다 samplomatic . 이를 통해 서로 다른 레이어에 걸쳐 모듈식 노이즈 모델링이 가능해집니다.
각 InjectNoise 주석에는 다음 내용이 포함됩니다:
InjectNoise.ref- 주석에 대한 고유 식별자. 이는 객체가samplex해당 노이즈 모델을 올바르게 할당하는 데 사용됩니다.InjectNoise.modifier_ref(선택 사항) - 할당된 노이즈 모델을 곱셈 계수로 조정할 수 있게 해주는 보조 참조.
이 단계에서는 각 InjectNoise.ref 에 대해 이에 대응하는 학습된 노이즈 모델 (PauliLindbladMap) 간의 매핑을 구축합니다. 이 매핑을 통해 회로 내의 각 얽힘 게이트 층이 적절한 노이즈 모델과 짝을 이루게 되어, 샘플링 및 이후의 노이즈 완화 단계에서 노이즈 효과가 정확하게 적용될 수 있도록 합니다.
# map inject noise refs to pauli lindblad maps
refs_to_noise_models = {}
for instruction, result in zip(
unique_2q_layers_and_meas, noise_learner_result, strict=False
):
if inject_noise_annot := get_annotation(
instruction.operation, InjectNoise
):
refs_to_noise_models[inject_noise_annot.ref] = (
result.to_pauli_lindblad_map()
)학습된 안티노이즈를 통해 관측값을 전파한다
앞서 설명한 바와 같이, 이 과정은 두 단계로 진행됩니다. 먼저, 안티노이즈 생성기를 회로의 끝까지 전파시킵니다. 그 후, 진화한 생성기를 통해 관측량을 전파합니다. 회로 내의 각 항잡음 발생기에 대해 이 과정이 반복됩니다. 이 구현 방식에서는 주어진 레이어의 각 생성기가 회로의 끝까지 병렬로 전파됩니다. 또한, Python 멀티프로세싱을 사용하여 안티노이즈의 전방 전파와 관측량의 역전파를 모두 병렬로 수행합니다. 이를 통해 메모리에 진화한 생성기가 과도하게 쌓이는 것을 방지하고, 연산 자원을 최대한 활용할 수 있습니다.
PNA를 실행할 때는 항상 노이즈가 포함된 회로와 관측 변수를 지정해야 합니다. 노이즈가 발생하는 회로가 주석이 달린 InjectNoise 박스형 회로인 경우, 위 단계에서 생성한 매핑 정보를 제공해야 합니다. qiskit-aer또한 에서 가져온 명령어를 포함하는 PauliLindbladError 비박스형 회로를 전달할 수도 있습니다. 그 경우, refs_to_noise_models 를 제공할 필요는 없습니다. 주요 입력 사항 외에도 다음 사항도 고려해 주십시오:
max_err_terms: 각 항잡음 생성기에서 전방 전파 과정에서 유지할 항의 수. 이 값을 더 크게 설정하면 일반적으로 정확도가 향상되지만, 이러한 현상이 단조 증가를 보인다는 보장은 없습니다.max_obs_terms: 진화한 안티노이즈를 통해 역전파될 때, 잡음 완화 관측량인 에 포함될 항의 개수. 값이 클수록 일반적으로 정확도는 높아지지만, 정확도가 단조롭게 증가한다고 보장할 수는 없습니다.num_processes: 해당 프로세스에 할당할 코어 수. 생성기는 전방 전파되며 관측 가능 변수에 병렬로 적용된다는 점을 기억하십시오.search_step: 역전파 단계에서는 탐욕적(greedy) 방법을 사용하여 파울리 기저에서 두 연산자를 근사적으로 공액으로 변환한다.search_step값을 늘리면 이 방법의 처리 속도를 높일 수 있습니다. 자세한 내용은 를pauli-prop문서 참조하십시오.generate_noise_mitigating_observable``num_to_measure: 이 변수는 의 입력 변수는 아니지만, 에서 실제로 측정하고자 하는 항의 개수를 제어하는 데 사용합니다. 여기서는 관측 가능한 변수에 포함된 원래 용어인 상위 30개 용어만 측정합니다. 이제 각 항의 크기가 재조정되어, 이를 측정함으로써 학습된 게이트 노이즈를 완화하는 효과가 나타나게 되었습니다. 에서 30개 항만 측정하지만, 이를 더 크게 늘리는 것이 종종 유용합니다. 그렇게 하면 선행 항들의 스케일링 계수의 정확도가 높아지기 때문입니다.
# PNA parameters
num_processes = 8
max_err_terms = 10_000
max_obs_terms = 10_000
num_to_measure = num_qubits
obs_tilde_isa = generate_noise_mitigating_observable(
boxed_circuit,
isa_observable,
refs_to_noise_models,
max_err_terms=max_err_terms,
max_obs_terms=max_obs_terms,
num_processes=num_processes,
print_progress=True,
search_step=8,
)
p_2_v = {p: v for v, p in enumerate(layout)}
obs_tilde_virtual = SparsePauliOp.from_sparse_list(
[
(pstr, [p_2_v[p] for p in p_qubits], coeff)
for (pstr, p_qubits, coeff) in obs_tilde_isa.to_sparse_list()
],
num_qubits=num_qubits,
)
obs_tilde_virtual = obs_tilde_virtual[
np.argsort(np.abs(obs_tilde_virtual.coeffs))[::-1]
][:num_to_measure]Output:
Finished! 13740 / 13740 generators propagated.
obs_tilde_isa = obs_tilde_isa[np.argsort(np.abs(obs_tilde_isa.coeffs))][::-1]
plt.xscale("log")
plt.yscale("log")
plt.title(r"$\tilde{O}$ coeff magnitudes")
plt.ylabel("Magnitude")
plt.xlabel("Pauli term index")
plt.plot(np.abs(obs_tilde_isa.coeffs), ".")Output:
[<matplotlib.lines.Line2D at 0x124b0a120>]
측정 기저를 표준 형태로 변환하십시오
다음으로, 측정 대상 관측량에 포함된 모든 파울리 항을 완전히 포괄할 수 있도록 측정할 기저의 최소 집합을 구한다( 큐비트 단위로 교환 관계에 있는 관측량들은 여러 개를 동시에 측정할 수 있다 ). 우리는 원래 관측량, 즉 모든 단일Z 파울리 연산들의 합에 포함된 항들만을 측정하고 있으므로, 단 하나의 기저, 즉 ‘모든’Z 기저가 필요합니다.
Executor파울리 측정 기저 집합을 찾는 것 외에도, 이러한 파울리 항들을 가정한 표준형에 매핑해야 합니다. 정규 큐비트 순서에 대한 자세한 내용은 Samplomatic 문서를 참조하세요.
meas_box = boxed_circuit.data[-1]
canonical_qubits = [
idx
for idx, qubit in enumerate(boxed_circuit.qubits)
if qubit in meas_box.qubits
]
c_2_p = {
c: p for c, p in enumerate(canonical_qubits)
} # canonical -> physical
p_2_v = {p: v for v, p in enumerate(layout)} # physical -> virtual
c_2_v = {c: p_2_v[p] for c, p in c_2_p.items()} # canonical -> virtual
meas_bases, bases_reverser = get_measurement_bases(obs_tilde_virtual)
meas_bases_canonical = [
np.array([base[c_2_v[c]] for c in range(num_qubits)], dtype=np.uint8)
for base in meas_bases
]3단계: 양자 실험 수행
에서 샘플링 방법을 지정하십시오. QuantumProgram
Executor``QuantumProgram이제 를 설정합니다. 이는 에 의해 실행될 모든 회로 및 샘플링 구성을 담는 중앙 컨테이너 역할을 합니다. 이 객체는 PNA에서 사용되는 측정 결과를 산출하기 위해 무작위 회로 인스턴스가 어떻게 생성, 일괄 처리 및 실행되는지를 정의합니다.
A에는 QuantumProgram 여러 항목이 포함될 수 있으며, 각 항목은 템플릿 회로와 무작위화가 적용되는 방식을 정의하는 해당 samplex 객체로 구성됩니다. 이러한 추상화를 통해 는 Executor 무작위 회로 생성부터 샷 수집 및 통합에 이르기까지 전체 워크플로를 단일 모듈형 프로그램으로 처리할 수 있습니다.
이 단계에서는 앞서 만든 템플릿 회로와 samplex 를 사용하여 PNA 실험을 실행하는 를 QuantumProgram 생성합니다. 이 구성에는 다음 요소들이 포함됩니다:
template_circuit: 원하는 모든 무작위화(트와일링 무작위화, 매개변수 등)를 구현하는 데 필요한 모든 게이트를 포함하는 회로.samplex: 샘플링을 수행할 모든 가능한 회로 무작위 배열에 대해 확률 분포를 정의하는 객체.samplex_arguments: 을 완전히 정의하는 데 필요한 바인딩은samplexbasis_changes: 여기서는 측정 대상 관측량에 포함된 모든 파울리 항을 포괄할 수 있도록 측정할 기저 집합을 지정합니다.noise_scales.ref: 샘플에 추가적인 노이즈가 유입되는 것을 방지하기 위해 각 노이즈 레이어의 스케일을 로0.0설정했습니다.pauli_lindblad_maps: 가 전달된 경우noise_scales필수입니다. 이는 단순히 노이즈 레이어를 관련 노이즈 모델에 매핑하는 것입니다.
samplex_arguments``shape: 에 의해 정의된 암시적 형상을 확장하기 위한 형상 튜플. 이 확장에 의해 도입된 비자명 축들은 무작위화들을 열거한다.
# Control the # of shots during execution
shots_per_randomization_exec = 64
num_randomizations_exec = 6144
# Zero out the noise to prevent noise from being injected during execution.
# We only added InjectNoise annotations so PNA could associate the noise
# to layers in the circuit
samplex_inputs = {f"noise_scales.{ref}": 0.0 for ref in refs_to_noise_models}
samplex_inputs |= {"pauli_lindblad_maps": refs_to_noise_models}
# Specify the bases to measure. The samplex exposes one basis-change input per
# ChangeBasis-annotated box; here a single all-Z basis covers every term. We
# look up the basis-change interface name rather than hardcoding an index, since
# the name depends on the circuit's box structure.
bases_broadcastable = np.expand_dims(np.array(meas_bases_canonical), axis=1)
samplex_inputs |= {
spec.name: bases_broadcastable
for spec in samplex.inputs().get_specs(r"^basis_changes\.")
}
# Convert samplex_inputs into a dict to pass to QuantumProgram
samplex_arguments = (
samplex.inputs().make_broadcastable().bind(**samplex_inputs)
)
# Instantiate the QuantumProgram with the specified parameters
program = QuantumProgram(shots=shots_per_randomization_exec)
program.append_samplex_item(
circuit=template_circuit,
samplex=samplex,
samplex_arguments=samplex_arguments,
shape=(num_randomizations_exec,),
)다음 방법을 사용하여 회로를 테스트해 보십시오. Executor
QuantumProgram이제 를 정의했으므로, 실험을 수행하는 것은 간단합니다. 우리는 단순히 객체를 Executor 생성하고, 백엔드를 지정하고, 프로그램을 실행하기만 하면 됩니다.
# Execute (sample) the circuit
executor = Executor(backend)
job_exec = executor.run(program)
exec_results = job_exec.result()4단계: 결과 재구성 및 분석
오차를 완화한 기대값을 계산하려면 다음과 같이 합니다:
- 측정에 영향을 미치는 학습된 잡음을 바탕으로 TREX 스케일링 계수를 계산하고,
- 선택된 샘플만 남기도록 마스크를 생성하고,
- 의
qiskit-addon-utils함수를executor_expectation_values사용하여 모든 데이터를 오류가 완화된 기대값으로 통합하십시오.
# Computing the TREX factors
measurement_noise_map = noise_learner_result[2].to_pauli_lindblad_map()
trex_rescale_factors = trex_factors(measurement_noise_map, bases_reverser)
# Post-select the results
post_selector = PostSelector.from_circuit(
circuit=template_circuit, coupling_map=backend.coupling_map
)
# Compute the ps mask for filtering results
mask = post_selector.compute_mask(exec_results[0], strategy="edge")
# Compute expvals using post selected results
results = executor_expectation_values(
exec_results[0]["meas"],
bases_reverser,
meas_basis_axis=0,
avg_axis=1,
measurement_flips=exec_results[0]["measurement_flips.meas"],
pauli_signs=exec_results[0].get("pauli_signs", None),
postselect_mask=mask,
rescale_factors=trex_rescale_factors,
)완화 전략 비교: PNA, PNA+TREX, PNA+PS, PNA+PS+TREX
우리는 이 Executor 결과를 바탕으로 여러 완화 방안의 기대값을 계산하고 시각화합니다.
bases_reverser_unmit = {Pauli("Z" * num_qubits): [observable]}
args = [
(bases_reverser_unmit, None, None),
(bases_reverser, None, None),
(bases_reverser, None, trex_rescale_factors),
(bases_reverser, mask, None),
(bases_reverser, mask, trex_rescale_factors),
]
evs = []
for reverser, postsel_mask, factors in args:
# Compute expvals using post selected results
res_ps = executor_expectation_values(
exec_results[0]["meas"],
reverser,
meas_basis_axis=0,
avg_axis=1,
measurement_flips=exec_results[0]["measurement_flips.meas"],
pauli_signs=exec_results[0].get("pauli_signs", None),
postselect_mask=postsel_mask,
rescale_factors=factors,
)
res_ps = np.array(res_ps)
evs.append(res_ps[:, 0][0])
experiments = ["PNA", "PNA+TREX", "PNA+PS", "PNA+PS+TREX"]
colors = ["#d9d9d9", "#b0b0b0", "#7f7f7f", "#4c4c4c"]
plt.bar(experiments, evs[1:], color=colors)
plt.axhline(y=1, color="green", linestyle="--", linewidth=2, label="Ideal")
plt.axhline(
y=evs[0], color="red", linestyle="--", linewidth=2, label="Unmitigated"
)
plt.ylabel("Expectation value", fontsize=14)
plt.title(
r"30q Mirrored Ising, 10 Trotter steps, $\theta_{rx}=\frac{\pi}{8}$",
fontsize=14,
)
plt.legend(loc="upper left", bbox_to_anchor=(1.05, 1), borderaxespad=0.0)
plt.xticks(rotation=45)
plt.tight_layout()
plt.show()Output:
이 결과는 서로 다른 오류 완화 기법을 결합했을 때 나타나는 누적적 이점을 보여줍니다. 단순한 PNA 접근법만으로도 기대값이 이상적인 기준치에 근접하게 복원되는데, 이는 역잡음 채널을 관측량으로 전파시키는 것이 2-큐비트 게이트 오류를 효과적으로 보상한다는 것을 시사한다.
- TREX 재가중(PNA+TREX)을 적용하면 무작위 회로에서 발생하는 표본 불균형을 보정함으로써 추정치가 약간 개선됩니다.
- 선별 후 처리(PNA+PS)는 잔류 오차로 인해 발생할 가능성이 높은 일관성 없는 측정 결과를 걸러냄으로써 더 뚜렷한 성능 향상을 제공합니다.
- 마지막으로, 이 세 가지(PNA+PS+TREX)를 결합하면 가장 정확한 결과가 도출되며, 이는 이상적인 값에 거의 근접하여 이러한 완화 전략들이 서로를 어떻게 보완해 주는지를 보여줍니다.
전반적으로, 이번 비교를 통해 PNA가 노이즈를 고려한 기대값 추정의 견고한 토대가 되는 반면, TREX와 사후 선별은 정확도를 더욱 높이기 위한 상호 보완적인 개선 방안을 제공한다는 점이 부각된다.
다음 단계
이 글이 흥미로웠다면, 다음 자료도 참고해 보시기 바랍니다: