Forte Tutorial 2: Running forte in Jupyter notebooks


In this tutorial we are going to explore how to interact with forte in Jupyter notebooks using the Python API.

Import modules

The first step necessary to interact with forte is to import psi4 and forte


In [ ]:
import psi4
import forte

We need to generate SCF orbitals via psi4, so we will use some of the techniques used in Tutorial 1. However, we'll make a function


In [ ]:
def run_psi4(geom, basis = 'sto-3g', reference = 'rhf'):
    # build the molecule object
    mol = psi4.geometry(geom)

    # set basis/options
    psi4.set_options({'basis': basis, 'reference' : reference, 'scf_type': 'pk'})

    # pipe output to the file output.dat
    psi4.core.set_output_file('output.dat', False)

    # run scf and return the energy and a wavefunction object (will work only if pass return_wfn=True)
    E_scf, wfn = psi4.energy('scf', return_wfn=True)

    psi4.core.clean()
    return (E_scf, wfn)

and then get the energy and wavefunction using this function


In [ ]:
# setup xyz geometry
geom = """
O
H 1 1.0
H 1 1.0 2 180.0
"""
(E_scf, wfn) = run_psi4(geom)
print(f'SCF Energy = {E_scf}')

In later tutorials we will use the function forte.utils.psi4_scf(), which generalizes the one shown above to include the ability to pass options.

Starting Forte

To use Forte, we need to start it up first. This makes sure that libraries like ambit are properly initialized


In [ ]:
forte.startup()

Reading options via psi4 (will change in the future)

Now we can start to interact with Forte. The first thing we will do is to read forte-specific options. This interface is a bit clunky, and so it might be changed and improved sometime in the future


In [ ]:
from forte import forte_options

options = psi4.core.get_options() # options = psi4 option object
options.set_current_module('FORTE') # read options labeled 'FORTE'
forte_options.get_options_from_psi4(options)

Setting the molecular orbital spaces

A common first task when interacting with the Forte API is to compute 1- and 2-electron molecular integrals. The integral code needs to know if any orbitals will be dropped off from a computation. To do so we need to pass a tuple that specifies how many doubly occupied (docc) and unoccupied (uocc) orbitals to drop.


In [ ]:
# Setup forte and prepare the active space integral class
mos_spaces = {'FROZEN_DOCC' :     [1,0,0,0,0,0,0,0], # freeze the O 1s orbital
              'RESTRICTED_DOCC' : [1,0,0,0,0,1,0,0]}
mo_space_info = forte.make_mo_space_info_from_map(wfn,mos_spaces,[])

Task 1: Take a look at the file forte/src/base_classes/mo_space_info.h. This class stores information about molecule orbital space. However, only one function is exposed. Create a github/forte pull request to expose functions that you need to find out the number of orbital in each space (including symmetry).


In [ ]:
mo_space_info.size('ACTIVE')

Building a ForteIntegral object to read integrals from psi4

In Forte there are two classes responsible for handling integrals:

  • ForteIntegral: reads the integrals from psi4 and stores them in varios formats (conventional, density fitting, Cholesky, ...).
  • ActiveSpaceIntegrals: stores a copy of all integrals and it is used by active space methods. This class only stores a subset of the integrals and includes an effective potential due to non-active doubly occupied orbitals.

We will first build the ForteIntegral object via the function make_forte_integrals


In [ ]:
ints = forte.make_forte_integrals(wfn, forte_options, mo_space_info)
print(f'Number of molecular orbitals: {ints.nmo()}')
print(f'Number of correlated molecular orbitals: {ints.ncmo()}')

Task 2: Take a look at the file forte/src/integrals/integrals.h. This class allows the user to access the 1-/2-electron integrals via functions like double oei_a(size_t p, size_t q). However, these functions are not exposed to the python side. Expose one of these functions and commit them to github/forte.

Creating an ActiveSpaceIntegrals object to access integral elements

We can now create an ActiveSpaceIntegrals object using the ForteIntegral object and a couple of extra bits of information stored in the MOSpaceInfo object. When we create this object we need to specify two orbital spaces:

  • the active orbitals
  • the non-active doubly orbitals ActiveSpaceIntegrals uses this information to create 1-/2-electron integrals for the active orbitals

In [ ]:
# the space that defines the active orbitals. We select only the 'ACTIVE' part
active_space = 'ACTIVE'
# the space(s) with non-active doubly occupied orbitals
core_spaces = ['RESTRICTED_DOCC']

as_ints = forte.make_active_space_ints(mo_space_info, ints, active_space, core_spaces)

Getting the integrals from the ActiveSpaceIntegrals object

The ActiveSpaceIntegrals object exposes several quantitites


In [ ]:
print(f'Frozen-core energy = {as_ints.frozen_core_energy()}')
print(f'Nuclear repulsion energy = {as_ints.nuclear_repulsion_energy()}')
print(f'Scalar energy = {as_ints.scalar_energy()}')

We can also access individual elements of the 1-/2-electron integrals. This class stores five types of integrals:

  • the alpha effective one-electron integrals, $\langle\phi_p|\hat{h}|\phi_q\rangle$, via the oei_a function.
  • the beta effective one-electron integrals, $\langle\phi_\bar{p}|\hat{h}|\phi_\bar{q}\rangle$, via the oei_b function.
  • the alpha-alpha antisymmetrized two-electron integrals, $\langle\phi_p \phi_q\|\phi_r\phi_s\rangle$, via the tei_aa function.
  • the alpha-beta two-electron integrals, $\langle\phi_p \phi_\bar{q}\|\phi_r\phi_\bar{s}\rangle = \langle\phi_p \phi_\bar{q}|\phi_r\phi_\bar{s}\rangle$, via the tei_ab function
  • the beta-beta two-electron integrals, $\langle\phi_\bar{p}\phi_\bar{q}\|\phi_\bar{r}\phi_\bar{s}\rangle$, via the tei_bb function

Here we denote beta spin orbitals with a bar above the orbital index.


In [ ]:
print(f'<0|h|0> = {as_ints.oei_a(0,0)}')
print(f'<0a0a||0a0a> = {as_ints.tei_aa(0,0,0,0)}')
print(f'<0a0b||0a0b> = {as_ints.tei_ab(0,0,0,0)}')

We can also print all the integrals at once (see the output.dat file)


In [ ]:
as_ints.print()

Closing Forte

After running Forte, we should close it down


In [ ]:
forte.cleanup()