주 콘텐츠로 건너뛰기

Qiskit Runtime으로 모두 함께 정리하기

요약

Victoria Lipinska가 지금까지 배운 내용을 최종 정리해 줍니다.

참고 자료

다음 영상에서 참고된 논문들입니다.

Qiskit 패턴으로 VQE 수행하기

VQE 계산을 위해 필요한 모든 구성 요소가 있습니다:

  • 해밀턴이안
  • 앤자츠
  • 고전 최적화기

이제 이들을 Qiskit 패턴 프레임워크에 함께 통합하면 됩니다.

1단계: 고전 입력을 양자 문제로 매핑하기

앞에서 말했듯이, 여기서는 적절하게 포맷된 관심 해밀턴이안이 이미 생성되었다고 가정합니다. 이에 대해 궁금하다면, 지침을 위해 해밀턴이안 구성 단원을 참고하세요. 아래 코드 블록은 이전 단원에서 설명한 구성 요소들을 설정합니다. 여기서 우리는 H2를 모델링하도록 선택했습니다. 그 이유는 그 해밀턴이안이 충분히 작아서 작성할 수 있기 때문입니다.

# Added by doQumentation — required packages for this notebook
!pip install -q numpy qiskit qiskit-aer qiskit-ibm-runtime scipy
# General imports
import numpy as np
from qiskit.quantum_info import SparsePauliOp

# Hamiltonian obtained from a previous lesson

H = SparsePauliOp(
[
"IIII",
"IIIZ",
"IZII",
"IIZI",
"ZIII",
"IZIZ",
"IIZZ",
"ZIIZ",
"IZZI",
"ZZII",
"ZIZI",
"YYYY",
"XXYY",
"YYXX",
"XXXX",
],
coeffs=[
-0.09820182 + 0.0j,
-0.1740751 + 0.0j,
-0.1740751 + 0.0j,
0.2242933 + 0.0j,
0.2242933 + 0.0j,
0.16891402 + 0.0j,
0.1210099 + 0.0j,
0.16631441 + 0.0j,
0.16631441 + 0.0j,
0.1210099 + 0.0j,
0.17504456 + 0.0j,
0.04530451 + 0.0j,
0.04530451 + 0.0j,
0.04530451 + 0.0j,
0.04530451 + 0.0j,
],
)

nuclear_repulsion = 0.7199689944489797

먼저 efficient_su2 회로와 최적화기 COBYLA를 선택합니다.

# Pre-defined ansatz circuit
from qiskit.circuit.library import efficient_su2

# SciPy minimizer routine
from scipy.optimize import minimize

# Plotting functions

# Random initial state and efficient_su2 ansatz
ansatz = efficient_su2(H.num_qubits, su2_gates=["rx"], entanglement="linear", reps=1)
x0 = 2 * np.pi * np.random.random(ansatz.num_parameters)
print(ansatz.decompose().depth())
ansatz.decompose().draw("mpl")
5

Output of the previous code cell

이제 비용 함수를 구성합니다. 이는 명백히 해밀턴이안과 관련이 있지만, 해밀턴이안은 연산자이고 우리가 원하는 것은 Estimator를 사용하여 그 연산자의 기댓값을 반환하는 함수라는 점에서 다릅니다. 물론, 앤자츠와 변분 매개변수를 사용하여 이를 수행하므로, 모두 인수로 나타납니다. 아래에서는 실제 하드웨어나 시뮬레이터에서 사용할 약간 다른 버전들을 정의합니다.

def cost_func(params, ansatz, H, estimator):
pub = (ansatz, [H], [params])
result = estimator.run(pubs=[pub]).result()
energy = result[0].data.evs[0]
return energy

# def cost_func_sim(params, ansatz, H, estimator):
# energy = estimator.run(ansatz, H, parameter_values=params).result().values[0]
# return energy

2단계: 양자 실행을 위해 문제를 최적화하기

코드가 사용하는 하드웨어에서 최대한 효율적으로 실행되기를 원합니다. 따라서 최적화 단계를 시작하기 위해 Backend를 선택해야 합니다. 아래 코드는 귀하가 이용할 수 있는 가장 한가한 Backend를 선택합니다.

# To run on hardware, select the backend with the fewest number of jobs in the queue
from qiskit_ibm_runtime import QiskitRuntimeService

service = QiskitRuntimeService(channel="ibm_quantum_platform")
backend = service.least_busy(operational=True, simulator=False)
backend.name

