Plotting molecular electrostatic potential surfaces (MEPS) with ORCA

It was pissing me off how hard it is to plot decent Molecular Electrostatic Potentials using ORCA and that’s why I’m posting this (so you too don’t have to be pissed off).

First of all, ORCA does have a utility to plot MEPS for you, it is called orca_vpot. It is nicely described in chapter 9.46.10 of the ORCA 5.0.3 manual and in the ORCA Input Library. The command to get the potentials is not difficult: you need the gbw (orca always gives you this), the scfp densities (you may request it to be kept with ! KeepDens), the surfaces to which the potentials will be mapped to (vpot.xyz) and the output vpot.out.

orca_vpot filename.gbw filename.scfp filename.vpot.xyz filename.vpot.out

The gbw, scfp and out files are straightforward; but the surface (vpot.xyz) is complicated to get. Luckily there is a python script to help with that part, provided by Marius Retegan here. I’m preserving the script below but go and say thank you to Marius for his work.

"""
(c) 2013, 2019 Marius Retegan

License: BSD-2-Clause

Description: Create a .cube file of the moleclar electrostatic
             potential (MEP) using ORCA.

Run: python mep.py basename npoints (e.g. python mep.py water 40).

Arguments: basename - file name without the extension;
                      this should be the same for the .gbw and .scfp.
           npoints  - number of grid points per side
                      (80 should be fine)
"""

#!/usr/bin/env python


def read_xyz(xyz):
    atoms = list()
    x = list()
    y = list()
    z = list()

    with open(xyz) as fp:
        # Skip the first two lines.
        next(fp)
        next(fp)
        for line in fp:
            data = line.split()
            atoms.append(data[0])
            x.append(float(data[1]))
            y.append(float(data[2]))
            z.append(float(data[3]))

    return atoms, np.array(x), np.array(y), np.array(z)


def read_vpot(vpot):
    v = list()

    with open(vpot) as fp:
        next(fp)
        for line in fp:
            data = line.split()
            v.append(float(data[3]))

    return np.array(v)

