Visualization

This example demonstrates how to use the view() function, to visualize molecular and periodic systems in PLAMS. This is further described under component View.

To follow along, either

Worked Example

Visualizing Structures with PLAMS

Rendering Images

PLAMS molecules can be simply passed to the view function to render an image using AMSview. These images can then easily be displayed in a notebook.

For example, below we create a molecule of caffeine from its SMILES string, and view it.

import scm.plams as plams

caffeine = plams.from_smiles("CN1C=NC2=C1C(=O)N(C(=O)N2C)C", forcefield="uff")
plams.view(caffeine)
../../_images/visualization_3_0.png

The size of the image can be controlled via the height and width arguments, which specify the size in pixels. An additional padding argument can also be used to control how much space (in Angstrom) is added or trimmed from the edges of the image.

To save the image file, the picture_path argument can be supplied which will also persist the image at the given location. Furthermore, the molecule can also be opened interactively in AMSview using the option open_window = True.

Examples of all these options are demonstrated below for beta-carotene.

beta_carotene = plams.from_smiles(
    "CC1CCC/C(C)=C1/C=C/C(C)=C/C=C/C(C)=C/C=C/C=C(C)/C=C/C=C(C)/C=C/C2=C(C)/CCCC2(C)C", forcefield="uff"
)

plams.view(
    beta_carotene,
    width=1000,
    height=400,
    padding=-7,
    picture_path="beta-carotene.png",
    open_window=True,
)
../../_images/visualization_5_0.png

View Directions

The view method aims to make it simple to view your system in the most informative way. As such, the direction argument can be provided to change the view angle.

The direction, is composed of multiple parts and can be used to view:

  • along an axis

  • with a small_tilt, normal tilt or large_tilt

  • down a corner

In addition, the axis can be a:

  • cartesian axis - x, y, z

  • lattice vector - a, b, c

  • principal component axis - pca1, pca2, pca3

Example directions are therefore:

  • along_z, view along the cartesian z axis (default value)

  • tilt_a, view along the c lattice vector with a tilt

  • corner_pca2, view down the corner of a principal component

Illustrations of these are provided below. Further examples can be found in the Periodic Systems section.

# Function to display three images in a row
import matplotlib.pyplot as plt


def plot_three_images(images):
    fig, axes = plt.subplots(1, 3, figsize=(16, 16))  # 1 row, 3 columns
    for i, img in enumerate(images):
        axes[i].imshow(img)
        axes[i].axis("off")
    plt.show()
images = [
    plams.view(caffeine, direction="along_x", width=400),
    plams.view(caffeine, direction="along_y", width=400),
    plams.view(caffeine, direction="along_z", width=400),
]
plot_three_images(images)
../../_images/visualization_9_0.png
butane = plams.from_smiles("CCCC", forcefield="uff")

images = [
    plams.view(butane, direction="along_pca1", width=400),
    plams.view(butane, direction="along_pca2", width=400),
    plams.view(butane, direction="along_pca3", width=400),
]
plot_three_images(images)
../../_images/visualization_10_0.png
images = [
    plams.view(butane, direction="corner_pca1", width=400),
    plams.view(butane, direction="corner_pca2", width=400),
    plams.view(butane, direction="corner_pca3", width=400),
]
plot_three_images(images)
../../_images/visualization_11_0.png

If you want finer control over the view angle, this is possible through the ViewConfig (note that more generally, all settings can be passed in via the ViewConfig, and a sub-set of the most frequently used settings can be set directly via arguments).

Here, the normal option can be provided, which specifies the normal to the view plane. The normal_basis can also be set to xyz (cartesian), abc (lattice) or pca (principal components), which defines which basis the normal vector is in.

plams.view(butane, config=plams.ViewConfig(normal=(1, 0, 1), normal_basis="pca"))
../../_images/visualization_13_0.png

Regions and Labels

Atomic regions can be displayed by using the option show_regions=True. The regions then appear as color-coded translucent spheres on the molecule.

For example, with our caffeine molecule, we can define QM and MM regions and highlight these in the image.

# Identify methyl groups
methyl_atoms = []
for atom in caffeine.atoms:
    hydrogens = [n for n in atom.neighbors() if n.symbol == "H"]
    if atom.symbol == "C" and len(hydrogens) == 3:
        methyl_atoms.append(atom)
        methyl_atoms += hydrogens

# Set MM region as the methyl groups, QM as all other atoms
for atom in caffeine.atoms:
    if atom not in methyl_atoms:
        atom.properties.region = {"QM"}
    else:
        atom.properties.region = {"MM"}

plams.view(caffeine, show_regions=True)
../../_images/visualization_16_0.png

In addition, labels can also be displayed on the atoms, to indicate the elements or atom names.