회로를 실제 Backend에서 실행하기 위해 최적화하는 것은 풍부하고 중요한 주제입니다. 하지만 VQE에만 국한되지 않습니다. 지금은 두 가지 중요한 용어를 상기시켜 드리겠습니다:

  • optimization_level: 선택한 Backend의 레이아웃에 맞춰진 회로 정도를 설명합니다. 가장 낮은 최적화 수준은 회로를 장치에서 실행하는 데 필요한 최소한의 작업만 수행합니다. 회로 Qubit을 장치 Qubit에 매핑하고 모든 2-Qubit 연산을 허용하도록 Swap Gate를 추가합니다. 가장 높은 최적화 수준은 훨씬 더 스마트하고 전체 Gate 개수를 줄이기 위해 많은 트릭을 사용합니다. 2-Qubit Gate는 높은 오류율을 가지고 있고 Qubit은 시간에 따라 손상되므로, 더 짧은 회로가 더 나은 결과를 제공해야 합니다.
  • 동적 이중화 (Dynamical Decoupling): 유휴 Qubit에 Gate 수열을 적용할 수 있습니다. 이는 환경과의 원치 않는 상호작용 중 일부를 상쇄합니다. 회로 최적화에 대한 자세한 정보는 연결된 문서를 참고하세요. 아래 코드는 qiskit.transpiler의 사전 설정된 Pass Manager를 사용하여 Mass Manager를 생성합니다.
from qiskit.transpiler import PassManager
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit.transpiler.passes import (
ALAPScheduleAnalysis,
PadDynamicalDecoupling,
ConstrainedReschedule,
)
from qiskit.circuit.library import XGate

target = backend.target
pm = generate_preset_pass_manager(target=target, optimization_level=3)
pm.scheduling = PassManager(
[
ALAPScheduleAnalysis(target=target),
ConstrainedReschedule(
acquire_alignment=target.acquire_alignment,
pulse_alignment=target.pulse_alignment,
target=target,
),
PadDynamicalDecoupling(
target=target,
dd_sequence=[XGate(), XGate()],
pulse_alignment=target.pulse_alignment,
),
]
)

# Use the pass manager and draw the resulting circuit
ansatz_isa = pm.run(ansatz)
ansatz_isa.draw(output="mpl", idle_wires=False, style="iqp")

Output of the previous code cell

마찬가지로 해밀턴이안에 장치 레이아웃 특성을 적용해야 합니다.

hamiltonian_isa = H.apply_layout(ansatz_isa.layout)
hamiltonian_isa
SparsePauliOp(['IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIZIII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIIII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIZII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZIII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZZII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIZII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIYYYYII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIYYXXII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIXXYYII', 'IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIXXXXII'],
coeffs=[-0.09820182+0.j, -0.1740751 +0.j, -0.1740751 +0.j, 0.2242933 +0.j,
0.2242933 +0.j, 0.16891402+0.j, 0.1210099 +0.j, 0.16631441+0.j,
0.16631441+0.j, 0.1210099 +0.j, 0.17504456+0.j, 0.04530451+0.j,
0.04530451+0.j, 0.04530451+0.j, 0.04530451+0.j])

3단계: Qiskit Primitives를 사용하여 실행하기

선택한 하드웨어에서 실행하기 전에, 간단한 디버깅과 때로는 오류 추정을 위해 시뮬레이터를 사용하는 것이 좋은 생각입니다. 이러한 이유로, VQE를 시뮬레이터에서 실행하는 방법을 간단히 살펴봐요. 하지만 고전 컴퓨터, 시뮬레이터 또는 GPU도 고도로 얽힌 127-Qubit 양자 컴퓨터의 전체 기능을 정확하게 시뮬레이션할 수 없다는 점은 매우 중요합니다. 현재의 양자 유용성 시대에, 시뮬레이터의 사용은 제한적입니다.

변분 회로의 각 매개변수 선택에 대해 기댓값을 계산해야 한다는 점을 기억하세요. 최소화할 값이기 때문입니다. 이미 짐작하셨을 수도 있듯이, 가장 효율적인 방법은 Qiskit Primitive인 Estimator를 사용하는 것입니다. 로컬 시뮬레이터를 사용하면서 시작하겠습니다. 로컬 버전의 Estimator인 BackendEstimator를 사용해야 합니다.

최적화에 사용한 실제 Backend를 유지하면서, 로컬 시뮬레이터와 함께 사용할 그 장치의 노이즈 동작 모델을 가져올 수 있습니다. 여기서는 aer_simulator_statevector를 사용합니다.

# We will start by using a local simulator
from qiskit_aer import AerSimulator

