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:

  1. Define a MolecularOrbitals object from the Molecule and the corresponding MO coefficients.

    • If required, specify the active space (ActiveSpace) and create the corresponding MolecularOrbitals object represented by a ActiveSpaceMolecularOrbitals object.

    • If no active space is specified, we term the corresponding MolecularOrbitals as full space molecular orbital, which is represented by a PySCFMolecularOrbitals.

  2. Compute the atomic orbital (AO) eInt represented by AOeIntSet from the full space MolecularOrbitals.

  3. Compute the spatial molecular orbital (MO) eInt (SpatialMOeIntSet) from the AO eInt and the MolecularOrbitals.

  4. 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:

  1. 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.

  2. Use only the PySCF Mole object and the MO coefficients to do the computation with the quri-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 memory

    • AO2eIntArray: 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 memory

    • SpatialMO2eIntArray: 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 memory

    • SpinMO2eIntArray: 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 method

  • spin 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 method

  • active 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 method

  • active 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)