These are toggled through the show_atom_labels and atom_label_type arguments. Further customization options are also available in the ViewConfig object, which can be passed to the config argument.

See the example below which also allows tuning of the atom label size and color:

plams.view(
    caffeine,
    config=plams.ViewConfig(
        show_atom_labels=True,
        atom_label_color="#32CD32",
        atom_label_size=1.7,
    ),
)
../../_images/visualization_18_0.png

Periodic Systems

The view function works equally well for periodic systems as for molecular systems. For these systems, additional options are available for viewing the lattice and unit cell.

We set up various systems here as examples:

  • 1D carbon nanotube

  • 2D platinum surface

  • 3D crystal

  • 3D water box

First, we set up and view the carbon nanotube. By using show_unit_cell_edges=False argument we disable the default behaviour for displaying the unit cell.

import ase
import ase.build as build
import numpy as np

nanotube_atoms = build.nanotube(6, 0, length=4)
nanotube = plams.fromASE(nanotube_atoms)


# Add bonds to PLAMS molecule from neighbour list
def add_bonds(ase_atoms, mol, cutoff=1.5):
    nl_i, nl_j = ase.neighborlist.neighbor_list("ij", ase_atoms, cutoff)
    for i, j in zip(nl_i, nl_j):
        mol.add_bond(mol[i + 1], mol[j + 1])


add_bonds(nanotube_atoms, nanotube)

# Rotate from z-axis to x-axis (default in AMS)
nanotube.rotate([[0, 0, 1], [0, 1, 0], [-1, 0, 0]], lattice=True)

plams.view(nanotube, show_unit_cell_edges=False)
../../_images/visualization_22_0.png

Next, we create the platinum surface, and use the option show_lattice_vectors=True to add the lattice vectors onto the view.

pt_surface = plams.fromASE(build.fcc111("Pt", size=(4, 4, 3), vacuum=5.0, orthogonal=True, periodic=True))
pt_surface.lattice.pop()

plams.view(pt_surface, show_unit_cell_edges=False, show_lattice_vectors=True)
../../_images/visualization_24_0.png

Next we create a non-orthorhombic crystal structure - the wurtzite structure of zinc sulfide.

This demonstrates the further direction options that are available for periodic structures. In this case, we can also choose to view relative to the lattice vectors (a, b, c) as well as the cartesian axes (x, y, z).

For example, below we choose to view along each of the lattice vectors in turn using along_a, along_b and along_c.

Note that in this case we also set fixed_atom_size=False to scale the atoms according to their radii.

wurtzite_unit_cell = build.bulk("ZnS", crystalstructure="wurtzite", a=3.81)
wurtzite = plams.fromASE(build.make_supercell(wurtzite_unit_cell, np.diag([2, 2, 2])))

images = [
    plams.view(wurtzite, fixed_atom_size=False, show_lattice_vectors=True, direction="along_a", width=400),
    plams.view(wurtzite, fixed_atom_size=False, show_lattice_vectors=True, direction="along_b", width=400),
    plams.view(wurtzite, fixed_atom_size=False, show_lattice_vectors=True, direction="along_c", width=400),
]

plot_three_images(images)
../../_images/visualization_26_0.png

As a final example of a periodic structure, we generate an orthorhombic box of water.

Here we can also choose to view in a variety of different orientations, which emphasise the periodicity of the system. For this we set direction = tilt_x, to show the full unit cell.

In addition, we pass a ViewConfig object which exposes additional settings to fine-tune the image.

water_box = plams.packmol(
    plams.from_smiles("O"),
    density=1.0,
    box_bounds=[0.0, 0.0, 0.0, 8.0, 12.0, 14.0],
)

config = plams.ViewConfig(unit_cell_edge_thickness=0.1)
plams.view(water_box, config=config, direction="tilt_x", show_lattice_vectors=True)
../../_images/visualization_28_0.png

Backends

By default, the view method will try and use AMSview to generate images. However, if AMSview is not available, another backend will be used. A specific backend can also be explicitly selected.

The available options are:

  • amsview: uses the AMSview program

  • amsview_xvfb: for use on a headless machine (no GUI), when the Xvfb program is available

  • ase_plot: uses ASE and matplotlib

Efforts have been made to ensure the view options when using different backends are as close as possible, and so they can simply be switched. For example, see the following images reproduced using the ASE backend:

plams.view(
    caffeine,
    width=350,
    height=350,
    show_regions=True,
    backend="ase_plot",
    show_atom_labels=True,
)
../../_images/visualization_31_0.png
plams.view(
    water_box,
    width=350,
    height=350,
    config=config,
    direction="tilt_z",
    show_lattice_vectors=True,
    backend="ase_plot",
)
../../_images/visualization_32_0.png

Complete Python code