# Import an estimator, this time from qiskit (we will import from Runtime for real hardware)
from qiskit.primitives import BackendEstimatorV2

# generate a simulator that mimics the real quantum system
backend_sim = AerSimulator.from_backend(backend)
estimator = BackendEstimatorV2(backend=backend_sim)

이제 VQE를 구현할 때입니다. 선택한 해밀턴이안, 앤자츠, 고전 최적화기, 그리고 실제 Backend를 기반으로 한 BackendEstimator를 사용하여 비용 함수를 최소화합니다. 여기서는 최대 반복 횟수로 상대적으로 작은 수를 선택했다는 점에 유의하세요. 이는 시뮬레이터를 디버깅에만 사용하고 있기 때문입니다. VQE 최적화 단계는 종종 수렴하기까지 수백 번의 반복이 필요합니다.

res = minimize(
cost_func,
x0,
args=(ansatz_isa, hamiltonian_isa, estimator),
method="cobyla",
options={"maxiter": 10, "disp": True},
)

print(getattr(res, "fun") - nuclear_repulsion)
print(res)
Return from COBYLA because the objective function has been evaluated MAXFUN times.
Number of function values = 10 Least value of F = -0.11556938907226563
The corresponding X is:
[4.11796514 4.52126324 0.69570423 4.12781503 6.55507846 1.80713073
0.9645473 6.23812214]

-0.8355383835212453
message: Return from COBYLA because the objective function has been evaluated MAXFUN times.
success: False
status: 3
fun: -0.11556938907226563
x: [ 4.118e+00 4.521e+00 6.957e-01 4.128e+00 6.555e+00
1.807e+00 9.645e-01 6.238e+00]
nfev: 10
maxcv: 0.0

이 코드는 올바르게 평가되었지만 수렴하지 않았습니다. 이는 우리가 예상한 바입니다. 실제 하드웨어에서 계산을 실행한 후 출력을 논의할 것입니다. 실제 Backend의 경우 Qiskit Runtime Estimator를 사용합니다. 이를 Qiskit Runtime Session 내에서 실행하고 그 Session에 대한 옵션을 지정하고 싶을 것입니다.

from qiskit_ibm_runtime import QiskitRuntimeService, Session
from qiskit_ibm_runtime import EstimatorV2 as Estimator
from qiskit_ibm_runtime.options import EstimatorOptions

다른 것들 중에서도, Session을 사용하는 것은 우리의 작업이 시작하기 위해 오직 한 번만 대기열에 있다는 의미입니다. 고전 최적화기의 후속 반복은 대기열에 들어가지 않습니다. Session에서 복원력 및 최적화 수준을 설정할 수 있습니다. 이 도구들은 충분히 중요해서 각각의 간단한 검토와 VQE에서의 중요성을 포함하고 있으며, 더 많은 정보를 배우기 위한 링크를 포함하고 있습니다:

  • Runtime Session: VQE는 본질적으로 반복적이며, 고전 최적화기가 새로운 변분 매개변수를 선택하고, 따라서 각 후속 시도에서 새로운 Gate가 사용됩니다. Session을 사용하지 않으면, 이는 각 시도 회로 사이에 추가 대기열 시간을 초래할 수 있습니다. VQE 계산을 Session 내에 캡슐화하면 작업 시작 전에만 하나의 초기 대기열이 발생하지만, 변분 단계 사이에는 추가 대기열 시간이 없습니다. 이 전략은 이미 이전 단원의 예에서 사용되었지만, 기하학적 변동을 할 때 훨씬 더 중요한 역할을 할 수 있습니다. Session에 대한 자세한 정보는 실행 모드 문서를 검토하세요.
  • Estimator의 내장 최적화: Estimator에는 계산 최적화를 위한 내장 옵션이 있습니다. 많은 맥락에서 (Estimator 포함), 설정은 0과 1로 제한되며, 0은 최적화 없음을, 1 (기본값)은 선택한 하드웨어에 대한 회로의 일부 최적화를 나타냅니다. 다른 일부 맥락에서는 0, 1, 2, 또는 3의 설정을 허용합니다. 다양한 설정에서 사용되는 특정 방법에 대한 자세한 정보는 문서를 참고하세요. 여기서, 우리는 실제로 최적화를 0으로 설정하고 'skip_transpilation = true'를 사용할 것입니다. 왜냐하면 우리는 위의 최적화 섹션에서 Pass Manager를 사용하여 이미 우리의 회로를 Transpile했기 때문입니다.
  • Estimator의 내장 복원력: 최적화와 마찬가지로, Estimator는 오류에 대한 복원력을 위한 내장 설정을 가지고 있으며, 다양한 오류 완화 접근 방식에 해당합니다. 복원력 수준 설정에 대해 배우려면 문서를 참고하세요.

