量子コンピュータを用いて水素分子の基底状態のエネルギー固有値を求めてみた
はじめに
データソリューション事業部の鈴木です。
私は量子コンピュータに興味を持ち、ラボチームで基礎から学んでいます。
本記事では、量子化学計算の入門として、最もシンプルな分子である水素分子の基底状態のエネルギー固有値を量子アルゴリズムを用いて求め、その結果をシミュレータとIBMの量子コンピュータの実機で比較したいと思います。
水素分子のような単純な系で量子アルゴリズムを検証することは、将来、古典コンピュータでは扱いきれない複雑な分子の化学反応や物性を量子コンピュータでシミュレーションするための重要な一歩となります。
本記事はDAL Tech Blog Advent Calendar 2025として投稿しました。全ての記事は以下からご確認いただけます。
水素分子の基底状態のエネルギーの固有値の求め方
今回は変分的量子固有値解法(VQE)を用いて、水素分子の基底状態のエネルギー固有値を求めます。VQEは量子回路毎のエネルギー期待値を量子コンピュータを用いた計算で取得し、エネルギー期待値が小さくなるように古典コンピュータでパラメータを変化させる、量子・古典のハイブリットなアルゴリズムです。VQEは以下の手順で計算を行います。
STEP.1 ハミルトニアンの記述
エネルギー固有値を求めるには、ハミルトニアンを記述する必要があります。まず、第一量子化形式のハミルトニアンを記述します。
第一量子化形式のハミルトニアンは以下の式で表されます。
第二量子化形式に変換します。
STEP.2 第二量子化ハミルトニアンを量子ビット形式に変換
第二量子化ハミルトニアンが記述できたら、生成・消滅演算子を量子ビット表現に変換します。変換方法の代表的なものとして、Jordan-Wigner変換とBravyi-Kitaev変換があります。Jordan-Wigner変換で量子ビット表現された生成・消滅演算子はそれぞれ
今回は水素分子という単純な系について考えており、必要な量子ビットは4つで済むので、Jordan-Wigner変換で変換しました。
STEP.3 参照状態とユニタリ結合クラスタ(UCC)法によるアンザッツの作成
回路の入力には参照状態として古典計算の結果を用います。参照状態や前述の一電子積分
STEP.4 量子回路の実行とエネルギー期待値の取得
この部分が量子コンピュータを用いて行う処理になります。パラメータ
STEP.5 エネルギー期待値の最適化
量子コンピュータから取得したエネルギー期待値
今回はscipy.optimize.minimizeを用いて、エネルギー期待値
コード
使用したQiskitのバージョンとコードは以下になります。
- qiskit 2.2.3
- qiskit-nature 0.7.2
- qiskit-ibm-runtime 0.43.1
import numpy as np
from scipy.optimize import minimize
from qiskit_nature.units import DistanceUnit
from qiskit_nature.second_q.drivers import PySCFDriver
from qiskit_nature.second_q.mappers import JordanWignerMapper
from qiskit_nature.second_q.circuit.library import HartreeFock, UCCSD
# シミュレータ用
from qiskit.primitives import StatevectorEstimator
# 実機用
from qiskit_ibm_runtime import QiskitRuntimeService, EstimatorV2 as HWEstimator
from qiskit import transpile
# 実機/シミュレータを切り替え
USE_REAL_DEVICE = False # TrueでIBM Quantum実機を使用
backend = None
if USE_REAL_DEVICE:
service = QiskitRuntimeService(channel='ibm_quantum_platform',token = 'API_TOKEN')
backend = service.least_busy(operational=True, simulator=False)
print("Using real backend:", backend.name)
estimator = HWEstimator(
mode=backend,
options={"default_shots": 2048},
)
else:
estimator = StatevectorEstimator()
print("Using local StatevectorEstimator (statevector simulator).")
# ==================================================================================
# STEP.1 ハミルトニアンの記述
# ==================================================================================
# H2分子の電子構造
driver = PySCFDriver(
atom="H 0 0 0; H 0.735 0 0",
basis="sto3g",
unit=DistanceUnit.ANGSTROM,
)
problem = driver.run()
# H2分子の電子構造から第一量子化ハミルトニアンを作成
hamiltonian = problem.hamiltonian
# 第一量子化ハミルトニアンを第二量子化ハミルトニアンに変換
fermionic_op = hamiltonian.second_q_op()
# ==================================================================================
# STEP.2 第二量子化ハミルトニアンを量子ビット形式に変換
# ==================================================================================
# 第二量子化ハミルトニアンをJordan-Wigner変換を用いて量子ビット表現に変換
mapper = JordanWignerMapper()
qubit_op = mapper.map(fermionic_op)
num_spatial_orbitals = problem.num_spatial_orbitals
num_particles = problem.num_particles
# ==================================================================================
# STEP.3 参照状態とユニタリ結合クラスタ(UCC)法によるアンザッツの作成
# ==================================================================================
# HF 初期状態とUCCSDのansatzを作成
hf = HartreeFock(num_spatial_orbitals, num_particles, mapper)
ansatz = UCCSD(
num_spatial_orbitals,
num_particles,
mapper,
initial_state=hf,
)
num_params = ansatz.num_parameters
print("Ansatz parameters:", num_params)
# 実機の時だけbasis_gateに変換
if USE_REAL_DEVICE:
# backend がサポートしているゲート名を取得
if hasattr(backend, "configuration"):
basis_gates = backend.configuration().basis_gates
else:
basis_gates = list(backend.target.operation_names)
# basis_gates に分解
ansatz_isa = transpile(
ansatz,
basis_gates=basis_gates,
optimization_level=1,
)
print("Transpiled ansatz qubits:", ansatz_isa.num_qubits)
else:
ansatz_isa = ansatz
print("Observable qubits:", qubit_op.num_qubits)
energy_list = []
theta_list = []
# ==================================================================================
# STEP.4 量子回路の実行とエネルギー期待値の取得
# ==================================================================================
#Estimator に (回路, ハミルトニアン, パラメータ) を送り、エネルギー期待値を取得する関数
def energy_expectation(theta_array: np.ndarray) -> float:
job = estimator.run([(ansatz_isa, [qubit_op], theta_array)])
pub_result = job.result()[0]
value = float(pub_result.data.evs[0].real)
return value
def callback(theta_array: np.ndarray):
value = energy_expectation(theta_array)
energy_list.append(value)
theta_list.append(theta_array.copy())
theta = np.zeros(num_params)
print("Initial parameters:", theta)
# ==================================================================================
# STEP.5 エネルギー期待値の最適化
# ==================================================================================
opt_result = minimize(
energy_expectation,
theta,
method="SLSQP",
callback=callback,
options={"maxiter": 200, "ftol": 1e-8, "disp": True},
)
theta_opt = opt_result.x
e_elec = energy_expectation(theta_opt)
e_nuc = float(hamiltonian.nuclear_repulsion_energy)
e_total = e_elec + e_nuc
# 結果の出力
print("=== VQE Result ===")
print("Optimal parameters:", theta_opt)
print("Electronic energy (Hartree):", e_elec)
print("Nuclear repulsion (Hartree):", e_nuc)
print("Total energy (Hartree):", e_total)
print("Used real device:", USE_REAL_DEVICE)
結果
VQEの結果は以下の表の通りとなりました。
| ノイズなし シミュレータ (Hartree) |
IBQ Quantum (Hartree) |
Full CI (Hartree) |
|---|---|---|
| -1.1373 | - | -1.1373 |
Full CIは厳密解になります。ノイズなしのシミュレータとFull CIの値は一致しており、VQEで水素分子の基底状態のエネルギー固有値を求めることができています。一方で、実機でも実行したところ、私が使用できるインスタンスの時間内では解を得ることができませんでした。原因としてはVQEは1つのエネルギー期待値を取得するために何度も回路を実行し、そのうえでエネルギー期待値の最適化を行うため実行時間が長くなってしまうこと、そして実機にはノイズの影響があり、最適化に時間がかかってしまうことが考えられます。
Qiskitには実機のノイズを模したノイズありのシミュレータもあるので、そちらを使用して実行してみました。結果を以下の通りです。
| ノイズなし シミュレータ (Hartree) |
IBQ Quantum (Hartree) |
Full CI (Hartree) |
ノイズあり シミュレータ (Hartree) |
|---|---|---|---|
| -1.1373 | - | -1.1373 | -0.8228 |
ノイズありのシミュレータの結果はノイズなしシミュレータ及びFull CIの結果よりもエネルギーの値が大きくなっております。あくまで、シミュレータなので実機の場合はどうだったかはわかりませんが、最も単純な分子の結果ですら厳密解と一致しないほどノイズの影響を受けてしまうことがわかります。
ノイズモデル
ノイズモデルはqiskit-aer 0.17.2、qiskit_ibm_runtime.fake_provider.FakeFezを使用しました。
from qiskit_ibm_runtime.fake_provider import FakeFez
from qiskit_aer.primitives import EstimatorV2 as AerEstimator
from qiskit_aer.noise import NoiseModel
fake_backend = FakeFez()
noise_model = NoiseModel.from_backend(fake_backend)
estimator = AerEstimator(
options=dict(
backend_options=dict(
noise_model=noise_model,
coupling_map=fake_backend.coupling_map,
basis_gates=fake_backend.configuration().basis_gates,
),
run_options=dict(shots=2048),
)
)
おわりに
今回は水素分子の基底状態のエネルギー固有値をVQEを用いて計算しました。また、実機でも量子アルゴリズムを実行してみました。残念ながら、実機では計算結果を得ることはできませんでしたが、ノイズなしのシミュレータでは厳密解と一致する結果が得られました。
シミュレータのみでなく、実機での実行も通して、VQEについてやQiskitのライブラリの内容についても理解を深めることができました。
今後も量子アルゴリズムや量子コンピュータの実機について、勉強を進めていきたいと思います。
Discussion