⚛️

量子コンピュータを用いて水素分子の基底状態のエネルギー固有値を求めてみた

に公開

はじめに

データソリューション事業部の鈴木です。
私は量子コンピュータに興味を持ち、ラボチームで基礎から学んでいます。
本記事では、量子化学計算の入門として、最もシンプルな分子である水素分子の基底状態のエネルギー固有値を量子アルゴリズムを用いて求め、その結果をシミュレータとIBMの量子コンピュータの実機で比較したいと思います。
水素分子のような単純な系で量子アルゴリズムを検証することは、将来、古典コンピュータでは扱いきれない複雑な分子の化学反応や物性を量子コンピュータでシミュレーションするための重要な一歩となります。

本記事はDAL Tech Blog Advent Calendar 2025として投稿しました。全ての記事は以下からご確認いただけます。

https://adventar.org/calendars/12288

水素分子の基底状態のエネルギーの固有値の求め方

今回は変分的量子固有値解法(VQE)を用いて、水素分子の基底状態のエネルギー固有値を求めます。VQEは量子回路毎のエネルギー期待値を量子コンピュータを用いた計算で取得し、エネルギー期待値が小さくなるように古典コンピュータでパラメータを変化させる、量子・古典のハイブリットなアルゴリズムです。VQEは以下の手順で計算を行います。

STEP.1 ハミルトニアンの記述

エネルギー固有値を求めるには、ハミルトニアンを記述する必要があります。まず、第一量子化形式のハミルトニアンを記述します。
第一量子化形式のハミルトニアンは以下の式で表されます。

H = -\sum_{i}\frac{\nabla_i^2}{2} -\sum_{i,\mu}\frac{Z_\mu}{\left|\mathbf{r}_i-\boldsymbol{\tau}_{\mu}\right|} +\frac{1}{2}\sum_{i,j}\frac{1}{\left|\mathbf{r}_i-\mathbf{r}_j\right|}

\mathbf{r}_ii番目の電子の位置、Z_\mu\boldsymbol{\tau}_{\mu}はそれぞれ原子\muの電荷、位置を表します。
第二量子化形式に変換します。

H = \sum_{p_1,p_2} t_{p_1,p_2}\, a_{p_1}^\dagger a_{p_2} + \sum_{p_1,p_2,p_3,p_4}\frac{{U_{p_1,p_2,p_3,p_4}}}{2}\, a_{p_1}^\dagger a_{p_2}^\dagger a_{p_4} a_{p_3}

a_p^\daggera_pp番目のスピン軌道の電子の生成・消滅演算子です。tUはそれぞれ一電子積分と二電子積分で第一量子化形式のハミルトニアンから計算します。

STEP.2 第二量子化ハミルトニアンを量子ビット形式に変換

第二量子化ハミルトニアンが記述できたら、生成・消滅演算子を量子ビット表現に変換します。変換方法の代表的なものとして、Jordan-Wigner変換とBravyi-Kitaev変換があります。Jordan-Wigner変換で量子ビット表現された生成・消滅演算子はそれぞれO(N)個のパウリ行列を含むのに対し、Bravyi-Kitaev変換ではO(\log{N})個となります。そのため、演算子の量子回路実装にはBravyi-Kitaev変換の方が有利です。
今回は水素分子という単純な系について考えており、必要な量子ビットは4つで済むので、Jordan-Wigner変換で変換しました。

STEP.3 参照状態とユニタリ結合クラスタ(UCC)法によるアンザッツの作成

回路の入力には参照状態として古典計算の結果を用います。参照状態や前述の一電子積分t、二電子積分Uを取得するには事前にHartree-Fock近似で古典計算を行う必要があります。この計算結果をもとに、入力の初期状態を決定します。初期状態が求まったら、ユニタリ結合クラスタ(UCC)法を用いてアンザッツを作成します。アンザッツとは入力の参照状態に作用させてより詳細な多電子基底を得るために導入するユニタリ演算子になります。VQEではこのアンザッツの中にパラメータ\thetaを含めた回路を構築します。

STEP.4 量子回路の実行とエネルギー期待値の取得

この部分が量子コンピュータを用いて行う処理になります。パラメータ\thetaを持つアンザッツを導入した量子回路を実行し、エネルギー期待値E(\theta)を取得します。

STEP.5 エネルギー期待値の最適化

量子コンピュータから取得したエネルギー期待値E(\theta)をもとに、E(\theta)が最小になるようにパラメータ\thetaを更新していきます。
今回はscipy.optimize.minimizeを用いて、エネルギー期待値E(\theta)の最小値を求めました。

コード

使用した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のライブラリの内容についても理解を深めることができました。
今後も量子アルゴリズムや量子コンピュータの実機について、勉強を進めていきたいと思います。

DAL Tech Blog

Discussion