if __name__ == '__main__':
    import os
    import sys
    import subprocess
    import numpy as np

    ang_to_au = 1.0 / 0.5291772083

    elements = [None,
         'H', 'He',
         'Li', 'Be',
         'B', 'C', 'N', 'O', 'F', 'Ne',
         'Na', 'Mg',
         'Al', 'Si', 'P', 'S', 'Cl', 'Ar',
         'K', 'Ca',
         'Sc', 'Ti', 'V', 'Cr', 'Mn', 'Fe', 'Co', 'Ni', 'Cu', 'Zn',
         'Ga', 'Ge', 'As', 'Se', 'Br', 'Kr',
         'Rb', 'Sr',
         'Y', 'Zr', 'Nb', 'Mo', 'Tc', 'Ru', 'Rh', 'Pd', 'Ag', 'Cd',
         'In', 'Sn', 'Sb', 'Te', 'I', 'Xe',
         'Cs', 'Ba',
         'La', 'Ce', 'Pr', 'Nd', 'Pm', 'Sm', 'Eu', 'Gd', 'Tb', 'Dy', 'Ho', 'Er', 'Tm', 'Yb',
         'Lu', 'Hf', 'Ta', 'W', 'Re', 'Os', 'Ir', 'Pt', 'Au', 'Hg',
         'Tl', 'Pb', 'Bi', 'Po', 'At', 'Rn',
         'Fr', 'Ra',
         'Ac', 'Th', 'Pa', 'U', 'Np', 'Pu', 'Am', 'Cm', 'Bk', 'Cf', 'Es', 'Fm', 'Md', 'No',
         'Lr', 'Rf', 'Db', 'Sg', 'Bh', 'Hs', 'Mt', 'Ds', 'Rg', 'Uub']

    basename = sys.argv[1]
    xyz = basename + '.xyz'

    if not os.path.isfile(xyz):
        sys.exit('Could not find the .xyz. To quickly generate one for '
                 'your molecule run: echo 11 | orca_plot {}.gbw -i.'.format(basename))

    atoms, x, y, z = read_xyz(xyz)

    try:
        npoints = int(sys.argv[2])
    except ValueError:
        sys.exit('Invalid number of points: {}'.format(sys.argv[2]))

    natoms = len(atoms)

    extent = 7.0
    xmin = x.min() * ang_to_au - extent
    xmax = x.max() * ang_to_au + extent
    ymin = y.min() * ang_to_au - extent
    ymax = y.max() * ang_to_au + extent
    zmin = z.min() * ang_to_au - extent
    zmax = z.max() * ang_to_au + extent

    with open(basename + '_mep.inp', 'w') as fp:
        fp.write('{0:d}\n'.format(npoints**3))
        for ix in np.linspace(xmin, xmax, npoints, True):
            for iy in np.linspace(ymin, ymax, npoints, True):
                for iz in np.linspace(zmin, zmax, npoints, True):
                    fp.write('{0:12.6f} {1:12.6f} {2:12.6f}\n'.format(ix, iy, iz))

    subprocess.check_call(['orca_vpot', basename + '.gbw', basename + '.scfp',
            basename + '_mep.inp', basename + '_mep.out'])

    vpot = read_vpot(basename + '_mep.out')

    with open(basename + '_mep.cube', 'w') as fp:
        fp.write('Generated with ORCA\n')
        fp.write('Electrostatic potential for ' + basename + '\n')
        fp.write('{0:5d}{1:12.6f}{2:12.6f}{3:12.6f}\n'.format(
            len(atoms), xmin, ymin, zmin))
        fp.write('{0:5d}{1:12.6f}{2:12.6f}{3:12.6f}\n'.format(
            npoints, (xmax - xmin) / float(npoints - 1), 0.0, 0.0))
        fp.write('{0:5d}{1:12.6f}{2:12.6f}{3:12.6f}\n'.format(
            npoints, 0.0, (ymax - ymin) / float(npoints - 1), 0.0))
        fp.write('{0:5d}{1:12.6f}{2:12.6f}{3:12.6f}\n'.format(
            npoints, 0.0, 0.0, (zmax - zmin) / float(npoints - 1)))
        for i, atom in enumerate(atoms):
            index = elements.index(atom)
            fp.write('{0:5d}{1:12.6f}{2:12.6f}{3:12.6f}{4:12.6f}\n'.format(
                index, 0.0, x[i] * ang_to_au, y[i] * ang_to_au, z[i] * ang_to_au))

        m = 0
        n = 0
        vpot = np.reshape(vpot, (npoints, npoints, npoints))
        for ix in range(npoints):
            for iy in range(npoints):
                for iz in range(npoints):
                    fp.write('{0:14.5e}'.format(vpot[ix][iy][iz]))
                    m += 1
                    n += 1
                    if (n > 5):
                        fp.write('\n')
                        n = 0
                if n != 0:
                    fp.write('\n')
                    n = 0

The script can be run just using python mep.py basename npoints (like the example below):

python mep.py water 100

The script will use orca_vpot for you, (yes, you need to have ORCA installed) and do all the hard lifting, generating the potential surfaces in 2 files, a .cube and a .out. Looking at the .out file, the potentials that you can use to map against any surface is the 4th column. I’ve found that using the .out file is kind of difficult (at least for me) but the .cube works perfectly in conjunction with the electronic density.

Let me show you how to use UCSF Chimera to see the MEP generated by ORCA then.

I’m assuming that you already used the python script to get the potentials. Now you must use orca_plot to generate the electron density. For example, running:

orca_plot Water.gbw -i

Select, sequentially, options 1 (Enter type of plot), 2 ((scf) electron density), 4 (Enter number of grid intervals), 120, 5 (Select output file format), 7 (3D   Gaussian cube), 10 (Plot), 11 (Exit). You will get something like Water.eldens.cube.

Now you can use Chimera to open the XYZ file and the electron density going to Tools à Volume Data à Volume Viewer à File à Open map… à Water.eldens.cube. Then go to Tools à Surface Color and select by “electrostatic potential”. In “Potential file” browse to Water_mep.cube that was generated by the python script. Choose your coloring scheme if you want by looking into “Options” and when you are done press “Color”. That is it!

1 thought on “Plotting molecular electrostatic potential surfaces (MEPS) with ORCA

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.