Advanced Guide on Computing Electron Integrals#
In previous tutorials, we explained how to obtain electron integrals (eInts) as well as the molecular Hamiltonian. In this tutorial, we give more detailed introductions on objects that QURI Parts provides for computing eInts, so that you may customize your algorithms for efficient eInt computations.
Prerequisite#
QURI Parts modules used in this tutorial: quri-parts-chem
, quri-parts-pyscf
, and quri-parts-openfermion
. You can install them as follows:
[ ]:
!pip install "quri_parts[chem]"
!pip install "quri_parts[pyscf]"
!pip install "quri_parts[openfermion]"
Overview#
We now demonstrate the detailed steps to obtain the SpinMOeInt
from molecule without the get_spin_mo_integrals_from_mole
function introduced in the Hamiltonian generation tutorial:
Define a
MolecularOrbitals
object from theMolecule
and the corresponding MO coefficients.If required, specify the active space (
ActiveSpace
) and create the correspondingMolecularOrbitals
object represented by aActiveSpaceMolecularOrbitals
object.If no active space is specified, we term the corresponding
MolecularOrbitals
as full space molecular orbital, which is represented by aPySCFMolecularOrbitals
.
Compute the atomic orbital (AO) eInt represented by
AOeIntSet
from the full spaceMolecularOrbitals
.Compute the spatial molecular orbital (MO) eInt (
SpatialMOeIntSet
) from the AO eInt and theMolecularOrbitals
.Compute the spin molecular orbital (MO) eInt (
SpinMOeIntSet
) from the spatial MO eInt or directly from the AO eInt.
We will follow the above structure to demonstrate how different objects relate to each other. Note that starting from step 2, there are 2 paths of finishing the rest of the computations:
Store all the electron integral arrays on memory and do the computation with the
quri-parts.chem.mol
package:This method is efficient for small molecules, but the memory usage grows rapidly with the molecular size.
Use only the
PySCF
Mole object and the MO coefficients to do the computation with thequri-parts.pyscf.mol
package:This method is useful for large molecules as the electron integral arrays are not release onto the memory unless required.
Quick summary: Compute with storing the electron integral arrays on memory#
[2]:
# Step 1: Construct a PySCF Mole object
from pyscf import gto, scf
from quri_parts.pyscf.mol import PySCFMolecularOrbitals
from quri_parts.chem.mol import ActiveSpaceMolecularOrbitals, cas
## Define molecule
h2o_atom_list = [['H', [0, 0, 0]], ['O', [2, 0, 1]], ['H', [0, 0, 2]]]
h2o_mol = gto.M(atom=h2o_atom_list, verbose = 0)
h2o_mf = scf.RHF(h2o_mol).run()
## Create a PySCFMolecularOrbitals
h2o_mo = PySCFMolecularOrbitals(mol=h2o_mol, mo_coeff=h2o_mf.mo_coeff)
## If an active space is required, createn a ActiveSpaceMolecularOrbitals
active_space_mo = ActiveSpaceMolecularOrbitals(
mo=h2o_mo, active_space=cas(n_active_ele=6, n_active_orb=4)
)
# Step 2: Compute the AO electron integrals
from quri_parts.pyscf.mol import get_ao_eint_set
ao_eint_set = get_ao_eint_set(h2o_mo, store_array_on_memory=True)
# Step 3, 4: Compute the MO electron integrals
## Full space electron integrals
full_space_spatial_mo_eint_set = ao_eint_set.to_full_space_spatial_mo_int(h2o_mo)
full_space_spin_mo_eint_set = ao_eint_set.to_full_space_mo_int(h2o_mo)
## Active space electron integrals
active_space_spatial_mo_eint_set = ao_eint_set.to_active_space_spatial_mo_int(active_space_mo)
active_space_spin_mo_eint_set = ao_eint_set.to_active_space_mo_int(active_space_mo)
Quick summary: Compute without storing the electron integral arrays on memory#
[3]:
# Step 1: Construct a PySCF Mole object
from pyscf import gto, scf
from quri_parts.pyscf.mol import PySCFMolecularOrbitals
from quri_parts.chem.mol import ActiveSpaceMolecularOrbitals, cas
## Define molecule
h2o_atom_list = [['H', [0, 0, 0]], ['O', [2, 0, 1]], ['H', [0, 0, 2]]]
h2o_mol = gto.M(atom=h2o_atom_list, verbose = 0)
h2o_mf = scf.RHF(h2o_mol).run()
## Create a PySCFMolecularOrbitals
h2o_mo = PySCFMolecularOrbitals(mol=h2o_mol, mo_coeff=h2o_mf.mo_coeff)
## If an active space is required, createn a ActiveSpaceMolecularOrbitals
active_space_mo = ActiveSpaceMolecularOrbitals(
mo=h2o_mo, active_space=cas(n_active_ele=6, n_active_orb=4)
)
# Step 2: Compute the AO electron integrals
from quri_parts.pyscf.mol import get_ao_eint_set
pyscf_ao_eint_set = get_ao_eint_set(
h2o_mo,
store_array_on_memory=False # default to False
)
# Step 3, 4: Compute the MO electron integrals
## Full space electron integrals
pyscf_full_space_spatial_mo_eint_set = pyscf_ao_eint_set.to_full_space_spatial_mo_int(h2o_mo)
pyscf_full_space_spin_mo_eint_set = pyscf_ao_eint_set.to_full_space_mo_int(h2o_mo)
## Active space electron integrals
pyscf_active_space_spatial_mo_eint_set = pyscf_ao_eint_set.to_active_space_spatial_mo_int(active_space_mo)
pyscf_active_space_spin_mo_eint_set = pyscf_ao_eint_set.to_active_space_mo_int(active_space_mo)
Defining the Molecules and Molecular Orbitals#
In this section, we introduce the MolecularOrbitals
object. A MolecularOrbitals
object is an object that holds basic information of the molecule such as MO coefficients, number of spatial orbitals, electrons and spin, etc. In QURI Parts
, we provide 2 types of MolecularOrbitals
object:
Let’s first create a molecule with the PySCF
library.
[4]:
from pyscf import gto, scf
from quri_parts.pyscf.mol import PySCFMolecularOrbitals
h2o_atom_list = [['H', [0, 0, 0]], ['O', [2, 0, 1]], ['H', [0, 0, 2]]]
h2o_mol = gto.M(atom=h2o_atom_list, verbose = 0)
h2o_mf = scf.RHF(h2o_mol).run()
Now, let’s create a PySCFMolecularOrbital
object which contains:
number of electrons of the molecule
number of spatial orbitals of the molecule
spin of the molecule
[5]:
h2o_mo = PySCFMolecularOrbitals(mol=h2o_mol, mo_coeff=h2o_mf.mo_coeff)
print(f'Number of electrons: {h2o_mo.n_electron}')
print(f'Number of spatial orbitals: {h2o_mo.n_spatial_orb}')
print(f'Spin of the molecule: {h2o_mo.spin}')
print(f'MO coefficients:\n {h2o_mo.mo_coeff.round(3)}')
Number of electrons: 10
Number of spatial orbitals: 7
Spin of the molecule: 0
MO coefficients:
[[-0. 0.03 -0. -0.305 0.651 0.597 0.38 ]
[ 0.995 -0.262 -0. -0.011 0. 0.021 0. ]
[ 0.02 1.025 0. 0.05 -0. -0.098 -0. ]
[-0. 0.003 0. 0.861 -0. 0.512 0. ]
[ 0. -0. 1. -0. 0. -0. -0. ]
[-0. 0. 0. -0. -0.473 -0. 0.882]
[-0. 0.03 -0. -0.305 -0.651 0.597 -0.38 ]]
We may also select an active space for the molecule with the ActiveSpace
object we introduced in the Hamiltonian tutorial to create an ActiveSpaceMolecularOrbitals
object. Detailed information of the active space can be obtained by printing out the active_space_mo
.
[6]:
from quri_parts.chem.mol import ActiveSpace, ActiveSpaceMolecularOrbitals
n_active_ele = 6
n_active_orb = 4
active_space = ActiveSpace(n_active_ele=n_active_ele, n_active_orb=n_active_orb)
active_space_mo = ActiveSpaceMolecularOrbitals(mo=h2o_mo, active_space=active_space)
print(active_space_mo)
n_electron: 10
n_active_ele: 6
n_core_ele: 4
n_ele_alpha: 3
n_ele_beta: 3
n_spatial_orb: 7
n_active_orb: 4
n_core_orb: 2
n_vir_orb: 1
Compute the Electron Integrals with QURI Parts#
Here, we introduce the first way of computing the electron integrals, where we release all the electron integrals of each intermediate step onto the memory. We first list out all the objects that represents an electron integral:
AO electron integral:
AOeIntArraySet
, which contains:constant: The nuclear repulsion energy.
AO1eIntArray
: The AO 1-electron integral array on memoryAO2eIntArray
: The AO 2-electron integral array on memory
Spatial electron integral:
SpatialMOeIntSet
, which contains:constant: The nuclear repulsion energy.
SpatialMO1eIntArray
: The spatial MO 1-electron integral array on memorySpatialMO2eIntArray
: The spatial MO 2-electron integral array on memory
Spin electron integral:
SpinMOeIntSet
, which contains:constant: The nuclear repulsion energy.
SpinMO1eIntArray
: The spin MO 1-electron integral array on memorySpinMO2eIntArray
: The spin MO 2-electron integral array on memory
Computing the AO electron integrals#
One can construct the AOeIntArraySet
using the get_ao_eint_set
function. When the store_array_on_memory
argument is set to True, it returns an AOeIntArraySet
object that stores the AO electron integrals on memory.
[7]:
from quri_parts.pyscf.mol import get_ao_eint_set
ao_eint_set= get_ao_eint_set(h2o_mo, store_array_on_memory=True)
# the nuclear repulsion energy
nuc_energy= ao_eint_set.constant
# the AO one-electron integrals: an AO1eIntArray object
ao_1e_int= ao_eint_set.ao_1e_int
# the AO two-electron integrals: an AO2eIntArray object
ao_2e_int= ao_eint_set.ao_2e_int
One can access the explicit array of the electron atomic orbital integrals with the array attribute:
[8]:
# ao_1e_int.array
# ao_2e_int.array
Computing the spatial and spin MO electron integrals#
With the AO electron integrals and the MolecularOrbitals
at hand, we may compute the:
spatial MO electron integrals with the
to_full_space_spatial_mo_int
methodspin MO electron integrals with the
to_full_space_mo_int
methods
in ao_eint_set
. Note that the explicit array of the integrals are computed and released to the memory once these methods are called.
[9]:
# Computes the full space spatial mo electron integrals
spatial_mo_e_int_set = ao_eint_set.to_full_space_spatial_mo_int(h2o_mo)
# Computes the full space spin mo electron integrals
spin_mo_e_int_set = ao_eint_set.to_full_space_mo_int(h2o_mo)
To access the explicit array of the integrals, you may run:
[10]:
# For the spatial MO electron integrals
nuclear_repulsion_energy = spatial_mo_e_int_set.const
spatial_1e_int = spatial_mo_e_int_set.mo_1e_int.array
spatial_2e_int = spatial_mo_e_int_set.mo_2e_int.array
# For the spin MO electron integrals
nuclear_repulsion_energy = spin_mo_e_int_set.const
spin_1e_int = spin_mo_e_int_set.mo_1e_int.array
spin_2e_int = spin_mo_e_int_set.mo_2e_int.array
Computing the active space electron integrals#
The active space spin and spatial MO eInts can also be computed in a similar way. Instead of using the full space MolecularOrbitals
, we compute the:
active space spatial MO eInt with the
to_active_space_spatial_mo_int
methodactive space spin MO eInt with the
to_active_space_mo_int
method
by passing in the ActiveSpaceMolecularOrbitals
. Note that the explicit eInt arrays are released onto the memory once these methods are called.
[11]:
# Convenient method of computing the active space spatial mo electron integrals
active_space_spatial_integrals = ao_eint_set.to_active_space_spatial_mo_int(active_space_mo=active_space_mo)
# Convenient method of computing the active space spin mo electron integrals
active_space_spin_integrals = ao_eint_set.to_active_space_mo_int(active_space_mo=active_space_mo)
To obtain the explicit active space MO eInts
[23]:
# For the spatial MO electron integrals
active_space_nuclear_repulsion_energy = active_space_spatial_integrals.const
active_space_spatial_1e_int = active_space_spatial_integrals.mo_1e_int.array
active_space_spatial_2e_int = active_space_spatial_integrals.mo_2e_int.array
# For the spin MO electron integrals
active_space_nuclear_repulsion_energy = active_space_spin_integrals.const
active_space_spin_1e_int = active_space_spin_integrals.mo_1e_int.array
active_space_spin_2e_int = active_space_spin_integrals.mo_2e_int.array
To achieve optimal efficiency when computing active space eInts, we recommend using the
get_active_space_spatial_integrals_from_mo_eint
get_active_space_spin_integrals_from_mo_eint
functions as the to_active_space_spatial_mo_int
and to_active_space_mo_int
methods computes the full space MO eInt first before projecting them onto the selected active space. This does not matter if one wants to compute eInts for only one active space configuration. However, the MO eInts will be calculated repeatedly if multiple active space eInts are required.
[13]:
from quri_parts.chem.mol import (
get_active_space_spatial_integrals_from_mo_eint,
get_active_space_spin_integrals_from_mo_eint
)
# Convenient method of computing the active space spatial mo electron integrals
active_space_spatial_integrals = get_active_space_spatial_integrals_from_mo_eint(
active_space_mo=active_space_mo, electron_mo_ints=spatial_mo_e_int_set
)
# Convenient method of computing the active space spin mo electron integrals
active_space_spin_integrals = get_active_space_spin_integrals_from_mo_eint(
active_space_mo=active_space_mo, electron_mo_ints=spatial_mo_e_int_set
)
Compute the electron integrals with QURI Parts in a memory efficient manner#
When the molecule gets larger, it is unrealistic to store the full space mo electron integrals on memory, most especially when we just want to get the eInts of a small active space from a big molecule. In QURI Parts, we provide a memory efficient way of computing the active space integrals while bypassing the full space electron integrals. The idea is to store only the PySCF Mole object and the mo coefficients on memory. The explicit eInts are only released onto the memory on demand.
We first introduce the objects used to perform memory efficient eInt evaluation
AO electron integral:
PySCFAOeIntSet
, which contains:constant: The nuclear repulsion energy.
PySCFAO1eInt
: The AO 1-electron integral.PySCFAO2eInt
: The AO 2-electron integral.
Full space spatial electron integral:
SpatialMOeIntSet
, which contains:constant: The nuclear repulsion energy.
PySCFSpatialMO1eInt
: The spatial MO 1-electron integral.PySCFSpatialMO2eInt
: The spatial MO 2-electron integral.
Full space spin electron integral:
SpinMOeIntSet
, which contains:constant: The nuclear repulsion energy.
PySCFSpinMO1eInt
: The spin MO 1-electron integral.PySCFSpinMO2eInt
: The spin MO 2-electron integral.
The above objects only holds the Molecule objects and the mo coefficients. The actual eInt array are only computed when the .array
property is accessed. For the active space MO eInts, they are stored by:
Active space spatial electron integral:
SpatialMOeIntSet
, which contains:constant: The nuclear repulsion energy.
SpatialMO1eIntArray
: The spatial MO 1-electron integral.SpatialMO2eIntArray
: The spatial MO 2-electron integral.
Active space spin electron integral:
SpinMOeIntSet
, which contains:constant: The nuclear repulsion energy.
SpinMO1eIntArray
: The spin MO 1-electron integral.SpinMO2eIntArray
: The spin MO 2-electron integral.
where the explicit array are released to the memory once the PySCFAOeIntSet.to_active_space_mo_int
or PySCFAOeIntSet.to_active_space_spatial_mo_int
is called.
Computing the AO spatial electron integrals#
We may obtain the PySCFAOeIntSet
using the get_ao_eint_set
function with the store_array_on_memory
option to False
[14]:
# This constructs a PySCFAOeIntSet object, which only holds pyscf mol object on memory
pyscf_ao_eint_set = get_ao_eint_set(
h2o_mo,
store_array_on_memory=False # default to False
)
Computing the spatial and spin MO electron integrals#
As in the AOeIntArraySet
, the full space spatial and spin MO eInts can be calculated by the to_full_space_spatial_mo_int
and to_full_space_mo_int
respectively.
[15]:
# Returns a SpatialMOeIntSet object, which only holds the molecule and the mo coefficients on memory
pyscf_spatial_mo_eint_set = pyscf_ao_eint_set.to_full_space_spatial_mo_int(h2o_mo)
# Returns a SpinMOeIntSet object, which only holds the molecule and the mo coefficients on memory
pyscf_spin_mo_eint_set = pyscf_ao_eint_set.to_full_space_mo_int(h2o_mo)
The explicit electron integrals are only computed on demand when the array
property is accessed
[16]:
# For the spatial MO electron integrals
pyscf_nuclear_repulsion_energy = pyscf_spatial_mo_eint_set.const # computation happens here
pyscf_spatial_1e_int = pyscf_spatial_mo_eint_set.mo_1e_int.array # computation happens here
pyscf_spatial_2e_int = pyscf_spatial_mo_eint_set.mo_2e_int.array # computation happens here
# For the spin MO electron integrals
pyscf_nuclear_repulsion_energy = pyscf_spin_mo_eint_set.const # computation happens here
pyscf_spin_1e_int = pyscf_spin_mo_eint_set.mo_1e_int.array # computation happens here
pyscf_spin_2e_int = pyscf_spin_mo_eint_set.mo_2e_int.array # computation happens here
Getting the active space spin electron integrals#
We may also compute the:
active space spatial MO eInt with the
PySCFAOeIntSet.to_active_space_spatial_mo_int
methodactive space spin MO eInt with the
PySCFAOeIntSet.to_active_space_mo_int
method
just like how we computed them with the AOeIntArraySet
. Note that the computation is performed with pyscf
’s CASCI
once these methods are called.
[17]:
# Convenient method of computing the active space spatial mo electron integrals
pyscf_active_space_spatial_integrals = pyscf_ao_eint_set.to_active_space_spatial_mo_int(active_space_mo)
# Convenient method of computing the active space spin mo electron integrals
pyscf_active_space_spin_integrals = pyscf_ao_eint_set.to_active_space_mo_int(active_space_mo)