양자 노이즈와 오류 완화
Toshinari Itoko (2024년 6월 28일)
원본 강의 PDF 다운로드. 일부 코드 스니펫은 정적 이미지이므로 더 이상 지원되지 않을 수 있다는 점에 유의해 주세요.
이 실험을 실행하는 데 필요한 대략적인 QPU 시간은 1분 40초입니다.
1. 소개
이번 강의 전체에 걸쳐 양자 컴퓨터에서 잡음과 이를 완화하는 방법을 살펴봅니다. 먼저 시뮬레이터로 잡음의 영향을 살펴봅니다. 이 시뮬레이터는 실제 양자 컴퓨터의 잡음 프로파일을 불러오는 방식을 비롯해 여러 방법으로 잡음을 재현할 수 있습니다. 그런 다음 잡음이 본질적으로 존재하는 실제 양자 컴퓨터로 이동합니다. ZNE(영잡음 외삽)와 게이트 트워링(gate twirling) 같은 기법들의 조합을 포함한 오류 완화의 효과를 살펴보겠습니다.
먼저 몇 가지 패키지를 로드하는 것으로 시작하겠습니다.
# Added by doQumentation — required packages for this notebook
!pip install -q matplotlib qiskit qiskit-aer qiskit-ibm-runtime
# !pip install qiskit qiskit_aer qiskit_ibm_runtime
# !pip install jupyter
# !pip install matplotlib pylatexenc
import qiskit
qiskit.__version__
'2.0.2'
import qiskit_aer
qiskit_aer.__version__
'0.17.1'
import qiskit_ibm_runtime
qiskit_ibm_runtime.__version__
'0.40.1'
2. 오류 완화 없이 수행하는 잡음 시뮬레이션
Qiskit Aer는 양자 컴퓨팅을 위한 고전적 시뮬레이터입니다. 이상적인 실행뿐만 아니라 양자 Circuit의 잡음이 있는 실행도 시뮬레이션할 수 있습니다. 이 노트북은 Qiskit Aer를 사용하여 잡음 시뮬레이션을 수행하는 방법을 보여줍니다.
-
잡음 모델 구축
-
잡음 모델을 이용한 잡음 Sampler(시뮬레이터) 구축
-
잡음 Sampler에서 양자 Circuit 실행
noise_model = NoiseModel()
...
noisy_sampler = Sampler(options={"backend_options": {"noise_model": noise_model}})
job = noisy_sampler.run([circuit])
2.1 테스트 Circuit 구축
X Gate를 d번 반복하고(d=0 ... 100) Z 관측량을 측정하는 단순한 1-Qubit Circuit을 고려합니다.
from qiskit.circuit import QuantumCircuit
MAX_DEPTH = 100
circuits = []
for d in range(MAX_DEPTH + 1):
circ = QuantumCircuit(1)
for _ in range(d):
circ.x(0)
circ.barrier(0)
circ.measure_all()
circuits.append(circ)
display(circuits[3].draw(output="mpl"))
from qiskit.quantum_info import SparsePauliOp
obs = SparsePauliOp.from_list([("Z", 1.0)])
obs
SparsePauliOp(['Z'],
coeffs=[1.+0.j])
2.2 잡음 모델 구축
잡음 시뮬레이션을 수행하려면 NoiseModel을 지정해야 합니다. 이 섹션에서는 NoiseModel을 구축하는 방법을 보여줍니다.
먼저 잡음 모델에 추가할 양자(또는 판독) 오류를 정의해야 합니다.
from qiskit_aer.noise.errors import (
coherent_unitary_error,
amplitude_damping_error,
ReadoutError,
)
from qiskit.circuit.library import RXGate
# Coherent (unitary) error: Over X-rotation error
# https://qiskit.github.io/qiskit-aer/stubs/qiskit_aer.noise.coherent_unitary_error.html#qiskit_aer.noise.coherent_unitary_error
OVER_ROTATION_ANGLE = 0.05
coherent_error = coherent_unitary_error(RXGate(OVER_ROTATION_ANGLE).to_matrix())
# Incoherent error: Amplitude dumping error
# https://qiskit.github.io/qiskit-aer/stubs/qiskit_aer.noise.amplitude_damping_error.html#qiskit_aer.noise.amplitude_damping_error
AMPLITUDE_DAMPING_PARAM = 0.02 # in [0, 1] (0: no error)
incoherent_error = amplitude_damping_error(AMPLITUDE_DAMPING_PARAM)
# Readout (measurement) error: Readout error
# https://qiskit.github.io/qiskit-aer/stubs/qiskit_aer.noise.ReadoutError.html#qiskit_aer.noise.ReadoutError
PREP0_MEAS1 = 0.03 # P(1|0): Probability of preparing 0 and measuring 1
PREP1_MEAS0 = 0.08 # P(0|1): Probability of preparing 1 and measuring 0
readout_error = ReadoutError(
[[1 - PREP0_MEAS1, PREP0_MEAS1], [PREP1_MEAS0, 1 - PREP1_MEAS0]]
)
from qiskit_aer.noise import NoiseModel
noise_model = NoiseModel()
noise_model.add_quantum_error(coherent_error.compose(incoherent_error), "x", (0,))
noise_model.add_readout_error(readout_error, (0,))
2.3 잡음 모델을 이용한 잡음 Sampler 구축
from qiskit_aer.primitives import SamplerV2 as Sampler
noisy_sampler = Sampler(options={"backend_options": {"noise_model": noise_model}})
2.4 잡음 Sampler에서 양자 Circuit 실행
job = noisy_sampler.run(circuits, shots=400)
result = job.result()
result[0].data.meas.get_counts()
{'0': 389, '1': 11}
2.5 결과 그래프
import matplotlib.pyplot as plt
plt.title("Noisy simulation")
ds = list(range(MAX_DEPTH + 1))
plt.plot(
ds,
[result[d].data.meas.expectation_values(["Z"]) for d in ds],
color="gray",
linestyle="-",
)
plt.scatter(ds, [result[d].data.meas.expectation_values(["Z"]) for d in ds], marker="o")
plt.hlines(0, xmin=0, xmax=MAX_DEPTH, colors="black")
plt.ylim(-1, 1)
plt.xlabel("Circuit depth")
plt.ylabel("Measured <Z>")
plt.show()
2.6 이상적 시뮬레이션
ideal_sampler = Sampler()
job_ideal = ideal_sampler.run(circuits)
result_ideal = job_ideal.result()
plt.title("Ideal simulation")
ds = list(range(MAX_DEPTH + 1))
plt.plot(
ds,
[result_ideal[d].data.meas.expectation_values(["Z"]) for d in ds],
color="gray",
linestyle="-",
)
plt.scatter(
ds, [result_ideal[d].data.meas.expectation_values(["Z"]) for d in ds], marker="o"
)
plt.hlines(0, xmin=0, xmax=MAX_DEPTH, colors="black")
plt.xlabel("Circuit depth")
plt.ylabel("Measured <Z>")
plt.show()

