On-the-fly plotting of atoms

Due to the size of typical Asap simulations (millions of atoms for hundreds of thousands of timesteps) it is impractical to store all data generated. Often, there is a need to generate plots during the simulation, so e.g. a movie can be generated.

Many of the visualization methods of the ASE can be used in a running simulation. The PrimiPlotter tool has in particular been designed for this task, is was moved from ASE to Asap in September 2020.

The PrimiPlotter

The PrimiPlotter is a relatively primitive plotting module, generating output as PostScript files (i.e. suitable for inclusion in LaTeX documents) or in various other graphics file formats. It is also possible to render the output on the screen, although that is less useful for on-the-fly plotting.

The PrimiPlotter is intended to be used as an observer, so it is informed by the dynamics object when to generate a plot. This is best illustrated with an example:

from asap3.visualize.primiplotter import *

[ ... ]

dynamics = VelocityVerlet(atoms, 5*units.fs)
plotter = PrimiPlotter(atoms)
plotter.set_output(PostScriptFile("mysim"))
dynamics.attach(plotter.update, interval=100)
dynamics.run(10000)

This will run Velocity Verlet dynamics for 10000 time steps, at every 100 time steps a plot is generated. The plots will be stored in files called mysim0000.ps, mysim0001.ps etc.

The PrimiPlotter contains a number of methods for orienting the plot, coloring the atoms, removing some atoms etc.

PrimiPlotter / ParallelPrimiPlotter interface

There are two objects, PrimiPlotter and ParallelPrimiPlotter, intended for serial and parallel simulations, respectively. Their interface is identical (and they should really be merged into a single object).

The PrimiPlotter plots atoms during simulations, extracting the relevant information from the list of atoms. It is created using the list of atoms as an argument to the constructor. Then one or more output devices must be attached using set_output(device). The list of supported output devices is at the end.

The atoms are plotted as circles. The system is first rotated using the angles specified by set_rotation([vx, vy, vz]). The rotation is vx degrees around the x axis (positive from the y toward the z axis), then vy degrees around the y axis (from x toward z), then vz degrees around the z axis (from x toward y). The rotation matrix is the same as the one used by RasMol.

Per default, the system is scaled so it fits within the canvas (autoscale mode). Autoscale mode is enabled and disables using autoscale(“on”) or autoscale(“off”). A manual scale factor can be set with set_scale(scale), this implies autoscale(“off”). The scale factor (from the last autoscale event or from set_scale) can be obtained with get_scale(). Finally, an explicit autoscaling can be triggered with autoscale(“now”), this is mainly useful before calling get_scale or before disabling further autoscaling. Finally, a relative scaling factor can be set with SetRelativeScaling(), it is multiplied to the usual scale factor (from autoscale or from set_scale). This is probably only useful in connection with autoscaling.

The radii of the atoms are obtained from the first of the following methods which work:

  1. If the radii are specified using PrimiPlotter.set_radii(r), they are used. Must be an array, or a single number.

  2. If the atoms has a get_atomic_radii() method, it is used. This is unlikely.

  3. If the atoms has a get_atomic_numbers() method, the corresponding covalent radii are extracted from the ASE.ChemicalElements module.

  4. If all else fails, the radius is set to 1.0 Angstrom.

The atoms are colored using the first of the following methods which work.

  1. If colors are explicitly set using PrimiPlotter.set_colors(), they are used.

  2. If these colors are specified as a dictionary, the tags (from atoms.get_tags()) are used as an index into the dictionary to get the actual colors of the atoms.

  3. If a color function has been set using PrimiPlotter.set_color_function(), it is called with the atoms as an argument, and is expected to return an array of colors.

  4. If the atoms have a get_colors() method, it is used to get the colors.

  5. If these colors are specified as a dictionary, the tags (from atoms.get_tags()) are used as an index into the dictionary to get the actual colors of the atoms.

  6. If all else fails, the atoms will be white.

The colors are specified as an array of colors, one color per atom. Each color is either a real number from 0.0 to 1.0, specifying a grayscale (0.0 = black, 1.0 = white), or an array of three numbers from 0.0 to 1.0, specifying RGB values. The colors of all atoms are thus a Numerical Python N-vector or a 3xN matrix.

In cases 1a and 3a above, the keys of the dictionary are integers, and the values are either numbers (grayscales) or 3-vectors (RGB values), or strings with X11 color names, which are then translated to RGB values. Only in case 1a and 3a are strings recognized as colors.

Some atoms may be invisible, and thus left out of the plot. Invisible atoms are determined from the following algorithm. Unlike the radius or the coloring, all points below are tried and if an atom is invisible by any criterion, it is left out of the plot.

  1. All atoms are visible.

  2. If PrimiPlotter.set_invisible() has be used to specify invisible atoms, any atoms for which the value is non-zero becomes invisible.

  3. If an invisiblility function has been set with PrimiPlotter.set_invisibility_function(), it is called with the atoms as argument. It is expected to return an integer per atom, any non-zero value makes that atom invisible.

  4. If a cut has been specified using set_cut, any atom outside the cut is made invisible.

Note that invisible atoms are still included in the algorithm for positioning and scaling the plot.

The following output devices are implemented.

PostScriptFile(prefix): Create PS files names prefix0000.ps etc.

PnmFile(prefix): Similar, but makes PNM files.

PngFile(prefix): Similar, but makes PNG files.

JpegFile(prefix): Similar, but makes JPEG files.

X11Window(): Show the plot in an X11 window using ghostscript.

Output devices writing to files take an extra optional argument to the constructor, compress, specifying if the output file should be gzipped. This is not allowed for some (already compressed) file formats.

Instead of a filename prefix, a filename containing a % can be used. In that case the filename is expected to expand to a real filename when used with the Python string formatting operator (%) with the frame number as argument. Avoid generating spaces in the file names: use e.g. %03d instead of %3d.

set_output(self, device)

Attach an output device to the plotter.

set_dimensions(self, dims)

Set the size of the canvas (a 2-tuple).

plot(self)

Create a plot now. Does not respect the interval timer.

This method makes a plot unconditionally. It does not look at the interval variable, nor is this plot taken into account in the counting done by the update() method if an interval variable was specified.

autoscale(self, mode)

Turns autoscale mode on or off (keywords “on” or “off”), or trigger an immediate autoscaling (keyword “now”).

set_scale(self, scale)

Turns off autoscale, and sets a scale factor manually.

get_scale(self)

Gets the scale factor from the last call to set_scale or the last autoscale event.

set_relative_scale(self, rscale = 1.0)

Sets an additional scale factor applied after the main scale factor. Mainly useful to modify what autoscale does.