#!/usr/bin/env amspython
# coding: utf-8

# ## Visualizing Structures with PLAMS

# ### Rendering Images

# PLAMS molecules can be simply passed to the `view` function to render an image using AMSview. These images can then easily be displayed in a notebook.
#
# For example, below we create a molecule of caffeine from its SMILES string, and view it.

import scm.plams as plams

caffeine = plams.from_smiles("CN1C=NC2=C1C(=O)N(C(=O)N2C)C", forcefield="uff")
plams.view(caffeine)


# The size of the image can be controlled via the `height` and `width` arguments, which specify the size in pixels. An additional `padding` argument can also be used to control how much space (in Angstrom) is added or trimmed from the edges of the image.
#
# To save the image file, the `picture_path` argument can be supplied which will also persist the image at the given location. Furthermore, the molecule can also be opened interactively in AMSview using the option `open_window = True`.
#
# Examples of all these options are demonstrated below for beta-carotene.

beta_carotene = plams.from_smiles(
    "CC1CCC/C(C)=C1/C=C/C(C)=C/C=C/C(C)=C/C=C/C=C(C)/C=C/C=C(C)/C=C/C2=C(C)/CCCC2(C)C", forcefield="uff"
)

plams.view(
    beta_carotene,
    width=1000,
    height=400,
    padding=-7,
    picture_path="beta-carotene.png",
    open_window=True,
)


# ### View Directions

# The `view` method aims to make it simple to view your system in the most informative way. As such, the `direction` argument can be provided to change the view angle.
#
# The `direction`, is composed of multiple parts and can be used to view:
#
# - __along__ an axis
# - with a __small_tilt__, normal __tilt__ or __large_tilt__
# - down a __corner__
#
# In addition, the axis can be a:
#
# - cartesian axis - __x__, __y__, __z__
# - lattice vector - __a__, __b__, __c__
# - principal component axis - __pca1__, __pca2__, __pca3__
#
# Example directions are therefore:
#
# - `along_z`, view along the cartesian `z` axis (default value)
# - `tilt_a`, view along the `c` lattice vector with a tilt
# - `corner_pca2`, view down the corner of a principal component
#
# Illustrations of these are provided below. Further examples can be found in the _Periodic Systems_ section.

# Function to display three images in a row
import matplotlib.pyplot as plt


def plot_three_images(images):
    fig, axes = plt.subplots(1, 3, figsize=(16, 16))  # 1 row, 3 columns
    for i, img in enumerate(images):
        axes[i].imshow(img)
        axes[i].axis("off")
    plt.show()


images = [
    plams.view(caffeine, direction="along_x", width=400),
    plams.view(caffeine, direction="along_y", width=400),
    plams.view(caffeine, direction="along_z", width=400),
]
plot_three_images(images)


butane = plams.from_smiles("CCCC", forcefield="uff")

images = [
    plams.view(butane, direction="along_pca1", width=400),
    plams.view(butane, direction="along_pca2", width=400),
    plams.view(butane, direction="along_pca3", width=400),
]
plot_three_images(images)


images = [
    plams.view(butane, direction="corner_pca1", width=400),
    plams.view(butane, direction="corner_pca2", width=400),
    plams.view(butane, direction="corner_pca3", width=400),
]
plot_three_images(images)


# If you want finer control over the view angle, this is possible through the `ViewConfig` (note that more generally, all settings can be passed in via the `ViewConfig`, and a sub-set of the most frequently used settings can be set directly via arguments).
#
# Here, the `normal` option can be provided, which specifies the normal to the view plane. The `normal_basis` can also be set to `xyz` (cartesian), `abc` (lattice) or `pca` (principal components), which defines which basis the normal vector is in.

plams.view(butane, config=plams.ViewConfig(normal=(1, 0, 1), normal_basis="pca"))


# ### Regions and Labels

# Atomic regions can be displayed by using the option `show_regions=True`. The regions then appear as color-coded translucent spheres on the molecule.
#
# For example, with our caffeine molecule, we can define QM and MM regions and highlight these in the image.

# Identify methyl groups
methyl_atoms = []
for atom in caffeine.atoms:
    hydrogens = [n for n in atom.neighbors() if n.symbol == "H"]
    if atom.symbol == "C" and len(hydrogens) == 3:
        methyl_atoms.append(atom)
        methyl_atoms += hydrogens

# Set MM region as the methyl groups, QM as all other atoms
for atom in caffeine.atoms:
    if atom not in methyl_atoms:
        atom.properties.region = {"QM"}
    else:
        atom.properties.region = {"MM"}

plams.view(caffeine, show_regions=True)