2.7 연습문제
아래 코드를 조정하여 다음을 수행해 보세요.
- 샷 수를 25배(= 10_000 샷)로 늘려 더 부드러운 그래프가 얻어지는지 확인해 보세요
- 잡음 파라미터(OVER_ROTATION_ANGLE, AMPLITUDE_DAMPING_PARAM, PREP0_MEAS1 또는 PREP1_MEAS0)를 변경하고 그래프가 어떻게 바뀌는지 살펴보세요
OVER_ROTATION_ANGLE = 0.05
coherent_error = coherent_unitary_error(RXGate(OVER_ROTATION_ANGLE).to_matrix())
AMPLITUDE_DAMPING_PARAM = 0.02 # in [0, 1] (0: no error)
incoherent_error = amplitude_damping_error(AMPLITUDE_DAMPING_PARAM)
PREP0_MEAS1 = 0.1 # P(1|0): Probability of preparing 0 and measuring 1
PREP1_MEAS0 = 0.05 # P(0|1): Probability of preparing 1 and measuring 0
readout_error = ReadoutError(
[[1 - PREP0_MEAS1, PREP0_MEAS1], [PREP1_MEAS0, 1 - PREP1_MEAS0]]
)
noise_model = NoiseModel()
noise_model.add_quantum_error(coherent_error.compose(incoherent_error), "x", (0,))
noise_model.add_readout_error(readout_error, (0,))
options = {
"backend_options": {"noise_model": noise_model},
}
noisy_sampler = Sampler(options=options)
job = noisy_sampler.run(circuits, shots=400)
result = job.result()
plt.title("Noisy simulation")
ds = list(range(MAX_DEPTH + 1))
plt.plot(
ds,
[result[d].data.meas.expectation_values(["Z"]) for d in ds],
marker="o",
linestyle="-",
)
plt.hlines(0, xmin=0, xmax=MAX_DEPTH, colors="black")
plt.ylim(-1, 1)
plt.xlabel("Depth")
plt.ylabel("Measured <Z>")
plt.show()
2.8 보다 현실적인 잡음 시뮬레이션
from qiskit_aer import AerSimulator
from qiskit_ibm_runtime import SamplerV2 as Sampler, QiskitRuntimeService
service = QiskitRuntimeService()
real_backend = service.least_busy(
operational=True, simulator=False, min_num_qubits=127
) # Eagle
<IBMBackend('ibm_strasbourg')>
aer = AerSimulator.from_backend(real_backend)
noisy_sampler = Sampler(mode=aer)
job = noisy_sampler.run(circuits)
result = job.result()
plt.title("Noisy simulation with noise model from real backend")
ds = list(range(MAX_DEPTH + 1))
plt.plot(
ds,
[result[d].data.meas.expectation_values(["Z"]) for d in ds],
marker="o",
linestyle="-",
)
plt.hlines(0, xmin=0, xmax=MAX_DEPTH, colors="black")
plt.ylim(-1, 1)
plt.xlabel("Depth")
plt.ylabel("Measured <Z>")
plt.show()

