Fulqrum으로 SQD 화학 워크플로 확장하기
Fulqrum은 부분 공간에 투영된 Hamiltonian의 고유값과 고유 벡터를 계산하는 소프트웨어 패키지입니다. 투영된 Hamiltonian의 이식 가능한 표현을 구성하며, 이를 표준 기성 솔버를 포함해 원하는 고유값 솔버에 전달할 수 있습니다. 이러한 솔버는 SQD 애드온이 대각화에 사용하는 기본 PySCF 솔버보다 훨씬 빠른 대안이 될 수 있습니다. Fulqrum을 설치하고 사용하는 방법은 Fulqrum 문서를 참고하세요.
Fulqrum의 addon_sqd_support 모듈은 diagonalize_fermionic_hamiltonian이 직접 받아들이는 솔버를 만들어 주므로, 양쪽에 같은 적분을 전달하면 Fulqrum이 투영과 대각화를 처리합니다.
이 예제에 사용된 SQD 코드에 대한 자세한 내용은 화학 Hamilton 튜토리얼을 참고하세요.
# Added by doQumentation — installs the packages this notebook needs if they are missing
import importlib.util
_needed = {"fulqrum": "fulqrum", "numpy": "numpy", "primme": "primme", "pyscf": "pyscf", "qiskit_addon_sqd": "qiskit-addon-sqd"}
_missing = [pip for module, pip in _needed.items()
if importlib.util.find_spec(module) is None]
# One at a time, so a package that fails to install does not block the others
for _pip in _missing:
%pip install -q {_pip}
if not _missing:
print("\u2713 All required packages are installed")
import numpy as np
import pyscf
import pyscf.cc
import pyscf.mcscf
from fulqrum.addon_sqd_support import sci_solver_from_integrals
from qiskit_addon_sqd.counts import generate_bit_array_uniform
from qiskit_addon_sqd.fermion import diagonalize_fermionic_hamiltonian
# 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
)
# Build a solver that diagonalizes with Fulqrum. It converts the integrals to a
# Fulqrum operator once, then projects onto each subspace the SQD loop requests.
sci_solver = sci_solver_from_integrals(hcore, eri, tol=1e-12)
# Run SQD
result = diagonalize_fermionic_hamiltonian(
hcore,
eri,
bit_array,
samples_per_batch=300,
norb=num_orbitals,
nelec=(num_elec_a, num_elec_b),
num_batches=5,
max_iterations=5,
sci_solver=sci_solver,
symmetrize_spin=True,
seed=rng,
)
WARN: Unable to to identify input symmetry using original axes.
Different symmetry axes will be used.
converged SCF energy = -108.835236570774
CASCI E = -109.046671778080 E(CI) = -32.8155692383188 S^2 = 0.0000000
print(f"Exact energy: {exact_energy}")
print(f"Estimated energy: {result.energy + nuclear_repulsion_energy}")
Exact energy: -109.04667177808024
Estimated energy: -109.03402667559365
다른 고유값 풀이기 사용하기
Fulqrum은 투영된 Hamiltonian을 표준 scipy.sparse.linalg.LinearOperator로 제공하므로, scipy.sparse.linalg.eigsh에 얽매이지 않고 원하는 어떤 고유값 풀이기로도 대각화할 수 있습니다. 따라서 문제 크기나 하드웨어에 더 적합한 솔버로 쉽게 바꿀 수 있습니다.
원하는 고유값 풀이기를 eigensolver 인수로 전달하세요. 예로 이 안내서에서는 고성능 고유값 풀이 라이브러리인 PRIMME를 사용합니다. Python 인터페이스인 primme.eigsh는 scipy.sparse.linalg.eigsh와 비슷한 호출 규약을 따르므로 그대로 대체하여 사용할 수 있습니다.
import primme
# Build a solver that diagonalizes with PRIMME instead of the default,
# scipy.sparse.linalg.eigsh
sci_solver = sci_solver_from_integrals(
hcore, eri, eigensolver=primme.eigsh, tol=1e-12
)
# Run SQD using the PRIMME-based solver
result = diagonalize_fermionic_hamiltonian(
hcore,
eri,
bit_array,
samples_per_batch=300,
norb=num_orbitals,
nelec=(num_elec_a, num_elec_b),
num_batches=5,
max_iterations=5,
sci_solver=sci_solver,
symmetrize_spin=True,
seed=rng,
)
print(f"Exact energy: {exact_energy}")
print(f"Estimated energy: {result.energy + nuclear_repulsion_energy}")
Exact energy: -109.04667177808024
Estimated energy: -109.03937429196168