Visualizations in the OpenFF Toolkit

OpenFF Molecule and Topology objects provide some facilities for visualization in Jupyter notebooks. This can be useful for inspecting molecules and topologies!

There are two ways to invoke the visualization:

There are currently three backends, each using a different external tool:

  • RDKit (default)

  • OpenEye (requires OpenEye Toolkits installed and licensed)

  • NGLview (requires conformers)

The backend can be explicitly chosen with the backend argument.

Topology visualization works similarly by calling Topology.visualize(). However, there is no backend argument and it defaults to using NGLview for a 3D representation.

Troubleshooting if NGLView doesn't work This notebook below demonstrates usage of nglview with openforcefield. This can be tricky to get working. You may need to run an additional command after creating the Conda environment.

To configure for use with Jupyter Notebooks:

    jupyter-nbextension enable nglview --py --sys-prefix

To use with Jupyter Lab, configure with:

    jupyter labextension install  nglview-js-widgets
    jupyter-labextension install @jupyter-widgets/jupyterlab-manager

For NGLView ≥ 3.0.0, the above should not be necessary; however, it is not yet compatible with Jupyter Lab ≥ 4.0.0.

from openff.toolkit import Molecule, Topology

Implicit visualization

When you run a cell with an object in the last line, IPython makes an effort to display some sort of representation - for a class or built-in type, this looks like its __repr__ method. Here we call this implicit visualization.

The toolkit will try to visualize a molecule using nglview if it has conformers. If it does not, it tries rdkit and then openeye (in that order).

m = Molecule.from_smiles("CCCCOCC")
m
../../../../_images/33f5c12448c42bf807285f04aa98dfead4a0ce3813d1c60dd66b119c4b8b1939.svg

The regular display() call works on Molecule objects too.

display(m)  # noqa
../../../../_images/33f5c12448c42bf807285f04aa98dfead4a0ce3813d1c60dd66b119c4b8b1939.svg

Explicit visualization

Explicit visualization is how we’re describing the use of Molecule.visualize(). It works as one would expect:

m.visualize()
../../../../_images/33f5c12448c42bf807285f04aa98dfead4a0ce3813d1c60dd66b119c4b8b1939.svg

This method can take a backend parameter, which defaults to rdkit:

m.visualize(backend="rdkit")
../../../../_images/33f5c12448c42bf807285f04aa98dfead4a0ce3813d1c60dd66b119c4b8b1939.svg

openeye can also be used, if available:

try:
    from openeye import oechem

    assert oechem.OEChemIsLicensed()

    m.visualize(backend="openeye")
except (ImportError, AssertionError):
    print('Visualizing with `backend="openeye"` requires the OpenEye Toolkits')
Visualizing with `backend="openeye"` requires the OpenEye Toolkits

nglview, if installed, can only be used if conformers have been generated:

try:
    m.visualize(backend="nglview")  # this will fail because we have no conformers yet
except ValueError as excinfo:
    print(str(excinfo))
    print(f"this molecule has {m.n_conformers=}")
Visualizing with NGLview requires that the molecule has conformers, found self.conformers=None
this molecule has m.n_conformers=0

But, once you generate them, it works! You can zoom in/out, rotate and translate the molecule. You can even inspect the different conformers (if available) using the trajectory player:

m.generate_conformers()
m.visualize(backend="nglview")

For example, a benzene molecule will not typically have multiple conformers, so you won’t see the trajectory player.

benzene = Molecule.from_smiles("c1ccccc1")
benzene.generate_conformers()
benzene

Notice that, once conformers are available, the implicit representation will use nglview to provide a 3D visualization.

It’s also possible with any backend to hide non-polar hydrogens, which may produce a more readable image:

m.visualize(backend="nglview", show_all_hydrogens=False)
m.visualize(backend="rdkit", show_all_hydrogens=False)
../../../../_images/02f8e8f40cfb020b59e82a4fd31b449967b2b9482bf44f9ae8cf48a11169dc22.svg

If you’re using a strangely-shaped molecule or otherwise want the display to be larger or smaller, you can fiddle with the width and height arguments (defaults are 500 and 300). These arguments are ignored with the NGLview backend.

ether = Molecule.from_smiles(12 * "CCO")
ether.visualize(
    width=1000,
    height=100,
    show_all_hydrogens=False,
)
../../../../_images/57e0e7fb2825f1386226524026ab3b7a0069968c761fc0033c81823e53e1d5bb.svg

Topologies can also be explicitly visualized:

from openff.toolkit.utils.utils import get_data_file_path

topology = Topology.from_pdb(
    get_data_file_path("systems/test_systems/T4_lysozyme_water_ions.pdb")
)
w = topology.visualize()
w

However, they don’t currently support implicit visualization for performance reasons. Note that, again for performance reasons, Topology by default does not guarantee to correctly represent connectivity; in the future there may be an implementation that makes an effort to do this.