3. 오류 완화를 적용한 실제 양자 계산
이 부분에서는 Qiskit Estimator를 사용하여 오류 완화된 결과(기대값)를 얻는 방법을 시연합니다. 1차원 Ising 모델의 시간 진화를 시뮬레이션하기 위한 6-Qubit Trotter화된 Circuit을 고려하여 시간 단계 수에 따라 오류가 어떻게 스케일링되는지 살펴봅니다.
backend = service.least_busy(
operational=True, simulator=False, min_num_qubits=127
) # Eagle
backend
<IBMBackend('ibm_strasbourg')>
NUM_QUBITS = 6
NUM_TIME_STEPS = list(range(8))
RX_ANGLE = 0.1
RZZ_ANGLE = 0.1
3.1 Circuit 구축
# Build circuits with different number of time steps
circuits = []
for n_steps in NUM_TIME_STEPS:
circ = QuantumCircuit(NUM_QUBITS)
for i in range(n_steps):
# rx layer
for q in range(NUM_QUBITS):
circ.rx(RX_ANGLE, q)
# 1st rzz layer
for q in range(1, NUM_QUBITS - 1, 2):
circ.rzz(RZZ_ANGLE, q, q + 1)
# 2nd rzz layer
for q in range(0, NUM_QUBITS - 1, 2):
circ.rzz(RZZ_ANGLE, q, q + 1)
circ.barrier() # need not to optimize the circuit
# Uncompute stage
for i in range(n_steps):
for q in range(0, NUM_QUBITS - 1, 2):
circ.rzz(-RZZ_ANGLE, q, q + 1)
for q in range(1, NUM_QUBITS - 1, 2):
circ.rzz(-RZZ_ANGLE, q, q + 1)
for q in range(NUM_QUBITS):
circ.rx(-RX_ANGLE, q)
circuits.append(circ)
이상적인 출력을 미리 알기 위해, 원래의 Circuit 가 적용되는 첫 번째 단계와 역 가 적용되는 두 번째 단계로 구성된 compute-uncompute Circuit을 사용합니다. 이러한 Circuit의 이상적인 결과는 자명하게도 입력 상태 이며, 임의의 Pauli 관측량에 대해 자명한 기대값을 갖습니다. 예를 들어 입니다.
# Print the circuit with 2 time steps
circuits[2].draw(output="mpl")
참고: 위와 같이, 개의 시간 단계를 갖는 Circuit은 개의 2-Qubit Gate 레이어를 가지게 됩니다.
obs = SparsePauliOp.from_sparse_list([("Z", [0], 1.0)], num_qubits=NUM_QUBITS)
obs
SparsePauliOp(['IIIIIZ'],
coeffs=[1.+0.j])
3.2 Circuit 트랜스파일
최적화(optimization_level=1)를 적용하여 Backend용으로 Circuit을 트랜스파일합니다.
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
pm = generate_preset_pass_manager(optimization_level=1, backend=backend)
isa_circuits = pm.run(circuits)
display(isa_circuits[2].draw("mpl", idle_wires=False, fold=-1))