오류 완화는 VQE 계산의 수렴에서 미묘한 역할을 한다는 점은 주목할 가치가 있습니다. 고전 최적화기는 에너지를 최소화하는 매개변수를 찾기 위해 매개변수 공간을 검색합니다. 최적 매개변수에서 매우 멀리 떨어져 있을 때, 오류가 있는 경우에도 고전 최적화기에는 가파른 기울기가 명확할 수 있습니다. 하지만 계산이 수렴하고 최적 값에 접근할수록 기울기가 더 작아지고 오류에 의해 더 쉽게 씻겨 나갑니다. 얼마나 많은 오류 완화를 사용하시겠습니까? 수렴의 어느 지점에서? 이들은 특정 사용 사례에 대해 결정해야 할 선택입니다.

이 첫 번째 하드웨어 실행의 경우, 우리는 상대적으로 빠른 실행을 용이하게 하기 위해 복원력을 0으로 설정했습니다. 어떤 심각한 응용 프로그램의 경우, 오류 완화를 사용하고 싶을 것입니다. 아래 셀에는 (1) Runtime Session을 위한 옵션 (우리가 "session_options"라고 명명한)과 (2) 고전 최적화기를 위한 옵션 (여기서는 단순히 "options"라고 불림)의 두 가지 옵션 세트가 있다는 점에 유의하세요.

estimator_options = EstimatorOptions(resilience_level=0, default_shots=2000)
with Session(backend=backend) as session:
estimator = Estimator(mode=session, options=estimator_options)

res = minimize(
cost_func,
x0,
args=(ansatz_isa, hamiltonian_isa, estimator),
method="cobyla",
options={"maxiter": 10, "disp": True},
)
Return from COBYLA because the objective function has been evaluated MAXFUN times.
Number of function values = 10 Least value of F = -0.11691688904
The corresponding X is:
[5.11796514 5.52126324 0.69570423 5.12781503 6.55507846 1.80713073
1.9645473 6.23812214]

IBM Quantum® Platform의 Workloads 아래에서 작업의 진행 상황을 볼 수 있습니다.

print(getattr(res, "fun") - nuclear_repulsion)
print(res)
-0.8368858834889796
message: Return from COBYLA because the objective function has been evaluated MAXFUN times.
success: False
status: 3
fun: -0.11691688904
x: [ 5.118e+00 5.521e+00 6.957e-01 5.128e+00 6.555e+00
1.807e+00 1.965e+00 6.238e+00]
nfev: 10
maxcv: 0.0

4단계: 후처리, 고전 형식으로 결과 반환하기

이러한 출력을 이해하는지 확인하기 위해 잠시 시간을 가져봅시다. "fun" 출력은 비용 함수에 대해 얻은 최소 값입니다 (반드시 마지막으로 계산된 값은 아님). 이것은 양수 핵 반발을 포함하는 전체 에너지이며, 이것이 우리가 전자_에너지도 정의한 이유입니다.

위의 경우, 우리는 최대 함수 평가 횟수를 초과했다는 메시지를 가지고 있으며, 함수 평가의 수 (nfev)는 10이었습니다. 이는 단순히 최적화의 다른 수렴 기준이 충족되지 않았다는 의미입니다. 즉, 바닥 상태 에너지를 찾았다고 생각할 이유가 없습니다. 이는 success가 "False"인 의미이기도 합니다.

마지막으로, x가 있습니다. 이는 변분 매개변수의 벡터입니다. 이들은 최소 비용 함수 (에너지 기댓값)를 산출한 계산에 사용된 매개변수입니다. 이 8개의 값은 앤자츠에서 변수 회전 각도를 취하는 Gate의 8개 회전 각도에 해당합니다.

축하합니다! IBM Quantum QPU에서 VQE 계산을 실행했습니다!

다음 단원에서는 해밀턴이안에 변수를 포함하도록 이 작업 흐름을 조정하는 방법을 살펴봅시다. 양자 화학 문제의 맥락에서, 이는 분자의 모양이나 결합 사이트를 결정하기 위해 기하학을 변동시키는 것을 의미할 수 있습니다.

import qiskit
import qiskit_ibm_runtime

print(qiskit.version.get_version_info())
print(qiskit_ibm_runtime.version.get_version_info())
2.1.0
0.40.1