주 콘텐츠로 건너뛰기

첫 번째 양자 실험

소개

아래 영상에서 Olivia Lanes가 이 레슨의 내용을 안내해 드립니다. 또는 별도 창에서 이 레슨의 YouTube 영상을 열 수도 있습니다.

이제 첫 번째 양자 Circuit을 실행하고 양자 컴퓨팅의 기초를 배웠습니다. 양자 상태가 어떻게 표현되는지, Gate가 그 상태에 어떻게 작용하는지, 그리고 중첩과 얽힘 같은 양자적 특성이 어떻게 관여하는지 이해했습니다. 이제 이 모든 것을 실제로 적용하여 양자 컴퓨터에서 첫 번째 문제를 풀어볼 차례입니다.

양자에 적합한 문제들의 더 넓은 영역은 이후 레슨에서 살펴볼 것입니다. 지금은 자연 시뮬레이션이라는 도메인의 한 문제에 집중하겠습니다. 양자 컴퓨터를 자연 양자 시스템의 더 깔끔하고 제어하기 쉬운 대안으로 사용하는 것입니다. 사실, 이것은 1980년대에 Richard Feynman이 양자 컴퓨터를 위해 처음 구상한 응용 분야였습니다. 그의 유명한 말처럼: "자연은 고전적이지 않습니다, 젠장, 그리고 자연 시뮬레이션을 만들고 싶다면 양자역학적으로 만들어야 합니다..."

이 레슨에서는 그 원칙을 따라 두 스핀 간의 상호작용을 시뮬레이션할 것입니다. 스핀을 작은 자석처럼 생각할 수 있습니다. 상호작용의 부호에 따라 같은 방향으로 정렬되거나 반대 방향으로 역정렬되려는 경향이 있습니다. 더 흥미롭고 도전적인 거동으로 이어지는 경우가 많기 때문에 후자의 경우에 초점을 맞추겠습니다. 이 작은 2-Qubit 시스템을 이해하고 나면, 동일한 아이디어가 어떻게 확장되어 양자 컴퓨터가 대규모 스핀 시스템을 시뮬레이션할 때 지수적 스케일링을 활용할 수 있는지 보여드리겠습니다.

상호작용하는 두 자석

이 문제에서는 모델의 각 스핀 하나에 Qubit 하나씩, 총 두 개의 Qubit을 사용합니다. 각 스핀은 위를 가리키거나(Qubit 상태 0|0\rangle), 아래를 가리키거나(Qubit 상태 1|1\rangle), 두 상태의 중첩에 있을 수 있습니다.

스핀들이 반강자성 상호작용을 한다면, 서로 역정렬하려는 경향이 있습니다. 하나가 위를 향하면 다른 하나는 아래를 향하려 하고, 그 반대도 마찬가지입니다.

이제 우리 시스템에 왼쪽에서 오른쪽으로 향하는 자기장이 있다고 가정합시다. 이 자기장은 스핀의 일반적인 위아래 방향과 교차하기 때문에 횡단 자기장이라고 합니다. 이 자기장은 스핀을 뒤집을 수 있어서, 가장 낮은 에너지 배치가 특정 단일 스핀 패턴이 아닌 위아래 스핀 배열의 특정 중첩이 됩니다.

이 모든 효과를 해밀토니안이라는 수학적 객체로 기술할 수 있습니다. 해밀토니안은 주어진 스핀 배열에 대한 시스템의 에너지를 알려줍니다:

H=JZ1Z0+hx(X1+X0)H = J Z_1 Z_0 + h_x (X_1 + X_0)

여기서 JJ는 스핀 간 상호작용 강도를 제어하는 계수이고, hxh_x는 외부 자기장 강도에 대한 계수입니다. Z1Z0Z_1 Z_0는 스핀이 정렬 또는 역정렬되었는지에 따라 보상하거나 벌칙을 부여하며, X0X_0X1X_1은 자기장의 스핀 뒤집기 효과를 나타냅니다.