3.3 Estimator를 사용한 실행 (서로 다른 복원력 레벨 적용)
복원력 레벨(estimator.options.resilience_level) 설정은 Qiskit Estimator를 사용할 때 오류 완화를 적용하는 가장 쉬운 방법입니다. Estimator는 다음과 같은 복원력 레벨을 지원합니다(2024/06/28 기준). 자세한 내용은 오류 완화 구성 가이드에서 확인할 수 있습니다.

from qiskit_ibm_runtime import Batch
from qiskit_ibm_runtime import EstimatorV2 as Estimator
jobs = []
job_ids = []
with Batch(backend=backend):
for resilience_level in [0, 1, 2]:
estimator = Estimator()
estimator.options.resilience_level = resilience_level
job = estimator.run(
[(circ, obs.apply_layout(circ.layout)) for circ in isa_circuits]
)
job_ids.append(job.job_id())
print(f"Job ID (rl={resilience_level}): {job.job_id()}")
jobs.append(job)
Job ID (rl=0): d146vcnmya70008emprg
Job ID (rl=1): d146vdnqf56g0081sva0
Job ID (rl=2): d146ven5z6q00087c61g
# check job status
for job in jobs:
print(job.status())
DONE
DONE
DONE
# REPLACE WITH YOUR OWN JOB IDS
jobs = [service.job(job_id) for job_id in job_ids]
# Get results
results = [job.result() for job in jobs]
3.4 결과 그래프
plt.title("Error mitigation with different resilience levels")
labels = ["0 (No mitigation)", "1 (TREX)", "2 (ZNE + Gate twirling)"]
steps = NUM_TIME_STEPS
for result, label in zip(results, labels):
plt.errorbar(
x=steps,
y=[result[s].data.evs for s in steps],
yerr=[result[s].data.stds for s in steps],
marker="o",
linestyle="-",
capsize=4,
label=label,
)
plt.hlines(
1.0, min(steps), max(steps), linestyle="dashed", label="Ideal", colors="black"
)
plt.xlabel("Time steps")
plt.ylabel("Mitigated <IIIIIZ>")
plt.legend()
plt.show()
4. (선택) 오류 완화 옵션 커스터마이즈
아래와 같이 옵션을 통해 오류 완화 기법의 적용 방식을 커스터마이즈할 수 있습니다.
# TREX
estimator.options.twirling.enable_measure = True
estimator.options.twirling.num_randomizations = "auto"
estimator.options.twirling.shots_per_randomization = "auto"
# Gate twirling
estimator.options.twirling.enable_gates = True
# ZNE
estimator.options.resilience.zne_mitigation = True
estimator.options.resilience.zne.noise_factors = [1, 3, 5]
estimator.options.resilience.zne.extrapolator = ("exponential", "linear")
# Dynamical decoupling
estimator.options.dynamical_decoupling.enable = True # Default: False
estimator.options.dynamical_decoupling.sequence_type = "XX"
# Other options
estimator.options.default_shots = 10_000
오류 완화 옵션에 대한 자세한 내용은 다음 가이드와 API 참조에서 확인할 수 있습니다.