Skip to main content
IBM Quantum Platform

軌道最適化によるSQD推定値の改善

サンプルベース量子対角化(SQD)は、 電子配置の固定された部分空間においてハミルトニアンを対角化することで、基底状態のエネルギーを近似する。 その 推定値は、ハミルトニアンが表現される軌道基底に依存しており、 軌道最適化 (OO)はこの自由度を利用して、 部分空間を拡大することなくエネルギーを低減させる。

このガイドでは、 N2N_2 分子に対してSQDを実行し、その後、軌道 最適化を用いて結果を改善します。ここでは、 ハミルトニアンを表現しffsim、エネルギーを最小化する軌道回転を求めるためにを使用します。


SQDを実行する

分子軌道(MO)基底において、 N2N_2 の分子積分を構築し、 一様乱数サンプルを生成した上で、SQDを実行して基底状態の近似を求める。

import numpy as np
import pyscf
import pyscf.cc
import pyscf.mcscf
from qiskit_addon_sqd.counts import generate_bit_array_uniform
from qiskit_addon_sqd.fermion import diagonalize_fermionic_hamiltonian

# Specify molecule properties
num_orbitals = 16
num_elec_a = num_elec_b = 5
spin_sq = 0

# Build N2 molecule
mol = pyscf.gto.Mole()
mol.build(
    atom=[["N", (0, 0, 0)], ["N", (1.0, 0, 0)]],
    basis="6-31g",
    symmetry="Dooh",
)

# Define active space
n_frozen = 2
active_space = range(n_frozen, mol.nao_nr())

# Get molecular integrals
scf = pyscf.scf.RHF(mol).run()
num_orbitals = len(active_space)
n_electrons = int(sum(scf.mo_occ[active_space]))
num_elec_a = (n_electrons + mol.spin) // 2
num_elec_b = (n_electrons - mol.spin) // 2
cas = pyscf.mcscf.CASCI(scf, num_orbitals, (num_elec_a, num_elec_b))
mo = cas.sort_mo(active_space, base=0)
hcore, nuclear_repulsion_energy = cas.get_h1cas(mo)
eri = pyscf.ao2mo.restore(1, cas.get_h2cas(mo), num_orbitals)

# Compute exact energy
exact_energy = cas.run().e_tot

# Create a seed to control randomness throughout this workflow
rng = np.random.default_rng(24)


# Generate random samples
bit_array = generate_bit_array_uniform(
    10_000, num_orbitals * 2, rand_seed=rng
)

# Run SQD
result = diagonalize_fermionic_hamiltonian(
    hcore,
    eri,
    bit_array,
    samples_per_batch=100,
    norb=num_orbitals,
    nelec=(num_elec_a, num_elec_b),
    num_batches=1,
    max_iterations=5,
    symmetrize_spin=True,
    seed=rng,
)

Output:

converged SCF energy = -108.835236570775
CASCI E = -109.046671778080  E(CI) = -32.8155692383187  S^2 = 0.0000000
sqd_energy = result.energy + nuclear_repulsion_energy
print(f"Exact energy:  {exact_energy:.8f}")
print(f"SQD energy:    {sqd_energy:.8f}")

Output:

Exact energy:  -109.04667178
SQD energy:    -108.98469255

軌道を最適化する

軌道最適化とは、変分エネルギーを低減させる軌道回転を探すことである

E=ψUHUψE = \langle \psi | \mathcal{U}^\dagger\, H\, \mathcal{U} | \psi \rangle

SQD基底状態近似 ψ|\psi\rangle に基づく。軌道回転は、 N×NN \times N ユニタリ行列 U\mathbf{U}NN は空間軌道の数)によって指定され、 これは演算子を通じて多体状態に作用する。

U=exp[pq,σlog(U)pqapσaqσ].\mathcal{U} = \exp\left[\sum_{pq, \sigma} \log(\mathbf{U})_{pq}\, a^\dagger_{p\sigma} a_{q\sigma}\right].

ffsim.optimize_orbitals 行列U\mathbf{U} を返し、これを 軌道基底に( via を用いて hamiltonian.rotated)適用することは、 U\mathcal{U} を その状態に適用することと同等である。 詳細については、 ffsimの軌道回転に関する説明 を参照してください。

軌道回転を行うと、その部分空間から見てハミルトニアンが変化するため、 エネルギーがこれ以上改善しなくなるまで、以下の2つの手順を交互に繰り返します:

  1. 固定された一連の 構成について、現在の基底におけるハミルトニアンを対角化する
  2. 結果として得られる状態のエネルギーを最小化する回転を見つけ、 軌道関数を最適化し、 その後、積分を新しい基底に変換する。

軌道回転のステップは、 状態の1体および2体の縮約密度行列(RDM)から、 エネルギーを最小化する回転を求める処理に ffsim.optimize_orbitals委ねます。 「第○条」を参照 。 詳細はII A 4 を参照のこと。

なぜこの場面で軌道最適化が役立つのか

