ปรับปรุงค่าประมาณ SQD ด้วยการเพิ่มประสิทธิภาพออร์บิทัล
Sample-based quantum diagonalization (SQD) ประมาณค่าพลังงานสถานะพื้นโดย ทำ diagonalization Hamiltonian ในซับสเปซคงที่ของคอนฟิกูเรชันอิเล็กตรอน ค่าประมาณนั้น ขึ้นอยู่กับฐานออร์บิทัลที่ใช้แสดง Hamiltonian และ การเพิ่มประสิทธิภาพออร์บิทัล (OO) ใช้ประโยชน์จากอิสระนี้เพื่อลดพลังงานโดยไม่ขยาย ซับสเปซ
คู่มือนี้รัน SQD บนโมเลกุล แล้วปรับปรุงผลลัพธ์ด้วยการเพิ่มประสิทธิภาพออร์บิทัล
โดยใช้ ffsim แทน
Hamiltonian และหาการหมุนออร์บิทัลที่ทำให้พลังงานต่ำที่สุด
รัน SQD
เราสร้างอินทิกรัลโมเลกุลของ ในฐานออร์บิทัลโมเลกุล (MO) สร้าง ตัวอย่างสุ่มแบบสม่ำเสมอ และรัน SQD เพื่อให้ได้ค่าประมาณสถานะพื้น
# Added by doQumentation — installs the packages this notebook needs if they are missing
import importlib.util
_needed = {"ffsim": "ffsim", "numpy": "numpy", "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 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,
)
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}")
Exact energy: -109.04667178
SQD energy: -108.98469255
เพิ่มประสิทธิภาพออร์บิทัล
การเพิ่มประสิทธิภาพออร์บิทัลค้นหาการหมุนออร์บิทัลที่ลดพลังงานแบบ variational
ของค่าประมาณสถานะพื้น SQD การหมุนออร์บิทัลถูกกำหนดด้วย เมทริกซ์ยูนิทารี ( คือจำนวนออร์บิทัลเชิงพื้นที่) ซึ่ง กระทำต่อสถานะหลายวัตถุผ่านตัวดำเนินการ
ffsim.optimize_orbitals ส่งคืนเมทริกซ์ และการนำไปใช้กับ
ฐานออร์บิทัล (ผ่าน hamiltonian.rotated) เทียบเท่ากับการใช้ กับ
สถานะ ดู
คำอธิบายการหมุนออร์บิทัลของ ffsim
สำหรับรายละเอียด
เนื่องจากการหมุนออร์บิทัลเปลี่ยน Hamiltonian ที่ซับสเปซมองเห็น เราจึงสลับ สองขั้นตอนจนกว่าพลังงานจะไม่ดีขึ้นอีก:
-
ทำ diagonalization Hamiltonian ในฐานปัจจุบันบนชุดคอนฟิกูเรชันคงที่
-
เพิ่มประสิทธิภาพออร์บิทัล โดยหาการหมุนที่ทำให้พลังงานของ สถานะที่ได้ต่ำที่สุด แล้วหมุนอินทิกรัลไปยังฐานใหม่
เรามอบหมายขั้นตอนการหมุนออร์บิทัลให้
ffsim.optimize_orbitals
ซึ่งหาการหมุนที่ทำให้พลังงานต่ำที่สุดจากเมทริกซ์ความหนาแน่นลดรูปหนึ่งและสองวัตถุ
(RDM) ของสถานะ ดู
Sec. II A 4 สำหรับรายละเอียด
ทำไมการเพิ่มประสิทธิภาพออร์บิทัลจึงช่วยในกรณีนี้
ฐานออร์บิทัลโมเลกุล (MO) แบบ SCF นิ่งเมื่อเทียบกับการหมุนออร์บิทัลสำหรับปัญหา CI แบบ เต็ม แต่ SQD ทำงานในซับสเปซที่ถูกตัดทอนขนาดเล็ก (ที่นี่คือสตริง CI ไม่กี่ร้อยตัวจากดีเทอร์มิแนนต์ full-CI ราว 19 ล้านตัว) ซึ่งฐาน 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.
สลับระหว่าง diagonalization และการเพิ่มประสิทธิภาพออร์บิทัล
เราคงซับสเปซสำหรับ diagonalization ให้ คงที่ ตามคอนฟิกูเรชันที่ SQD ค้นพบ ข้างต้น เพื่อให้แต่ละรอบแยกผลของการหมุนออร์บิทัลออกมาได้ แต่ละ รอบ:
-
ทำ diagonalization Hamiltonian บนซับสเปซคงที่ในฐานปัจจุบัน โดยใช้ ตัวแก้ selected-CI ของ PySCF
-
สร้าง RDM ของสถานะที่ได้ ซึ่งเป็นทั้งหมดที่
ffsim.optimize_orbitalsต้องการ -
เพิ่มประสิทธิภาพออร์บิทัล:
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())
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}")
Exact energy: -109.04667178
SQD energy (MO): -108.98469255
Energy after OO: -109.01178727