오류 완화 옵션을 추정기 프리미티브와 결합하십시오.
예상 소요 시간: Heron r2 프로세서 기준 7분 (참고: 이는 단지 예상치일 뿐입니다.) (실행 시간은 다를 수 있습니다.)
학습 성과
- 동적 디커플링, 측정 오차 완화, 게이트 트위링, 제로 노이즈 외삽법이 하드웨어에서 어떻게 선택적으로 구현되는지.
- 하드웨어 소음을 줄이는 능력 면에서 두 제품이 어떻게 비교되는지.
전제조건
- 이 가이드에서 설명하는 동적 디커플링, 측정 오차 완화, 게이트 트위링 및 무잡음 외삽법의 기본 개념.
배경
이 튜토리얼에서는 ‘ IBM Quantum ’ 추정기 프리미티브에서 사용할 수 있는 오류 억제 및 오류 완화 옵션을 살펴봅니다. 이 튜토리얼에서는 다음 메서드들을 각각 개별적으로 구현하는 방법을 설명합니다:
- 동적 디커플링
- 측정 오류 완화
- 게이트 돌리기
- 무노이즈 외삽(ZNE)
이러한 기법들을 개별적으로 구현하는 대신, 복원력 수준을 사용하여 구현할 수도 있는데, 이 경우 복원력 수준은 0, resilience_level 1, 2의 값을 취합니다:
- 0 : 완화 조치가 적용되지 않습니다.
- 1 : 측정 오차 완화 기능이 구현되었습니다.
- 2 : 게이트 회전, 측정 오차 완화 및 ZNE 기능이 구현되었습니다.
이 튜토리얼에서는 다양한 오류 완화 설정 조합을 사용하여 Estimator 기본 요소를 통해 회로와 관측 가능한 객체를 구성하고 작업을 제출하게 됩니다. 그런 다음, 결과를 그래프로 표시하여 다양한 설정의 영향을 관찰하게 됩니다. 이 튜토리얼의 대부분은 시각화를 쉽게 하기 위해 10큐비트 회로를 사용하며, 마지막에는 워크플로를 50큐비트로 확장하게 됩니다.
요구사항
이 워크스루를 시작하기 전에 다음이 설치되어 있는지 확인하십시오:
- Qiskit SDK v2.1 또는 이후 버전, 시각화 지원 기능 포함
- Qiskit Runtime v0.40 이상 (
pip install qiskit-ibm-runtime)
설정
import matplotlib.pyplot as plt
import numpy as np
from qiskit.circuit.library import efficient_su2, unitary_overlap
from qiskit.quantum_info import SparsePauliOp
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit_ibm_runtime import QiskitRuntimeService
from qiskit_ibm_runtime import Batch, EstimatorV2 as Estimator소규모 시뮬레이터 예시
시뮬레이터에서는 런타임 오류 완화 기능이 지원되지 않으므로 이 단계는 생략하겠습니다.
하드웨어 예시
1단계: 고전적 입력을 양자 문제에 매핑하기
이 설명서는 고전적 문제가 이미 양자 문제로 매핑되었다는 전제하에 진행됩니다. 측정할 회로와 관측자를 구성하는 것으로 시작하십시오. 여기서 사용된 기법은 다양한 종류의 회로에 적용될 수 있지만, 간편함을 위해 이 예제에서는 Qiskit 회로 라이브러리에 포함된 회로를 efficient_su2 사용합니다.
efficient_su2 이는 큐비트 연결성이 제한된 양자 하드웨어에서도 효율적으로 실행될 수 있도록 설계된 매개변수화 양자 회로로, 최적화나 화학 같은 응용 분야의 문제를 해결할 수 있을 만큼 충분한 표현력을 갖추고 있습니다. 이는 선택된 반복 횟수에 맞춰, 매개변수화된 단일 큐비트 게이트 층과 고정된 패턴의 2큐비트 게이트 층을 번갈아 쌓아 올려 구축됩니다. 사용자가 2큐비트 게이트의 구성을 지정할 수 있습니다. 여기서는 두 큐비트 게이트를 최대한 밀집시켜 배치함으로써 회로 깊이를 최소화하므로 내장된 pairwise 패턴을 사용할 수 있습니다. 이 패턴은 선형 큐비트 연결만을 사용하여 구현할 수 있습니다.
n_qubits = 10
reps = 1
circuit = efficient_su2(n_qubits, entanglement="pairwise", reps=reps)
circuit.decompose().draw("mpl", scale=0.7)Output:
관측량으로, 마지막 큐비트에 작용하는 파울리 연산자 , 즉 를 취해 봅시다. 마지막 큐비트가 이 문자열의 첫 번째 요소에 해당하는 것은 Qiskit이 리틀 엔디안 표기법을 사용하기 때문입니다.
# Z on the last qubit (index -1) with coefficient 1.0
observable = SparsePauliOp.from_sparse_list(
[("Z", [-1], 1.0)], num_qubits=n_qubits
)이제 회로를 작동시키고 관측량을 측정할 수 있습니다. 하지만 동시에 양자 장치의 출력을 정답, 즉 회로가 오류 없이 실행되었을 때 관측량의 이론적 값과 비교하고 싶을 것입니다. 소규모 양자 회로의 경우, 고전 컴퓨터에서 회로를 시뮬레이션하여 이 값을 계산할 수 있지만, 더 크고 실용적인 규모의 회로에서는 이것이 불가능합니다. 이 문제는 양자 장치의 성능 벤치마킹에 유용한 “미러 회로”(“컴퓨트-언컴퓨트”라고도 함) 기법을 사용하여 해결할 수 있습니다.
미러 회로
미러 회로 기법에서는 회로의 각 게이트를 역순으로 뒤집어 구성하는 역회로를 연결합니다. 결과 회로는 신원 연산자를 구현하며, 이는 간단하게 시뮬레이션할 수 있습니다. 원래 회로의 구조가 미러 회로에 보존되어 있기 때문에 미러 회로를 실행하면 양자 장치가 원래 회로에서 어떻게 작동할지 알 수 있습니다.
다음 코드 셀은 회로에 임의의 매개변수를 할당한 다음, unitary_overlap 클래스를 사용하여 대칭 회로를 구성합니다. 회로를 미러링하기 전에, 트랜스파일러가 배리어 양쪽의 회로 부분을 병합하여 게이트가 전혀 없는 트랜스파일링된 회로가 생성되는 것을 방지하기 위해 배리어 명령어를 추가하십시오.
# Generate random parameters
rng = np.random.default_rng(1234)
params = rng.uniform(-np.pi, np.pi, size=circuit.num_parameters)
# Assign the parameters to the circuit
assigned_circuit = circuit.assign_parameters(params)
# Add a barrier to prevent circuit optimization of mirrored operators
assigned_circuit.barrier()
# Construct mirror circuit
mirror_circuit = unitary_overlap(assigned_circuit, assigned_circuit)
mirror_circuit.decompose().draw("mpl", scale=0.7)Output:
2단계: 양자 하드웨어 실행을 위한 문제 최적화
하드웨어에서 실행하기 전에 회로를 최적화해야 합니다. 이 과정에는 몇 가지 단계가 포함됩니다:
- 회로의 가상 큐비트를 하드웨어의 물리적 큐비트에 매핑하는 큐비트 레이아웃을 선택합니다.
- 필요에 따라 스왑 게이트를 삽입하여 연결되지 않은 큐비트 간의 상호 작용을 라우팅합니다.
- 회로의 게이트를 하드웨어에서 직접 실행할 수 있는 ISA(명령어 집합 아키텍처) 명령어로 변환하세요.
- 회로 최적화를 수행하여 회로 깊이와 게이트 수를 최소화합니다.
Qiskit에 내장된 트랜스파일러가 이러한 모든 단계를 대신 수행할 수 있습니다. 이 예제는 하드웨어 효율적인 회로를 사용하므로, 트랜스파일러는 상호작용 경로 설정을 위해 스왑 게이트를 삽입할 필요가 없는 큐비트 배치를 선택할 수 있어야 합니다.
회로를 최적화하기 전에 사용할 하드웨어 장치를 선택해야 합니다. 다음 코드 셀은 127 큐비트 이상을 갖춘, 사용량이 가장 적은 장치를 요청합니다.
service = QiskitRuntimeService()
backend = service.least_busy(
operational=True, simulator=False, min_num_qubits=127
)print(backend)Output:
<IBMBackend('ibm_fez')>
패스 매니저를 생성한 다음 해당 회로에 패스 매니저를 실행하여 회로를 선택한 백엔드로 트랜스파일할 수 있습니다. 패스 매니저를 만드는 쉬운 방법은 함수를 generate_preset_pass_manager 사용하는 것입니다. 패스 관리자를 사용한 트랜스파일링에 대한 자세한 내용은 ‘패스 관리자를 사용한 트랜스파일링’을 참조하십시오.
pass_manager = generate_preset_pass_manager(
optimization_level=3, backend=backend, seed_transpiler=1234
)
isa_circuit = pass_manager.run(mirror_circuit)
isa_circuit.draw("mpl", idle_wires=False, scale=0.7, fold=-1)Output:
이제 트랜스파일링된 회로에는 ISA 명령어만 포함되어 있습니다. 모든 게이트는 비선형 게이트( ), 회전( ), CZ 게이트 로 분해되었습니다.
트랜스필레이션 프로세스는 회로의 가상 큐비트를 하드웨어의 물리적 큐비트에 매핑했습니다. 큐비트 레이아웃에 대한 정보는 트랜스파일된 회로의 layout 어트리뷰트에 저장됩니다. 옵저버블도 가상 큐비트 측면에서 정의되었으므로 이 레이아웃을 옵저버블에 적용해야 하며, 이를 위해 다음과 같이 apply_layoutSparsePauliOp 메서드를 사용하여 할 수 있습니다.
isa_observable = observable.apply_layout(isa_circuit.layout)
print("Original observable:")
print(observable)
print()
print("Observable with layout applied:")
print(isa_observable)Output:
Original observable:
SparsePauliOp(['ZIIIIIIIII'],
coeffs=[1.+0.j])
Observable with layout applied:
SparsePauliOp(['IIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII'],
coeffs=[1.+0.j])
3단계: Qiskit primitives 명령어로 실행합니다
이제 추정기 프리미티브를 사용하여 회로를 실행할 준비가 되었습니다.
여기에서는 오류 억제 또는 완화 기능을 전혀 적용하지 않은 상태에서 시작하여, IBM Quantum Compute Service에서 제공하는 다양한 오류 억제 및 완화 옵션을 차례로 활성화하면서 총 5개의 개별 작업을 제출하게 됩니다. 각 옵션에 대한 자세한 내용은 다음 페이지를 참조하십시오:
이러한 작업들은 서로 독립적으로 실행될 수 있으므로, 배치 모드를 사용하여 Quantum Compute가 작업 실행 시점을 최적화하도록 할 수 있습니다.
pub = (isa_circuit, isa_observable)
jobs = []
with Batch(backend=backend) as batch:
estimator = Estimator(mode=batch)
estimator.options.environment.job_tags = [
"TUT_CEM_SS"
] # add tag for this small scale job
# Set number of shots
estimator.options.default_shots = 100_000
# Disable runtime compilation and error mitigation
estimator.options.resilience_level = 0
# Run job with no error mitigation
job0 = estimator.run([pub])
jobs.append(job0)
# Add dynamical decoupling (DD)
estimator.options.dynamical_decoupling.enable = True
estimator.options.dynamical_decoupling.sequence_type = "XpXm"
job1 = estimator.run([pub])
jobs.append(job1)
# Add readout error mitigation (DD + TREX)
estimator.options.resilience.measure_mitigation = True
job2 = estimator.run([pub])
jobs.append(job2)
# Add gate twirling (DD + TREX + Gate Twirling)
estimator.options.twirling.enable_gates = True
estimator.options.twirling.num_randomizations = "auto"
job3 = estimator.run([pub])
jobs.append(job3)
# Add zero-noise extrapolation (DD + TREX + Gate Twirling + ZNE)
estimator.options.resilience.zne_mitigation = True
estimator.options.resilience.zne.noise_factors = (1, 3, 5)
estimator.options.resilience.zne.extrapolator = ("exponential", "linear")
job4 = estimator.run([pub])
jobs.append(job4)4단계: 후처리 수행 및 원하는 클래식 형식으로 결과 반환
마지막으로 데이터를 분석할 수 있습니다. 여기에서 작업 결과를 검색하고, 측정된 기대값을 추출한 다음, 1표준편차의 오차 막대를 포함하여 값을 플로팅합니다.
# Retrieve the job results
results = [job.result() for job in jobs]
# Unpack the PUB results (there's only one PUB result in each job result)
pub_results = [result[0] for result in results]
# Unpack the expectation values and standard errors
expectation_vals = np.array(
[float(pub_result.data.evs) for pub_result in pub_results]
)
standard_errors = np.array(
[float(pub_result.data.stds) for pub_result in pub_results]
)
# Plot the expectation values
fig, ax = plt.subplots()
labels = ["No mitigation", "+ DD", "+ TREX", "+ Twirling", "+ ZNE"]
ax.bar(
range(len(labels)),
expectation_vals,
yerr=standard_errors,
label="experiment",
)
ax.axhline(y=1.0, color="gray", linestyle="--", label="ideal")
ax.set_xticks(range(len(labels)))
ax.set_xticklabels(labels)
ax.set_ylabel("Expectation value")
ax.legend(loc="upper left")
plt.show()Output:
이렇게 작은 규모에서는 대부분의 오류 완화 기법의 효과를 확인하기 어렵지만, 영 노이즈 외삽은 눈에 띄는 개선 효과를 제공합니다. 그러나 ZNE 결과의 오차 막대가 더 크기 때문에 이러한 개선 사항은 무료로 제공되지 않습니다.
대규모 하드웨어 예시
실험을 설계할 때는 시각화 및 시뮬레이션을 용이하게 하기 위해 소규모 회로로 시작하는 것이 유용합니다. 이제 10큐비트 회로에서 워크플로를 개발하고 테스트했으니, 이를 50큐비트까지 확장할 수 있습니다. 다음 코드 셀은 이 단계별 안내의 모든 단계를 반복하지만, 이제 50큐비트 회로에 적용합니다.
n_qubits = 50
reps = 1
# Construct circuit and observable
circuit = efficient_su2(n_qubits, entanglement="pairwise", reps=reps)
observable = SparsePauliOp.from_sparse_list(
[("Z", [-1], 1.0)], num_qubits=n_qubits
)
# Assign parameters to circuit
params = rng.uniform(-np.pi, np.pi, size=circuit.num_parameters)
assigned_circuit = circuit.assign_parameters(params)
assigned_circuit.barrier()
# Construct mirror circuit
mirror_circuit = unitary_overlap(assigned_circuit, assigned_circuit)
# Transpile circuit and observable
isa_circuit = pass_manager.run(mirror_circuit)
isa_observable = observable.apply_layout(isa_circuit.layout)
# Run jobs
pub = (isa_circuit, isa_observable)
jobs = []
with Batch(backend=backend) as batch:
estimator = Estimator(mode=batch)
estimator.options.environment.job_tags = [
"TUT_CEM_LS"
] # add tag for this large scale job
# Set number of shots
estimator.options.default_shots = 100_000
# Disable runtime compilation and error mitigation
estimator.options.resilience_level = 0
# Run job with no error mitigation
job0 = estimator.run([pub])
jobs.append(job0)
# Add dynamical decoupling (DD)
estimator.options.dynamical_decoupling.enable = True
estimator.options.dynamical_decoupling.sequence_type = "XpXm"
job1 = estimator.run([pub])
jobs.append(job1)
# Add readout error mitigation (DD + TREX)
estimator.options.resilience.measure_mitigation = True
job2 = estimator.run([pub])
jobs.append(job2)
# Add gate twirling (DD + TREX + Gate Twirling)
estimator.options.twirling.enable_gates = True
estimator.options.twirling.num_randomizations = "auto"
job3 = estimator.run([pub])
jobs.append(job3)
# Add zero-noise extrapolation (DD + TREX + Gate Twirling + ZNE)
estimator.options.resilience.zne_mitigation = True
estimator.options.resilience.zne.noise_factors = (1, 3, 5)
estimator.options.resilience.zne.extrapolator = ("exponential", "linear")
job4 = estimator.run([pub])
jobs.append(job4)
# Retrieve the job results
results = [job.result() for job in jobs]
# Unpack the PUB results (there's only one PUB result in each job result)
pub_results = [result[0] for result in results]
# Unpack the expectation values and standard errors
expectation_vals = np.array(
[float(pub_result.data.evs) for pub_result in pub_results]
)
standard_errors = np.array(
[float(pub_result.data.stds) for pub_result in pub_results]
)
# Plot the expectation values
fig, ax = plt.subplots()
labels = ["No mitigation", "+ DD", "+ TREX", "+ Twirling", "+ ZNE"]
ax.bar(
range(len(labels)),
expectation_vals,
yerr=standard_errors,
label="experiment",
)
ax.axhline(y=1.0, color="gray", linestyle="--", label="ideal")
ax.set_xticks(range(len(labels)))
ax.set_xticklabels(labels)
ax.set_ylabel("Expectation value")
ax.legend(loc="upper left")
plt.show()Output:
50-큐비트 결과와 앞서의 10-큐비트 결과를 비교하면 다음과 같은 사항을 확인할 수 있습니다(실행에 따라 결과가 다를 수 있습니다):
- 모든 실험 결과는 이상값에 더 가깝게 나왔으며, 모든 오차 막대는 더 작습니다.
- 동적 분리 기법을 적용한 결과, 완화 조치를 적용하지 않은 경우와 비교해 성능이 오히려 저하되었을 수도 있다. 회로가 매우 복잡하기 때문에 이는 놀라운 일이 아니다. 동적 분리(dynamical decoupling)는 주로 회로에 큰 간격이 존재하여, 그 기간 동안 큐비트에 게이트가 적용되지 않은 채 유휴 상태로 있을 때 유용합니다. 이러한 간격이 존재하지 않을 경우, 동적 분리 방식은 효과적이지 않을 뿐만 아니라, 동적 분리 펄스 자체에 발생하는 오류로 인해 오히려 성능을 저하시킬 수 있습니다. 10큐비트 회로는 이 현상을 관측하기에는 규모가 너무 작았을 수도 있습니다.
- 제로 노이즈 외삽법을 사용하면 결과는 이상적인 값에 매우 근접합니다. 이는 ZNE의 위력을 보여줍니다.
다음 단계
이 내용이 흥미로우셨다면, 이 튜토리얼에서 다루지 않은 추가적인 오류 완화 및 오류 억제 기법에 관한 다음 자료에도 관심을 가져보시기 바랍니다:
- 확률적 오차 상쇄(PEC)
- 행렬을 사용하지 않는 측정 오 차 완화 ( M3 ) ( M3 를 사용한 샘플러 프리미티브의 판독 오차 완화 튜토리얼도 참조)
- 음영 처리된 광뿔
- 전파 소음 흡수 (PNA)