# In addition, labels can also be displayed on the atoms, to indicate the elements or atom names.
#
# These are toggled through the `show_atom_labels` and `atom_label_type` arguments. Further customization options are also available in the `ViewConfig` object, which can be passed to the `config` argument.
#
# See the example below which also allows tuning of the atom label size and color:

plams.view(
    caffeine,
    config=plams.ViewConfig(
        show_atom_labels=True,
        atom_label_color="#32CD32",
        atom_label_size=1.7,
    ),
)


# ## Periodic Systems

# The `view` function works equally well for periodic systems as for molecular systems. For these systems, additional options are available for viewing the lattice and unit cell.
#
# We set up various systems here as examples:
#
# - 1D carbon nanotube
# - 2D platinum surface
# - 3D crystal
# - 3D water box

# First, we set up and view the carbon nanotube. By using `show_unit_cell_edges=False` argument we disable the default behaviour for displaying the unit cell.

import ase
import ase.build as build
import numpy as np

nanotube_atoms = build.nanotube(6, 0, length=4)
nanotube = plams.fromASE(nanotube_atoms)


# Add bonds to PLAMS molecule from neighbour list
def add_bonds(ase_atoms, mol, cutoff=1.5):
    nl_i, nl_j = ase.neighborlist.neighbor_list("ij", ase_atoms, cutoff)
    for i, j in zip(nl_i, nl_j):
        mol.add_bond(mol[i + 1], mol[j + 1])


add_bonds(nanotube_atoms, nanotube)

# Rotate from z-axis to x-axis (default in AMS)
nanotube.rotate([[0, 0, 1], [0, 1, 0], [-1, 0, 0]], lattice=True)

plams.view(nanotube, show_unit_cell_edges=False)


# Next, we create the platinum surface, and use the option `show_lattice_vectors=True` to add the lattice vectors onto the view.

pt_surface = plams.fromASE(build.fcc111("Pt", size=(4, 4, 3), vacuum=5.0, orthogonal=True, periodic=True))
pt_surface.lattice.pop()

plams.view(pt_surface, show_unit_cell_edges=False, show_lattice_vectors=True)


# Next we create a non-orthorhombic crystal structure - the wurtzite structure of zinc sulfide.
#
# This demonstrates the further `direction` options that are available for periodic structures. In this case, we can also choose to view relative to the lattice vectors (`a`, `b`, `c`) as well as the cartesian axes (`x`, `y`, `z`).
#
# For example, below we choose to view along each of the lattice vectors in turn using `along_a`, `along_b` and `along_c`.
#
# Note that in this case we also set `fixed_atom_size=False` to scale the atoms according to their radii.

wurtzite_unit_cell = build.bulk("ZnS", crystalstructure="wurtzite", a=3.81)
wurtzite = plams.fromASE(build.make_supercell(wurtzite_unit_cell, np.diag([2, 2, 2])))

images = [
    plams.view(wurtzite, fixed_atom_size=False, show_lattice_vectors=True, direction="along_a", width=400),
    plams.view(wurtzite, fixed_atom_size=False, show_lattice_vectors=True, direction="along_b", width=400),
    plams.view(wurtzite, fixed_atom_size=False, show_lattice_vectors=True, direction="along_c", width=400),
]

plot_three_images(images)


# As a final example of a periodic structure, we generate an orthorhombic box of water.
#
# Here we can also choose to view in a variety of different orientations, which emphasise the periodicity of the system. For this we set `direction = tilt_x`, to show the full unit cell.
#
# In addition, we pass a `ViewConfig` object which exposes additional settings to fine-tune the image.

water_box = plams.packmol(
    plams.from_smiles("O"),
    density=1.0,
    box_bounds=[0.0, 0.0, 0.0, 8.0, 12.0, 14.0],
)

config = plams.ViewConfig(unit_cell_edge_thickness=0.1)
plams.view(water_box, config=config, direction="tilt_x", show_lattice_vectors=True)


# ### Backends

# By default, the `view` method will try and use AMSview to generate images. However, if AMSview is not available, another backend will be used. A specific backend can also be explicitly selected.
#
# The available options are:
#
# - `amsview`: uses the AMSview program
# - `amsview_xvfb`: for use on a headless machine (no GUI), when the [Xvfb](https://www.x.org/archive/X11R7.7/doc/man/man1/Xvfb.1.xhtml) program is available
# - `ase_plot`: uses ASE and matplotlib
#
# Efforts have been made to ensure the view options when using different backends are as close as possible, and so they can simply be switched. For example, see the following images reproduced using the ASE backend:

plams.view(
    caffeine,
    width=350,
    height=350,
    show_regions=True,
    backend="ase_plot",
    show_atom_labels=True,
)


plams.view(
    water_box,
    width=350,
    height=350,
    config=config,
    direction="tilt_z",
    show_lattice_vectors=True,
    backend="ase_plot",
)