量子化学のためのハミルトニアン
まず、VQEにおいてハミルトニアンが果たす役割について簡単に説明しよう。
VQE のハミルトニアン概要
ビクトリア・リピンスカ博士が、ハミルトニアンと、量子コンピューティングで使用するためのハミルトニアンのマッピング方法について解説する。
参照
上記のビデオでは以下の記事が参照されている。
- フェルミオンシミュレーションのための量子アルゴリズム、Ortiz, et al.
- 量子コンピュータを使った化学のシミュレーション、Kassal et al.
- 量子化学の量子シミュレーションにおけるBravyi-Kitaev変換とJordan-Wigner変換の比較、Tranter, et al.
- 量子コンピューティング時代の量子化学、Cao, et al.
- 量子計算化学、 McArdle, 他。
- 電子構造の量子計算のためのブラヴィー・キタエフ変換、シーリーら。
量子化学のためのハミルトニアンの準備
量子コンピュータを化学の問題に応用するための最初のステップは、対象となる系のハミルトニアンを定義することである。 ここでは、量子化学のハミルトニアンに関する議論に限定する。ハミルトニアンは、同一のフェルミオン系に特有の写像を必要とするからである。
量子化学の研究者であれば、分子をモデリングするためのお気に入りのソフトウェアをすでにお持ちで、目的の系を記述するハミルトニアンを生成できることだろう。 ここでは、 PySCF, numpyとQiskitのみで構築されたコードを使用する。 しかし、ハミルトニアンの調製プロセスは、パッケージ化された溶液にも適用される。 既存のワークフローとの統合を容易にするため、「サードパーティソフトウェア」のサブセクションでその一部を説明している。
IBM Quantum® QPUで使用する量子化学ハミルトニアンの生成には、以下の手順が必要です:
- 分子を定義する(形状、スピン、活性空間など)
- フェルミオンのハミルトニアン(生成・消滅演算子)を生成する
- フェルミオンのハミルトニアンからボソニック作用素への写像(この文脈ではパウリ作用素を使う)
- サードパーティソフトウェアを使用する場合:生成ソフトウェアとQiskitの間の構文の不一致を処理する
フェルミオンのハミルトニアンはフェルミオンの作用素で書かれ、特に電子が区別できないフェルミオンであることを考慮している。 つまり、区別可能なボソニック量子ビットとはまったく異なる統計に従うということだ。 それゆえ、マッピング・プロセスが必要なのだ。
これらのプロセスをすでにご存知の方は、このセクションを読み飛ばしてもいいだろう。
目標:
最終的な目標は、次のような形のハミルトニアンを得ることである:
H = [(1, "XX"), (1, "YY"), (1, "ZZ")]
print(H)Output:
[(1, 'XX'), (1, 'YY'), (1, 'ZZ')]
それとも
from qiskit.quantum_info import SparsePauliOp
H = SparsePauliOp(["XX", "YY", "ZZ"], coeffs=[1.0 + 0.0j, 1.0 + 0.0j, 1.0 + 0.0j])
print(H)Output:
SparsePauliOp(['XX', 'YY', 'ZZ'],
coeffs=[1.+0.j, 1.+0.j, 1.+0.j])
まずはいくつかのパッケージをインポートすることから始めよう:
import numpy as np
from pyscf import ao2mo, gto, mcscf, scf- 分子を定義する
ここでは対象分子の属性を指定する。 この例では、二原子水素を選びました(結果のハミルトニアンが表示するのに十分短いからです)。
Python -based Simulations of Chemistry Framework ( PySCF )には、量子計算に適した分子ハミルトニアンの生成などに使用できる電子構造モジュールの幅広いコレクションがある。 PySCF クイックスタートガイドは、すべての変数と機能の完全な説明のための優れたリソースです。 すでにご存知の方も多いだろうから、ここではごく簡単な概要にとどめる。 これらの詳細については、 PySCF をご覧ください 簡単に説明する:
距離は二原子分子に使うこともできるし、単に各原子のデカルト座標を指定することもできる。 距離の単位はオングストローム。
gtoはガウシアンタイプの軌道を生成する。
basisとは、分子軌道をモデル化するために使われる関数のこと。 ここで' sto-6g ' は一般的な最小基底で、6つのプリミティブなガウス軌道を用いてスレーター型軌道をフィッティングするために名付けられた。
spin 不対電子の数を示す整数値 ( と等しい)。ソフトウェアによっては多重度を代わりに使うものもあるので注意( )。
分子の電荷。
symmetry - 分子の点対称グループ。文字列で指定するか、"symmetry = True "を設定することで自動的に検出される。 ここで "Dooh "は、同じ原子種を2つ持つ二原子分子に適した対称基である。
distance = 0.735
a = distance / 2
mol = gto.Mole()
mol.build(
verbose=0,
atom=[
["H", (0, 0, -a)],
["H", (0, 0, a)],
],
basis="sto-6g",
spin=0,
charge=0,
symmetry="Dooh",
)Output:
<pyscf.gto.mole.Mole at 0x7fc718f07610>
全エネルギー(電子エネルギーだけでなく核反発エネルギーも含む)、全電子軌道エネルギー、または電子軌道のある部分集合のエネルギー(相補的な部分集合は凍結)を記述できることに留意してほしい。 の場合、以下の異なるエネルギーに注目し、全エネルギーから核反発エネルギーを差し引いたものが、実際には電子エネルギーになることに注意されたい:
mf = scf.RHF(mol)
mf.scf()
print(
mf.energy_nuc(),
mf.energy_elec()[0],
mf.energy_tot(),
mf.energy_tot() - mol.energy_nuc(),
)Output:
0.7199689944489797 -1.8455976628764188 -1.125628668427439 -1.8455976628764188
active_space = range(mol.nelectron // 2 - 1, mol.nelectron // 2 + 1)- フェルミオンのハミルトニアンを生成する
scfとは、自己無撞着場法の広範な範囲を指す。
mfの rhf = scf.RHF (mol)は、Restricted Hartree Fock計算を使用するソルバーである。 この核(以下E)は、核反発と分子軌道を含む全エネルギーである。
mcscfはマルチ・コンフィギュレーション自己無撞着フィールド・パッケージである。
ao2mo は原子軌道から分子軌道への変換である。
また、以下の変数も使用する:
ncas :完全な活性空間の軌道数
nelecas :完全な活性空間の電子数
E1 = mf.kernel()
mx = mcscf.CASCI(mf, ncas=2, nelecas=(1, 1))
mo = mx.sort_mo(active_space, base=0)
E2 = mx.kernel(mo)[:2]我々はハミルトニアンを求めており、これはしばしば電子コアのエネルギー(ecore、最小化には関与しない)、単電子演算子( h1e )、および2電子エネルギー( h2e )に分けられる。 以下の最後の2行に、これらが明示的に抜粋されている。
h1e, ecore = mx.get_h1eff()
h2e = ao2mo.restore(1, mx.get_h2eff(), mx.ncas)これらのハミルトニアンは現在、フェルミオンの(生成と消滅)作用素であり、(区別できない)フェルミオンの系に適用され、交換のもとでは反対称性を持つ。 この結果、区別可能な系やボソニック系とは異なる静力学が適用されることになる。 IBM Quantum QPUで計算を実行するには、エネルギーを記述するボソニック演算子が必要である。 このような写像の結果は、慣例的にパウリ作用素で書かれる。パウリ作用素はエルミートでユニタリーだからだ。 使えるマッピングはいくつかある。 最も単純なもののひとつに、Jordan Wigner変換がある。
- ハミルトニアンのマッピング
化学的ハミルトニアンを量子コンピュータで実行するのに適したものにマッピングするためのツールはたくさんある。 ここでは、 PySCF, numpyとQiskitのみを使用して、Jordan Wignerマッピングを直接実装します。 他の解決策に関するシンタックスの考慮点については後述する。
コレスキー関数は、ハミルトニアンの2電子項の低ランク分解を得るのに役立つ。
def cholesky(V, eps):
# see https://arxiv.org/pdf/1711.02242.pdf section B2
# see https://arxiv.org/abs/1808.02625
# see https://arxiv.org/abs/2104.08957
no = V.shape[0]
chmax, ng = 20 * no, 0
W = V.reshape(no**2, no**2)
L = np.zeros((no**2, chmax))
Dmax = np.diagonal(W).copy()
nu_max = np.argmax(Dmax)
vmax = Dmax[nu_max]
while vmax > eps:
L[:, ng] = W[:, nu_max]
if ng > 0:
L[:, ng] -= np.dot(L[:, 0:ng], (L.T)[0:ng, nu_max])
L[:, ng] /= np.sqrt(vmax)
Dmax[: no**2] -= L[: no**2, ng] ** 2
ng += 1
nu_max = np.argmax(Dmax)
vmax = Dmax[nu_max]
L = L[:, :ng].reshape((no, no, ng))
print(
"accuracy of Cholesky decomposition ",
np.abs(np.einsum("prg,qsg->prqs", L, L) - V).max(),
)
return L, ng関数 identity と creators_destructors は、フェルミオンのハミルトニアンにおける生成と消滅の作用素をパウリ作用素に置き換えている。 creators_destructors は、ヨルダン-ウィグナー写像を用いている。
def identity(n):
return SparsePauliOp.from_list([("I" * n, 1)])
def creators_destructors(n, mapping="jordan_wigner"):
c_list = []
if mapping == "jordan_wigner":
for p in range(n):
if p == 0:
ell, r = "I" * (n - 1), ""
elif p == n - 1:
ell, r = "", "Z" * (n - 1)
else:
ell, r = "I" * (n - p - 1), "Z" * p
cp = SparsePauliOp.from_list([(ell + "X" + r, 0.5), (ell + "Y" + r, -0.5j)])
c_list.append(cp)
else:
raise ValueError("Unsupported mapping.")
d_list = [cp.adjoint() for cp in c_list]
return c_list, d_list最後に、 build_hamiltonian は、 cholesky、 identity、 creators_destructors 関数を用いて、量子コンピューター上で実行するのに適した最終的なハミルトニアンを作成する。
def build_hamiltonian(ecore: float, h1e: np.ndarray, h2e: np.ndarray) -> SparsePauliOp:
ncas, _ = h1e.shape
C, D = creators_destructors(2 * ncas, mapping="jordan_wigner")
Exc = []
for p in range(ncas):
Excp = [C[p] @ D[p] + C[ncas + p] @ D[ncas + p]]
for r in range(p + 1, ncas):
Excp.append(
C[p] @ D[r]
+ C[ncas + p] @ D[ncas + r]
+ C[r] @ D[p]
+ C[ncas + r] @ D[ncas + p]
)
Exc.append(Excp)
# low-rank decomposition of the Hamiltonian
Lop, ng = cholesky(h2e, 1e-6)
t1e = h1e - 0.5 * np.einsum("pxxr->pr", h2e)
H = ecore * identity(2 * ncas)
# one-body term
for p in range(ncas):
for r in range(p, ncas):
H += t1e[p, r] * Exc[p][r - p]
# two-body term
for g in range(ng):
Lg = 0 * identity(2 * ncas)
for p in range(ncas):
for r in range(p, ncas):
Lg += Lop[p, r, g] * Exc[p][r - p]
H += 0.5 * Lg @ Lg
return H.chop().simplify()最後に、 build_hamiltonian 、Jordan-Wigner変換を用いてパウリ演算子から量子ビット・ハミルトニアンを構築する。 これは、我々が使用したコレスキー分解の精度も示している。
H = build_hamiltonian(ecore, h1e, h2e)
print(H)Output:
accuracy of Cholesky decomposition 2.220446049250313e-16
SparsePauliOp(['IIII', 'IIIZ', 'IZII', 'IIZI', 'ZIII', 'IZIZ', 'IIZZ', 'ZIIZ', 'IZZI', 'ZZII', 'ZIZI', 'YYYY', 'XXYY', 'YYXX', 'XXXX'],
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])
この分子ノートブック例では、さまざまな複雑さの分子のセットアップとハミルトニアンを示しています。少し修正すれば、ほとんどの低分子を調べることができるでしょう。
分子のフェルミオン作用素を構成する際に考慮すべき2つの重要なポイントについて簡単に触れておこう。 分子の種類が変われば、対称性も変わる。 同様に、円筒対称の" A1 "のように、様々な対称性を持つ軌道の数も変化する。 これらの変化は、ここにあるように LiH, :
distance = 1.56
mol = gto.Mole()
mol.build(
verbose=0,
atom=[["Li", (0, 0, 0)], ["H", (0, 0, distance)]],
basis="sto-6g",
spin=0,
charge=0,
symmetry="Coov",
)
mf = scf.RHF(mol)
E1 = mf.kernel()
# %% ----------------------------------------------------------------------------------------------
mx = mcscf.CASCI(mf, ncas=5, nelecas=(1, 1))
cas_space_symmetry = {"A1": 3, "E1x": 1, "E1y": 1}
mo = mcscf.sort_mo_by_irrep(mx, mf.mo_coeff, cas_space_symmetry)
E2 = mx.kernel(mo)[:2]
h1e, ecore = mx.get_h1eff()
h2e = ao2mo.restore(1, mx.get_h2eff(), mx.ncas)また、最終的に得られるハミルトニアンの直感をすぐに失ってしまうことも注目に値する。 LiH (ヨルダン・ウィグナー・マッパーを使用)のハミルトニアンは、すでに276項で構成されている。
len(build_hamiltonian(ecore, h1e, h2e))Output:
accuracy of Cholesky decomposition 1.1102230246251565e-16
276
対称性に疑問がある場合は、 symmetry = True と verbose = 4 を設定することで、分子の対称性情報を生成することもできる:
distance = 1.56
mol = gto.Mole()
mol.build(
verbose=4,
atom=[["Li", (0, 0, 0)], ["H", (0, 0, distance)]],
basis="sto-6g",
spin=0,
charge=0,
symmetry=True,
)Output:
System: uname_result(system='Linux', node='IBM-R912JTRT', release='5.10.102.1-microsoft-standard-WSL2', version='#1 SMP Wed Mar 2 00:30:59 UTC 2022', machine='x86_64') Threads 16
Python 3.11.12 (main, May 16 2025, 02:33:32) [GCC 11.4.0]
numpy 2.3.1 scipy 1.16.0 h5py 3.14.0
Date: Mon Jun 30 12:56:55 2025
PySCF version 2.9.0
PySCF path /home/porter284/.pyenv/versions/3.11.12/lib/python3.11/site-packages/pyscf
[CONFIG] conf_file None
[INPUT] verbose = 4
[INPUT] num. atoms = 2
[INPUT] num. electrons = 4
[INPUT] charge = 0
[INPUT] spin (= nelec alpha-beta = 2S) = 0
[INPUT] symmetry True subgroup None
[INPUT] Mole.unit = angstrom
[INPUT] Symbol X Y Z unit X Y Z unit Magmom
[INPUT] 1 Li 0.000000000000 0.000000000000 0.000000000000 AA 0.000000000000 0.000000000000 0.000000000000 Bohr 0.0
[INPUT] 2 H 0.000000000000 0.000000000000 1.560000000000 AA 0.000000000000 0.000000000000 2.947972754321 Bohr 0.0
nuclear repulsion = 1.01764848253846
point group symmetry = Coov
symmetry origin: [0. 0. 0.73699319]
symmetry axis x: [1. 0. 0.]
symmetry axis y: [0. 1. 0.]
symmetry axis z: [0. 0. 1.]
num. orbitals of irrep A1 = 4
num. orbitals of irrep E1x = 1
num. orbitals of irrep E1y = 1
number of shells = 4
number of NR pGTOs = 36
number of NR cGTOs = 6
basis = sto-6g
ecp = {}
CPU time: 9.85
<pyscf.gto.mole.Mole at 0x7fc719f94850>
他の有用な情報の中で、これは point group symmetry = Coov 、またそれぞれの既約表現における軌道の数を返す。
point group symmetry = Coov
num. orbitals of irrep A1 = 4
num. orbitals of irrep E1x = 1
num. orbitals of irrep E1y = 1
number of shells = 4
これは、必ずしもアクティブスペースに含ませたい軌道の数を教えてくれるわけではないが、どのような軌道が存在し、それらの対称性を確認するのに役立つ。
対称性と軌道を指定することはしばしば役に立つが、含める軌道の数を指定することもできる。 エテンの場合を考えてみよう。 verbose = 4 を使えば、様々な軌道の対称性を印刷することができる:
# Replace these variables with correct distances:
a = 1
b = 1
c = 1
# Build
mol = gto.Mole()
mol.build(
verbose=4,
atom=[
["C", (0, 0, a)],
["C", (0, 0, -a)],
["H", (0, c, b)],
["H", (0, -c, b)],
["H", (0, c, -b)],
["H", (0, -c, -b)],
],
basis="sto-6g",
spin=0,
charge=0,
symmetry=True,
)Output:
System: uname_result(system='Linux', node='IBM-R912JTRT', release='5.10.102.1-microsoft-standard-WSL2', version='#1 SMP Wed Mar 2 00:30:59 UTC 2022', machine='x86_64') Threads 16
Python 3.11.12 (main, May 16 2025, 02:33:32) [GCC 11.4.0]
numpy 2.3.1 scipy 1.16.0 h5py 3.14.0
Date: Mon Jun 30 12:57:07 2025
PySCF version 2.9.0
PySCF path /home/porter284/.pyenv/versions/3.11.12/lib/python3.11/site-packages/pyscf
[CONFIG] conf_file None
[INPUT] verbose = 4
[INPUT] num. atoms = 6
[INPUT] num. electrons = 16
[INPUT] charge = 0
[INPUT] spin (= nelec alpha-beta = 2S) = 0
[INPUT] symmetry True subgroup None
[INPUT] Mole.unit = angstrom
[INPUT] Symbol X Y Z unit X Y Z unit Magmom
[INPUT] 1 C 0.000000000000 0.000000000000 1.000000000000 AA 0.000000000000 0.000000000000 1.889726124565 Bohr 0.0
[INPUT] 2 C 0.000000000000 0.000000000000 -1.000000000000 AA 0.000000000000 0.000000000000 -1.889726124565 Bohr 0.0
[INPUT] 3 H 0.000000000000 1.000000000000 1.000000000000 AA 0.000000000000 1.889726124565 1.889726124565 Bohr 0.0
[INPUT] 4 H 0.000000000000 -1.000000000000 1.000000000000 AA 0.000000000000 -1.889726124565 1.889726124565 Bohr 0.0
[INPUT] 5 H 0.000000000000 1.000000000000 -1.000000000000 AA 0.000000000000 1.889726124565 -1.889726124565 Bohr 0.0
[INPUT] 6 H 0.000000000000 -1.000000000000 -1.000000000000 AA 0.000000000000 -1.889726124565 -1.889726124565 Bohr 0.0
nuclear repulsion = 29.3377079104231
point group symmetry = D2h
symmetry origin: [0. 0. 0.]
symmetry axis x: [0. 1. 0.]
symmetry axis y: [1. 0. 0.]
symmetry axis z: [-0. -0. -1.]
num. orbitals of irrep Ag = 4
num. orbitals of irrep B2g = 2
num. orbitals of irrep B3g = 1
num. orbitals of irrep B1u = 4
num. orbitals of irrep B2u = 1
num. orbitals of irrep B3u = 2
number of shells = 10
number of NR pGTOs = 84
number of NR cGTOs = 14
basis = sto-6g
ecp = {}
CPU time: 9.92
<pyscf.gto.mole.Mole at 0x7fc719fa9290>
となる:
イレップの軌道数 Ag = 4
イレップの軌道数 B2g = 2
イレップの軌道数 B3g = 1
イレップの軌道数 B1u = 4
イレップの軌道数 B2u = 1
イレップの軌道数 B3u = 2
しかし、対称性によってすべての軌道を指定するのではなく、単純にこう書くことができる:
active_space = range(mol.nelectron // 2 - 2, mol.nelectron // 2 + 2)このアプローチでは、充填準位に近い複数の軌道(原子価軌道と非占有軌道)を取る。 ここでは、5つの軌道が活性空間に含まれるように選択されている(6番目から10番目)。
print(
mol.nelectron // 2 - 2,
mol.nelectron // 2 + 2,
)Output:
6 10
- サード・パーティー・ソフトウェア
量子化学用に開発されたソフトウェアパッケージはいくつかあり、複数のマッパーやアクティブスペースを制限するツールを提供しているものもある。 上記の手順は一般的なものであり、サードパーティのソフトウェアにも適用されます。 しかし、この他のソフトウェアはQiskitが受け付けない形式のハミルトニアンを返すかもしれません。 例えば、ハミルトニアンを返すソフトウェアもある:
H = -0.042 [] + -0.045 [X0 X1 Y2 Y3] + ... + 0.178 [Z0] + ... + 0.176 [Z2 Z3] + -0.243 [Z3]
特に、ゲートに番号が振られていることと、同一性演算子が示されていないことに注意されたい。 これはQiskitで使われているハミルトニアンとは対照的で、 ZZII (量子ビット0と1は同一作用素によって、量子ビット2と3はZ作用素によって作用され、量子ビット0が最も右側に来るように並べられている)として [Z2 Z3] 。
既存のワークストリームに対応するため、以下のコードブロックは一方の構文から他方の構文に変換する。 関数 convert_openfermion_to_qiskit は、 OpenFermion またはTangeloで生成されたハミルトニアン(および利用可能なマッパーを使用してすでにパウリ演算子にマッピングされている)、および分子に必要な量子ビット数を引数にとります。
from openfermion import QubitOperator
from qiskit.quantum_info import SparsePauliOp
def convert_openfermion_to_qiskit(
openfermion_operator: QubitOperator, num_qubits: int
) -> SparsePauliOp:
terms = openfermion_operator.terms
labels = []
coefficients = []
for term, constant in terms.items():
# Default set to identity
operator = list("I" * num_qubits)
# Iterate through PauliSum and replace I with Pauli
for index, pauli in term:
operator[index] = pauli
label = "".join(operator)
labels.append(label)
coefficients.append(constant)
return SparsePauliOp(labels, coefficients)さらに、 この Python ノートブックには、上記の変換を含め、ハミルトニアンを他のソフトウェアワークフローからQiskitに移行するための完全なサンプルコードが含まれています。
これで、 IBM® 量子コンピュータで量子化学計算を行うために必要なハミルトニアンを得るためのツールが揃ったことになる。