Visualization
atomli.visualize.view displays structures and trajectories in interactive 3D.
from atomli.build import bulkfrom atomli.visualize import view
view(bulk("NaCl", "rocksalt", a=5.64, cubic=True), repeat=(2, 2, 2), cell=True)Drag to rotate. Scroll to zoom.
Trajectories
Section titled “Trajectories”Pass a sequence of Atoms and the viewer becomes a player with a timeline. A
list from read, the frames an optimizer collected, or an
MD trajectory all work.
from atomli.io import readfrom atomli.visualize import view
view(read("relaxation.extxyz", ":"))That run starts at 20.1 eV/Å of maximum
force and takes 23 steps to reach
0.026 eV/Å. Scrub it and the ring flattens and
regularizes: the six C-C bonds end within
0.8 mÅ of each other at
1.385 Å, and no carbon sits more than
0.002 Å off the ring’s best-fit plane.
Watching the geometry is how you notice a relaxation that converged to
something you did not want, which a falling fmax column will not tell you.
Notebook display
Section titled “Notebook display”In a notebook, atoms on the last line of a cell shows the same viewer. There
is nothing to import.
from atomli.build import molecule
atoms = molecule("C6H6")atoms # renders 3DThis is the one place atomli deviates from ASE’s behaviour, and it is confined
to _repr_mimebundle_, the hook only IPython calls. repr(atoms) and
print(atoms) return plain text exactly as they did before, so doctests,
logging and terminal scripts are untouched.
Live visualization
Section titled “Live visualization”view returns a Viewer. Its update method pushes the current geometry into
the figure that is already on screen, and it takes no required argument, so it
attaches directly to any driver.
from atomli.optimize import BFGSfrom atomli.visualize import view
v = view(atoms)opt = BFGS(atoms)opt.attach(v.update, interval=1)opt.run(fmax=0.05)The cell animates while the optimizer runs rather than after it finishes. Frames are appended to the trajectory in place, not re-rendered, so the camera angle and the reader’s position on the scrub bar survive every step. The same works for MD:
dyn = VelocityVerlet(atoms, timestep=1.0 * fs)dyn.attach(v.update, interval=5)dyn.run(500)Three things worth knowing before you rely on it:
- Only positions are streamed. The species, the count and the cell come from
the structure
viewwas given, so a run that adds, removes or retypes atoms needs a freshviewcall. update()with no argument readsv.atoms, which is the object you passed in. Drivers mutate that object in place, which is why this works. If your loop builds a newAtomseach step, pass it:v.update(new_atoms).- Outside IPython,
updatedoes nothing at all. The same script runs unchanged from a terminal, with no viewer and no error.
Image export
Section titled “Image export”v = view(trajectory)v.save("figure.png") # 400 dpi by defaultv.save("figure.png", dpi=600)v.save("run.gif", fps=20, every=2)The extension chooses the format, and only .png and .gif are written;
anything else raises. A .gif needs more than one frame, so asking for one
from a single structure raises rather than writing a one-frame animation.
Be clear about where the file goes. The picture is rendered by the renderer,
which lives in the browser, because a Python kernel has no WebGL context. So
save drives the browser’s download flow: the file lands in the browser’s
download directory, not next to the notebook, and nothing at all is written
when there is no live viewer, which includes a headless nbconvert run or a
reloaded notebook whose kernel is gone. If you need a file on the machine
running the kernel, write the structure with
atomli.io.write and render it there.
dpi is the capture resolution and pixel count grows with its square, so a
long trajectory at 400 dpi can exceed the GIF encoder’s budget. Lower dpi or
raise every when it says so.
Appearance
Section titled “Appearance”Look options are keyword arguments, passed to the renderer unchanged.
view(atoms, style="vdw", colorScheme="jmol", background="black", atomScale=60)atomScale and bondScale are percentages, not multipliers: 100 is the
default size, so the call above draws atoms at 60% of it.
| Option | Does |
|---|---|
style | bTube (default), flat, skeletal, vdw, bubble |
colorScheme | vesta-soft, jmol, kessoku |
material | modern-matte, classic-matte, glossy, metallic, 2-5d, 2d |
background | light, white, black |
atomScale, bondScale | Radii, in percent of the default |
bondColorMode | bicolor splits a bond at its midpoint, unicolor paints it one colour |
bonds, edges, cell, axes, labels, polyhedra | Booleans: draw bonds, atom outlines, the cell box, orientation axes, element labels, coordination polyhedra |
autoRotate | Spin the scene continuously |
controls | Which of the options above the reader may adjust in the figure: True for all, a comma-separated string, or a list |
title, width | Caption, and width as a number of pixels or a CSS length |
A name that is not on that list raises a TypeError naming the valid options.
That is deliberate: the widget only logs a console warning for an option it
does not recognize, and a notebook user never sees the console, so a typo would
otherwise be silently ignored.
Two more arguments shape the frame rather than the scene. height takes a bare
number as pixels or any CSS length verbatim, and repeat builds a supercell for
display without touching the structure you passed in.
view(atoms, height=600)view(atoms, height="70vh")view(crystal, repeat=(3, 3, 3)) # `crystal` itself is unchangedASE compatibility
Section titled “ASE compatibility”The signature is ASE’s, in ASE’s order:
view(atoms, data=None, viewer=None, repeat=None, block=False, height=None, **look)so an unmodified ASE call such as view(atoms, None, "ase", (2, 2, 2)) binds
positionally the same way here. data and block are accepted and ignored:
ASE uses them to drive an external GUI window, and there is no external GUI
here. viewer accepts None, "ase" and "tako", all selecting the built-in
viewer. Any other backend name raises instead of quietly showing something you
did not ask for.
Display troubleshooting
Section titled “Display troubleshooting”The output is never a blank box. Before the mount script runs it shows a line naming the structure (formula, atom count, whether it is periodic, how many frames), and if the renderer fails to load, that line is replaced with the reason. Two common causes:
- The notebook is not trusted. JupyterLab leaves inserted HTML inert until
you run
jupyter trust notebook.ipynb. The same applies to nbviewer and to GitHub’s.ipynbrendering, which never execute the script at all. tako.atom.liis unreachable. The widget is fetched over the network. A self-hosted or air-gapped deployment can point at its own copy by passingscript_urlwhen constructing aViewerdirectly.