물리학에서 시스템은 가장 낮은 에너지 상태인 바닥 상태로 안정화되는 경향이 있습니다. 이 최저 에너지 상태를 찾는 것은 일반적인 문제이지만, 이 레슨의 범위를 벗어나는 최적화 기법이 필요합니다.

대신, 우리는 더 간단한 질문을 할 것입니다: 스핀을 특정 상태로 준비했을 때, 그 상태의 에너지는 얼마인가요?

이를 답하기 위해 다음과 같이 하겠습니다:

  1. 스핀을 우리가 선택한 상태로 준비합니다
  2. 위의 해밀토니안을 사용하여 그 상태의 에너지를 측정합니다

이것은 이 레슨의 이후에 탐구할 수 있는 변분 알고리즘 같은 대형 양자 알고리즘 내부에서 나타나는 바로 그런 계산입니다.

Qiskit 구현

코드 작성에 앞서, 약간의 맥락 설정이 필요합니다. 양자 Circuit을 실행할 때, 항상 Qubit을 측정하는 것으로 끝납니다. 그런데 그 측정 결과에 대해 물을 수 있는 두 가지 다른 종류의 질문이 있습니다: 때로는 Qubit의 상태가 무엇인지 알고 싶고, 다른 때는 양자 상태가 주어졌을 때 에너지 같은 물리량의 값이 얼마인지 알고 싶습니다.

Qiskit에서는 이 두 종류의 질문을 프리미티브라는 두 가지 다른 도구로 처리합니다.

Sampler는 첫 번째 종류의 질문에 답합니다. Circuit을 여러 번 실행하고 00, 01, 10, 11 같이 가능한 각 결과를 얼마나 자주 측정하는지 알려줍니다. 결과는 각 측정 결과의 확률을 보여주는 히스토그램입니다.

Estimator는 두 번째 종류의 질문에 답합니다. 히스토그램 대신, 여러 측정을 내부적으로 결합하여 우리가 제공한 해밀토니안에 따른 상태의 에너지 같은 단일 숫자를 계산합니다.

각 도구를 언제, 왜 사용하는지 이해할 수 있도록, 동일한 2-Qubit 시스템에 적용된 두 가지 완전한 워크플로("Qiskit 패턴"이라고 함)를 살펴보겠습니다.

Qiskit 패턴 워크플로

Qiskit 패턴 워크플로는 Qiskit으로 양자 문제를 해결하는 데 사용하는 일반적인 프레임워크입니다. 양자 컴퓨팅 작업을 네 단계로 나눕니다:

  1. 양자 Circuit으로 표현할 수 있는 모델에 문제를 매핑합니다
  2. 특정 Backend에서 실행할 수 있도록 Circuit을 최적화합니다
  3. 선택된 Backend에서 최적화된 Circuit을 실행합니다
  4. 원시 측정 데이터를 후처리합니다

실험 1: Sampler를 사용하여 상태 측정하기

매핑

일반적으로, 매핑 단계는 실세계 문제를 Qubit, 연산자, 측정으로 표현하는 방법을 알아내는 단계입니다. 많은 응용 분야에서 이것이 워크플로에서 가장 까다롭고 복잡한 부분입니다 — "각 Qubit이 무엇을 나타내는가?"처럼 단순한 질문도 항상 직접적인 답이 있는 것은 아닙니다.

이 실험에서는 그러나 매핑이 의도적으로 단순합니다. 각 물리적 자유도가 단일 Qubit에 직접 매핑됩니다. 이 일대일 대응 때문에, 매핑 단계는 준비하려는 양자 상태를 선택하고 그 상태를 준비하고 측정하는 Circuit을 작성하는 것으로 축소됩니다.

여기서는 이 과정의 첫 번째 레슨에서 만든 것과 유사한 얽힘 Bell 상태를 준비하겠습니다:

Ψ=12(1001)\vert\Psi\rangle = \frac{1}{\sqrt{2}}(\vert 10\rangle - \vert 01\rangle)
# Added by doQumentation — required packages for this notebook
!pip install -q qiskit qiskit-ibm-runtime
# Import Qiskit primitives
from qiskit import QuantumCircuit

# Make state
qc = QuantumCircuit(2)
qc.h(0)
qc.cx(0, 1)
qc.x(1)
qc.z(0)

# Measure state
qc.measure_all()

# Draw circuit
qc.draw("mpl")

Output of the previous code cell

최적화

양자 컴퓨터(또는 이달의 실제 양자 컴퓨터 무료 시간을 다 쓴 경우 시뮬레이터)에서 Circuit을 실행하기 전에, 실행을 위해 준비해야 합니다. 이 단계를 최적화라고 합니다. (참고: 여기서 "최적화"라는 단어의 사용이 혼란스러울 수 있습니다. 양자 컴퓨팅에서 최적화 문제는 특정 종류의 문제를 의미합니다. 여기서는 모든 양자 Circuit이 하드웨어에서 효율적으로 실행되기 전에 거치는 필수 준비 단계를 설명하기 위해 최적화를 사용하고 있습니다.)

최적화 중에:

  1. Backend를 선택합니다 — 실제 양자 컴퓨터 또는 시뮬레이터.
  2. Circuit의 Qubit을 디바이스의 물리적 Qubit에 할당합니다.
  3. 양자 컴퓨터가 실제로 수행할 수 있는 Gate만을 사용하여 Circuit을 재작성합니다.
  4. 선택적으로 노이즈의 영향을 줄이기 위한 오류 완화 및 억제 기법을 구현합니다.

Qiskit에서 이는 Transpiler에 의해 자동으로 처리됩니다. Backend를 선택하면, Transpiler가 Circuit을 실행 준비 상태로 만드는 모든 작업을 수행하므로 Gate나 Qubit 할당을 수동으로 조정할 필요가 없습니다. Transpiler는 또한 필요한 경우 오류를 줄이는 데 도움이 되는 다양한 최적화 수준을 제공합니다. 최적화는 '패스'라는 단계로 수행됩니다. 따라서 이 최적화는 아래 코드의 pass_manager에 의해 처리됩니다. 오류 및 오류 완화에 대해 더 알아보려면 Olivia Lanes의 Quantum Computing in Practice 강좌를 참조하세요.

# Load the Qiskit Runtime service
from qiskit_ibm_runtime import QiskitRuntimeService

## Load the Qiskit Runtime service
# QiskitRuntimeService.save_account(
# channel="ibm_quantum_platform",
# token="YOUR_TOKEN_HERE",
# overwrite=True,
# set_as_default=True,
# )
# service = QiskitRuntimeService(channel="ibm_quantum_platform")

# Or load saved credentials
service = QiskitRuntimeService()

# Use the least busy backend, or uncomment the loading of a specific backend like "ibm_brisbane".
backend = service.least_busy(operational=True, simulator=False, min_num_qubits=127)
# backend = service.backend("ibm_brisbane")
print(backend.name)
ibm_fez
# Transpile the circuit and optimize for running on the quantum computer selected
# Step 2: Transpile
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager

target = backend.target
pm = generate_preset_pass_manager(target=target, optimization_level=3)
qc_isa = pm.run(qc)

qc_isa.draw("mpl")

Output of the previous code cell

실행

이제 실행할 준비가 되었습니다! Sampler를 로드한 다음 작업을 Backend로 전송하겠습니다.

# Load the Runtime primitive and session
from qiskit_ibm_runtime import SamplerV2 as Sampler

sampler = Sampler(mode=backend)

시뮬레이터를 사용하는 경우, 대신 이 셀의 주석을 해제하고 실행할 수 있습니다:

## Load the backend sampler
# from qiskit.primitives import BackendSamplerV2