SCF分子軌道(MO)基底は、完全な-CI問題において軌道回転に対して静止している。 しかし、SQDは小さな切り詰め部分空間(ここでは、およそ1,900万個の完全CI行列式のうち、 数百個のCI文字列)で動作するため、その部分空間ではMO 基底は一般的に最適ではない。そのため、軌道を回転させると、その部分空間が 表現できるエネルギーが低下してしまう。

import ffsim
from pyscf import fci

# ffsim's ``MolecularHamiltonian`` uses the same "chemist" ordering for the two-body
# tensor as PySCF's ``eri``, and stores the nuclear repulsion energy as the constant
# term so that expectation values come out as total energies.

交互対角化と軌道最適化

SQDによって上記で発見された構成に対して、対角化部分空間を一定に保つことで、 各反復計算において軌道回転の影響のみを分離できるようにしています。 各 反復ごとに:

  1. PySCF'sの選択CIソルバーを用いて、現在の基底における固定部分空間上でハミルトニアンを対角化します

  2. 結果として得られる状態のRDMを構築します 。これだけで十分ffsim.optimize_orbitals です。

  3. 軌道を最適化します :エネルギーを最小化する 回転を返しffsim.optimize_orbitals、これを積分に適用して、改良された基底に移行させます。

各最適化ステップの前に、エネルギー値を記録します。 基底は反復ごとに 改善されるため、この数列は、固定された部分空間において達成可能な最良のエネルギーに向かって 単調に減少する。

# Fix the diagonalization subspace to the configurations found by SQD.
ci_strings = (result.sci_state.ci_strs_a, result.sci_state.ci_strs_b)
nelec = (num_elec_a, num_elec_b)

# Start from the MO basis in which we ran SQD.
hamiltonian_opt = ffsim.MolecularHamiltonian(
    hcore, eri, constant=nuclear_repulsion_energy
)

num_iters = 10
for i in range(num_iters):
    # Diagonalize over the fixed subspace in the current basis.
    myci = fci.selected_ci.SelectedCI()
    myci = fci.addons.fix_spin_(myci, ss=spin_sq)
    _, amplitudes = fci.selected_ci.kernel_fixed_space(
        myci,
        hamiltonian_opt.one_body_tensor,
        hamiltonian_opt.two_body_tensor,
        num_orbitals,
        nelec,
        ci_strs=ci_strings,
    )

    # Build the RDMs and record the energy before re-optimizing the orbitals.
    dm1, dm2 = myci.make_rdm12(amplitudes, num_orbitals, nelec)
    rdm = ffsim.ReducedDensityMatrix(dm1, dm2)
    energy = rdm.expectation(hamiltonian_opt).real
    print(f"Iteration {i}: energy = {energy:.8f}")

    # Rotate the Hamiltonian into the energy-minimizing basis for the next iteration.
    # optimize_orbitals returns the unitary matrix U minimizing
    # rdm.rotated(U).expectation(hamiltonian), equivalently
    # rdm.expectation(hamiltonian.rotated(U.conj().T)), so we rotate by U^dagger.
    orbital_rotation = ffsim.optimize_orbitals(rdm, hamiltonian_opt)
    hamiltonian_opt = hamiltonian_opt.rotated(orbital_rotation.T.conj())

Output:

Iteration 0: energy = -108.98452447
Iteration 1: energy = -108.99981993
Iteration 2: energy = -109.00585329
Iteration 3: energy = -109.00816569
Iteration 4: energy = -109.00936616
Iteration 5: energy = -109.01014322
Iteration 6: energy = -109.01069439
Iteration 7: energy = -109.01109308
Iteration 8: energy = -109.01138928
Iteration 9: energy = -109.01161411

結果の比較

軌道最適化により、固定部分空間による推定値が改善され、 正確なエネルギーとの差の大部分が縮まり、かつその値を上回る状態が維持される。

# Diagonalize once more in the final optimized basis to report the improved energy.
myci = fci.selected_ci.SelectedCI()
myci = fci.addons.fix_spin_(myci, ss=spin_sq)
_, amplitudes = fci.selected_ci.kernel_fixed_space(
    myci,
    hamiltonian_opt.one_body_tensor,
    hamiltonian_opt.two_body_tensor,
    num_orbitals,
    nelec,
    ci_strs=ci_strings,
)
dm1, dm2 = myci.make_rdm12(amplitudes, num_orbitals, nelec)
energy_after_oo = (
    ffsim.ReducedDensityMatrix(dm1, dm2).expectation(hamiltonian_opt).real
)

print(f"Exact energy:      {exact_energy:.8f}")
print(f"SQD energy (MO):   {sqd_energy:.8f}")
print(f"Energy after OO:   {energy_after_oo:.8f}")

Output:

Exact energy:      -109.04667178
SQD energy (MO):   -108.98469255
Energy after OO:   -109.01178727
このページは役に立ちましたか?
バグや誤字の報告、またはコンテンツの要求はGitHubで行ってください。