Let's import the psi4
and forte
modules, including the forte.utils
submodule
In [ ]:
import psi4
import forte
import forte.utils
Note: in this tutorial we do not call forte.startup()/cleanup()
because we are not going to do any computation using forte
We will start by generating SCF orbitals via psi4 using the forte.util
In [ ]:
xyz = """
0 1
C 0.722300 0.722300 0.000000
C 0.722300 -0.722300 0.000000
C -0.722300 0.722300 0.000000
C -0.722300 -0.722300 0.000000
H 1.483571 1.483571 0.000000
H 1.483571 -1.483571 0.000000
H -1.483571 1.483571 0.000000
H -1.483571 -1.483571 0.000000
symmetry c1"""
E_scf, wfn = forte.utils.psi4_scf(xyz, 'sto-3g', 'rhf', functional = 'hf')
forte.utils.psi4_cubeprop
functionNext we use a convenience function that generates cube files. Here is some information on it:
def psi4_cubeprop(wfn, path = '.', orbs = [], nocc = 3, nvir = 3, load = False):
"""
Run a psi4 cubeprop computation to generate cube files from a given Wavefunction object
By default this function plots from the HOMO -2 to the LUMO + 2
Parameters
----------
wfn : psi4Wavefunction
A psi4 Wavefunction object
path : str
The path of the directory that will contain the cube files
orbs : list
The list of orbitals to convert to cube files (one based).
nocc : int
The number of occupied orbitals
nvir : int
The number of virtual orbitals
We want to plot two occupied and two virtual orbitals so we pass the arguments nocc=2
and nvir=2
.
In [ ]:
forte.utils.psi4_cubeprop(wfn,path='cubes',nocc=2,nvir=2)
After we execute this command, four cube files are generated in the folder ./cubes
. We can list them with the os.listdir
function:
In [ ]:
import os
os.listdir('./cubes')
In [ ]:
cube = forte.CubeFile('cubes/Psi_a_15_15-A.cube')
A CubeFile
object cannot be directly visualized. However, we can access all the information about the cube file.
In [ ]:
# number of atoms
print(f'cube.natoms() -> {cube.natoms()}')
# number of grid points along each direction
print(f'cube.num() -> {cube.num()}')
The CubeFile
class provides a convenient method (compute_levels
) to determine the isocontour levels that contain a given fraction of the electron density. For example, to compute the isovalues that encompass 85% of the density we call compute_levels('mo',0.85)
, where the argument 'mo'
indicates that the cube file stores the values of a molecular orbital (this is necessary, because the levels are computed differently for the density)
In [ ]:
levels85 = cube.compute_levels('mo',0.85)
levels85
This information is also generated and stored by psi4
in the cube file (this is not standard, only psi4
implements this feature)
In [ ]:
with open('cubes/Psi_a_15_15-A.cube') as f:
print("".join(f.readlines()[:6]))
In [ ]:
renderer = forte.utils.Py3JSRenderer(width=400, height=400)
Once created, we can add it a CubeFile
object and display it. This is a Python widget and it is interactive. Try to interact with it by clicking and dragging the pointer around. You can also zoom the molecule in and out.
In [ ]:
renderer.add_cubefile(cube)
renderer.display()
The CubeFile
class will find the isolevels that correspond to 85% of the density. However, we can select a different value by passing the sumlevel
argument.
In [ ]:
renderer2 = forte.utils.Py3JSRenderer(width=400, height=400)
renderer2.add_cubefile(cube, sumlevel=0.5)
renderer2.display()
We can even specify custom levels and colors via the levels
and colors
parameters
In [ ]:
renderer3 = forte.utils.Py3JSRenderer(width=400, height=400)
renderer3.add_cubefile(cube, levels = [0.06,-0.04],colors = ['#4412B3', '#0DC2FF'])
renderer3.display()
In [ ]:
cube_lumo = forte.CubeFile('cubes/Psi_a_16_16-A.cube')
renderer_lumo = forte.utils.Py3JSRenderer(width=400, height=400)
renderer_lumo.add_cubefile(cube_lumo)
renderer_lumo.display()
In [ ]:
cubes = forte.utils.psi4_cubeprop(wfn,path='cubes',nocc=2,nvir=2,load=True)
There is also a convenience function (cube_viewer
) that allows to plot several cube files al at once. This can be invoked with
In [ ]:
forte.utils.cube_viewer(cubes)
This function is customizable, for example we can change the size and select a different font (or even not display the text)
In [ ]:
forte.utils.cube_viewer(cubes, width=500, height=500, font_size = 20, font_family = 'Times New Roman')
The CubeFile
class supports three type of operations:
scale(double factor)
: scale all the values on the grid by factor
:
$$ \phi(\mathbf{r}_i) \leftarrow \mathtt{factor} * \phi(\mathbf{r}_i) $$add(CubeFile cube)
: add to this cube file the grid values stored in cube:
$$ \phi(\mathbf{r}_i) \leftarrow \phi(\mathbf{r}_i) + \psi(\mathbf{r}_i) $$pointwise_product(CubeFile cube)
: multiply each point of this cube file with the values stored in cube:
$$ \phi(\mathbf{r}_i) \leftarrow \phi(\mathbf{r}_i) * \psi(\mathbf{r}_i) $$To evaluate the density corresponing to the HOMO we can do just compute the pointwise product with itself
In [ ]:
dens = forte.CubeFile(cube)
dens.pointwise_product(dens)
Then we can render it using the compute_levels
function to find the appropriate levels. Here we cannot let the renderer determine the levels because it cannot tell that this is a density.
In [ ]:
dens_renderer = forte.utils.Py3JSRenderer(width=400, height=400)
dens_levels = dens.compute_levels('density',0.6)
dens_renderer.add_cubefile(dens, levels = dens_levels) # need to specify a sensible rendering level
dens_renderer.display()
If we want, we can also save a cubefile to disk using the save
function
In [ ]:
dens.save('cubes/dens.cube')