## Load the Aer simulator and generate a noise model based on the currently-selected backend.
# from qiskit_aer import AerSimulator
# from qiskit_aer.noise import NoiseModel

# noise_model = NoiseModel.from_backend(backend)

## Define a simulator using Aer, and use it in Sampler.
# backend_sim = AerSimulator(noise_model=noise_model)
# sampler_sim = BackendSamplerV2(backend=backend_sim)

## Alternatively, load a fake backend with generic properties and define a simulator.
## backend_gen = GenericBackendV2(num_qubits=18)
## sampler_gen = BackendSamplerV2(backend=backend_gen)
job = sampler.run([qc_isa], shots=100)
# job = sampler_sim.run([qc_isa]) # uncomment if you want to run on a simulator
res = job.result()
counts = res[0].data.meas.get_counts()

후처리

from qiskit.visualization import plot_histogram

print("counts = ", counts)
plot_histogram(counts)
counts =  {'10': 49, '01': 50, '11': 1}

Output of the previous code cell

횟수의 대부분이 01 또는 10에 있음을 알 수 있습니다. 하나의 Qubit이 0으로 측정되면 다른 하나는 1이 되고, 그 반대도 마찬가지입니다. 이는 우리가 준비한 Bell 상태 Ψ\vert \Psi^- \rangle와 일치합니다.

실험 2: Estimator를 사용하여 에너지 측정하기

양자 상태를 샘플링하는 방법을 확인했으니, 이제 Estimator를 사용하여 Bell 상태 Ψ=12(0110)\vert \Psi^- \rangle = \frac{1}{\sqrt{2}}(\vert 01 \rangle - \vert 10 \rangle)의 에너지를 계산해 봅시다.

매핑

참고로, 시스템의 에너지는 해밀토니안이 포착하는 스핀 간 상호작용(JJ)과 외부 자기장(hxh_x)에 의해 결정됩니다:

H=JZ1Z0+hx(X1+X0)H = J Z_1 Z_0 + h_x (X_1 + X_0)

해밀토니안의 각 항은 특정 스핀 조합이 에너지에 어떻게 기여하는지 알려줍니다. Qiskit에서는 이 항들을 Qubit에 대한 간단한 동작의 레이블인 Pauli 연산자로 표현할 수 있습니다:

  • Z1Z0Z_1 Z_0는 두 Qubit 모두에 ZZ를 작용합니다.
  • X0X_0는 Qubit 0에 XX를 작용합니다.
  • X1X_1은 Qubit 1에 XX를 작용합니다.

Qiskit의 SparsePauliOp은 이 Pauli 연산자들을 수치 계수와 함께 저장하는 방법입니다. 이 Pauli 연산자들이 양자 컴퓨터가 측정하기를 원하는 관측량, 즉 시스템에 대한 정보를 알려주는 양입니다. Estimator를 사용하면 상태에서 각 관측량의 평균값을 계산하고, 이를 해밀토니안의 계수에 따라 결합하여 총 에너지를 구할 수 있습니다.

# Import Qiskit primitives
from qiskit.quantum_info import SparsePauliOp

# Parameters
J = 1.0 # antiferromagnetic coupling (J<0)
hx = -0.5 # transverse field strength

# 1. Define the Hamiltonian H = J Z1 Z2 + hx (X1 + X2)
obs = SparsePauliOp.from_list([("ZZ", J), ("XI", hx), ("IX", hx)])

# Make state
qc = QuantumCircuit(2)
qc.h(0)
qc.cx(0, 1)
qc.x(1)
qc.z(0)
<qiskit.circuit.instructionset.InstructionSet at 0x1387ed630>

코드에서 qc.measure_all() 줄을 생략했음을 주목하세요. Estimator를 사용할 때는 Circuit에서 측정할 위치를 지정할 필요가 없기 때문입니다. 어떤 관측량을 추정하고 싶은지만 알려주면, Qiskit이 내부적으로 측정을 처리합니다.

최적화

