주 콘텐츠로 건너뛰기

양자 잡음과 오류 완화

참고

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를 사용하여 잡음 시뮬레이션을 수행하는 방법을 보여줍니다.

  1. 잡음 모델 구축
  2. 잡음 모델을 이용한 잡음 Sampler(시뮬레이터) 구축
  3. 잡음 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 UU가 적용되는 첫 번째 단계와 역 UU^\dagger가 적용되는 두 번째 단계로 구성된 compute-uncompute Circuit을 사용합니다. 이러한 Circuit의 이상적인 결과는 자명하게도 입력 상태 000000|000000\rangle이며, 임의의 Pauli 관측량에 대해 자명한 기대값을 갖습니다. 예를 들어 IIIIIZ=1\langle IIIIIZ \rangle = 1입니다.

# Print the circuit with 2 time steps
circuits[2].draw(output="mpl")

이전 코드 셀의 출력

참고: 위와 같이, kk개의 시간 단계를 갖는 Circuit은 4k4k개의 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을 Transpile합니다.

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를 사용한 실행 (서로 다른 resilience 레벨 적용)

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

image.png

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 참조에서 확인할 수 있습니다.