최적화 단계는 이전과 동일하게 진행되며, 관측량도 양자 컴퓨터가 이해할 수 있는 방식으로 작성되도록 합니다.

# Transpile the circuit and optimize for running on the quantum computer selected
# Step 2: Transpile
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager

target = backend.target
pm = generate_preset_pass_manager(target=target, optimization_level=3)
qc_isa = pm.run(qc)
obs_isa = obs.apply_layout(layout=qc_isa.layout)

qc_isa.draw("mpl")

Output of the previous code cell

실행

실행 단계에서는 Estimator를 로드한 다음, Circuit과 추정하려는 관측량 목록을 양자 컴퓨터로 전송합니다.

# Load the Runtime primitive and session
from qiskit_ibm_runtime import EstimatorV2 as Estimator

estimator = Estimator(mode=backend)
# Load the backend sampler

# noise_model = NoiseModel.from_backend(backend)

# Use Aer simulator in Estimator
# estimator_sim = BackendEstimatorV2(backend=backend_sim)

# Alternatively, load a fake backend with generic properties and define a simulator.
# backend_gen = GenericBackendV2(num_qubits=18)
# estimator_gen = BackendEstimatorV2(backend=backend_gen)
pubs = [(qc_isa, obs_isa)]
job = estimator.run([[qc_isa, obs_isa]])
res = job.result()

# Uncomment lines below to run the job on the Aer simulator with noise model from real backend
# job = estimator_sim.run([[qc_isa,obs_isa]])
# res=job.result()

후처리

마지막으로, 후처리 단계에서는 Estimator가 내부적으로 계산한 에너지를 출력합니다.

print(res[0].data.evs)
-0.9934112021453058

이것이 우리 상태의 에너지입니다!

결론

이 레슨에서는 두 상호작용하는 스핀을 나타내는 간단한 2-Qubit 양자 상태를 준비하는 방법을 배웠습니다. Sampler를 사용하여 측정 결과의 분포를 관찰하는 방법과 Estimator를 사용하여 해밀토니안에 따른 상태의 에너지를 계산하는 방법을 확인했습니다. 그 과정에서 해밀토니안이 스핀 간 상호작용과 외부 자기장의 효과를 어떻게 인코딩하는지, 그리고 서로 다른 상태가 어떻게 다른 에너지를 가질 수 있는지 살펴보았습니다.

다수 스핀으로의 확장

지금까지는 두 스핀만 살펴보았는데, 이는 손으로 직접 분석할 수 있을 만큼 단순합니다. 실제 물리적 시스템에서는, 자석이나 다른 복잡한 물질처럼, 종종 많은 상호작용하는 스핀이 있습니다. 스핀 수가 증가하면, 해밀토니안은 더 복잡해지고 최저 에너지 상태를 찾는 것이 훨씬 어려워집니다. 이것이 바로 양자 컴퓨터가 도움이 될 수 있는 곳입니다: 서로 다른 상태를 준비하고 에너지를 추정함으로써, 대형 시스템에서 고전 컴퓨터보다 더 효율적으로 저에너지 배치를 탐색할 수 있습니다.

이 실험의 자연스러운 확장은 더 많은 스핀을 나타내기 위해 Qubit 수를 늘리고, 스핀이 준비되는 방식을 조정하여 최저 에너지 상태를 찾으려는 시도입니다. 이 접근법은 Variational Quantum Algorithms 강좌에서 배울 수 있는 변분 방법의 본질입니다.

바닥 상태 에너지 연구에 대한 변분 기법을 넘어서는 다른 양자 접근법도 있습니다. 이 방법들은 여기서 다루지 않지만, 관심 있다면 Quantum Diagonalization Algorithms 강좌에서 소개됩니다.

학습 목표

실험 2의 처음으로 돌아가서 다른 중첩 상태로 다시 시도해 보세요. 우리가 사용한 것보다 더 낮은 에너지를 가진 상태를 찾을 수 있나요?

This translation based on the English version of 2026년 5월 7일