wmaee.core.visualize

  1# coding: utf-8
  2# Copyright (c) Max-Planck-Institut für Eisenforschung GmbH - Computational Materials Design (CM) Department
  3# Distributed under the terms of "New BSD License", see the LICENSE file.
  4
  5import warnings
  6import numpy as np
  7from ase.io import Trajectory
  8from matplotlib.colors import rgb2hex
  9from scipy.interpolate import interp1d
 10from wmaee.core.requirements import requires
 11
 12__author__ = "Joerg Neugebauer, Sudarsan Surendralal"
 13__copyright__ = (
 14    "Copyright 2021, Max-Planck-Institut für Eisenforschung GmbH - "
 15    "Computational Materials Design (CM) Department"
 16)
 17__version__ = "1.0"
 18__maintainer__ = "Sudarsan Surendralal"
 19__email__ = "surendralal@mpie.de"
 20__status__ = "production"
 21__date__ = "Sep 1, 2017"
 22
 23
 24@requires("nglview")
 25def plot3d(
 26        structure,
 27        mode="NGLview",
 28        show_cell=True,
 29        show_axes=True,
 30        camera="orthographic",
 31        spacefill=True,
 32        particle_size=1.0,
 33        select_atoms=None,
 34        background="white",
 35        color_scheme=None,
 36        colors=None,
 37        scalar_field=None,
 38        scalar_start=None,
 39        scalar_end=None,
 40        scalar_cmap=None,
 41        vector_field=None,
 42        vector_color=None,
 43        magnetic_moments=False,
 44        view_plane=np.array([0, 0, 1]),
 45        distance_from_camera=1.0,
 46        opacity=1.0,
 47):
 48    """
 49    Plot3d relies on NGLView or plotly to visualize atomic structures. Here, we construct a string in the "protein database"
 50
 51    The final widget is returned. If it is assigned to a variable, the visualization is suppressed until that
 52    variable is evaluated, and in the meantime more NGL operations can be applied to it to modify the visualization.
 53
 54    Args:
 55        mode (str): `NGLView`, `plotly` or `ase`
 56        show_cell (bool): Whether or not to show the frame. (Default is True.)
 57        show_axes (bool): Whether or not to show xyz axes. (Default is True.)
 58        camera (str): 'perspective' or 'orthographic'. (Default is 'perspective'.)
 59        spacefill (bool): Whether to use a space-filling or ball-and-stick representation. (Default is True, use
 60            space-filling atoms.)
 61        particle_size (float): Size of the particles. (Default is 1.)
 62        select_atoms (numpy.ndarray): Indices of atoms to show, either as integers or a boolean array mask.
 63            (Default is None, show all atoms.)
 64        background (str): Background color. (Default is 'white'.)
 65        color_scheme (str): NGLView color scheme to use. (Default is None, color by element.)
 66        colors (numpy.ndarray): A per-atom array of HTML color names or hex color codes to use for atomic colors.
 67            (Default is None, use coloring scheme.)
 68        scalar_field (numpy.ndarray): Color each atom according to the array value (Default is None, use coloring
 69            scheme.)
 70        scalar_start (float): The scalar value to be mapped onto the low end of the color map (lower values are
 71            clipped). (Default is None, use the minimum value in `scalar_field`.)
 72        scalar_end (float): The scalar value to be mapped onto the high end of the color map (higher values are
 73            clipped). (Default is None, use the maximum value in `scalar_field`.)
 74        scalar_cmap (matplotlib.cm): The colormap to use. (Default is None, giving a blue-red divergent map.)
 75        vector_field (numpy.ndarray): Add vectors (3 values) originating at each atom. (Default is None, no
 76            vectors.)
 77        vector_color (numpy.ndarray): Colors for the vectors (only available with vector_field). (Default is None,
 78            vectors are colored by their direction.)
 79        magnetic_moments (bool): Plot magnetic moments as 'scalar_field' or 'vector_field'.
 80        view_plane (numpy.ndarray): A Nx3-array (N = 1,2,3); the first 3d-component of the array specifies
 81            which plane of the system to view (for example, [1, 0, 0], [1, 1, 0] or the [1, 1, 1] planes), the
 82            second 3d-component (if specified, otherwise [1, 0, 0]) gives the horizontal direction, and the third
 83            component (if specified) is the vertical component, which is ignored and calculated internally. The
 84            orthonormality of the orientation is internally ensured, and therefore is not required in the function
 85            call. (Default is np.array([0, 0, 1]), which is view normal to the x-y plane.)
 86        distance_from_camera (float): Distance of the camera from the structure. Higher = farther away.
 87            (Default is 14, which also seems to be the NGLView default value.)
 88
 89        Possible NGLView color schemes:
 90          " ", "picking", "random", "uniform", "atomindex", "residueindex",
 91          "chainindex", "modelindex", "sstruc", "element", "resname", "bfactor",
 92          "hydrophobicity", "value", "volume", "occupancy"
 93
 94    Returns:
 95        (nglview.NGLWidget): The NGLView widget itself, which can be operated on further or viewed as-is.
 96
 97    Warnings:
 98        * Many features only work with space-filling atoms (e.g. coloring by a scalar field).
 99        * The colour interpretation of some hex codes is weird, e.g. 'green'.
100    """
101    if mode == "NGLview":
102        return _plot3d(
103            structure=structure,
104            show_cell=show_cell,
105            show_axes=show_axes,
106            camera=camera,
107            spacefill=spacefill,
108            particle_size=particle_size,
109            select_atoms=select_atoms,
110            background=background,
111            color_scheme=color_scheme,
112            colors=colors,
113            scalar_field=scalar_field,
114            scalar_start=scalar_start,
115            scalar_end=scalar_end,
116            scalar_cmap=scalar_cmap,
117            vector_field=vector_field,
118            vector_color=vector_color,
119            magnetic_moments=magnetic_moments,
120            view_plane=view_plane,
121            distance_from_camera=distance_from_camera,
122        )
123    elif mode == "ase":
124        return _plot3d_ase(
125            structure=structure,
126            show_cell=show_cell,
127            show_axes=show_axes,
128            camera=camera,
129            spacefill=spacefill,
130            particle_size=particle_size,
131            background=background,
132            color_scheme=color_scheme,
133        )
134    else:
135        raise ValueError("plot method not recognized")
136
137
138def _plot3d(
139        structure,
140        show_cell=True,
141        show_axes=True,
142        camera="orthographic",
143        spacefill=True,
144        particle_size=1.0,
145        select_atoms=None,
146        background="white",
147        color_scheme=None,
148        colors=None,
149        scalar_field=None,
150        scalar_start=None,
151        scalar_end=None,
152        scalar_cmap=None,
153        vector_field=None,
154        vector_color=None,
155        magnetic_moments=False,
156        view_plane=np.array([0, 0, 1]),
157        distance_from_camera=1.0,
158):
159    """
160    Plot3d relies on NGLView to visualize atomic structures. Here, we construct a string in the "protein database"
161    ("pdb") format, then turn it into an NGLView "structure". PDB is a white-space sensitive format, so the
162    string snippets are carefully formatted.
163
164    The final widget is returned. If it is assigned to a variable, the visualization is suppressed until that
165    variable is evaluated, and in the meantime more NGL operations can be applied to it to modify the visualization.
166
167    Args:
168        show_cell (bool): Whether or not to show the frame. (Default is True.)
169        show_axes (bool): Whether or not to show xyz axes. (Default is True.)
170        camera (str): 'perspective' or 'orthographic'. (Default is 'perspective'.)
171        spacefill (bool): Whether to use a space-filling or ball-and-stick representation. (Default is True, use
172            space-filling atoms.)
173        particle_size (float): Size of the particles. (Default is 1.)
174        select_atoms (numpy.ndarray): Indices of atoms to show, either as integers or a boolean array mask.
175            (Default is None, show all atoms.)
176        background (str): Background color. (Default is 'white'.)
177        color_scheme (str): NGLView color scheme to use. (Default is None, color by element.)
178        colors (numpy.ndarray): A per-atom array of HTML color names or hex color codes to use for atomic colors.
179            (Default is None, use coloring scheme.)
180        scalar_field (numpy.ndarray): Color each atom according to the array value (Default is None, use coloring
181            scheme.)
182        scalar_start (float): The scalar value to be mapped onto the low end of the color map (lower values are
183            clipped). (Default is None, use the minimum value in `scalar_field`.)
184        scalar_end (float): The scalar value to be mapped onto the high end of the color map (higher values are
185            clipped). (Default is None, use the maximum value in `scalar_field`.)
186        scalar_cmap (matplotlib.cm): The colormap to use. (Default is None, giving a blue-red divergent map.)
187        vector_field (numpy.ndarray): Add vectors (3 values) originating at each atom. (Default is None, no
188            vectors.)
189        vector_color (numpy.ndarray): Colors for the vectors (only available with vector_field). (Default is None,
190            vectors are colored by their direction.)
191        magnetic_moments (bool): Plot magnetic moments as 'scalar_field' or 'vector_field'.
192        view_plane (numpy.ndarray): A Nx3-array (N = 1,2,3); the first 3d-component of the array specifies
193            which plane of the system to view (for example, [1, 0, 0], [1, 1, 0] or the [1, 1, 1] planes), the
194            second 3d-component (if specified, otherwise [1, 0, 0]) gives the horizontal direction, and the third
195            component (if specified) is the vertical component, which is ignored and calculated internally. The
196            orthonormality of the orientation is internally ensured, and therefore is not required in the function
197            call. (Default is np.array([0, 0, 1]), which is view normal to the x-y plane.)
198        distance_from_camera (float): Distance of the camera from the structure. Higher = farther away.
199            (Default is 14, which also seems to be the NGLView default value.)
200
201        Possible NGLView color schemes:
202          " ", "picking", "random", "uniform", "atomindex", "residueindex",
203          "chainindex", "modelindex", "sstruc", "element", "resname", "bfactor",
204          "hydrophobicity", "value", "volume", "occupancy"
205
206    Returns:
207        (nglview.NGLWidget): The NGLView widget itself, which can be operated on further or viewed as-is.
208
209    Warnings:
210        * Many features only work with space-filling atoms (e.g. coloring by a scalar field).
211        * The colour interpretation of some hex codes is weird, e.g. 'green'.
212    """
213    try:  # If the graphical packages are not available, the GUI will not work.
214        import nglview
215    except ImportError:
216        raise ImportError(
217            "The package nglview needs to be installed for the plot3d() function!"
218        )
219
220    if (
221            magnetic_moments is True
222            and np.sum(np.abs(structure.get_initial_magnetic_moments())) > 0
223    ):
224        if len(structure.get_initial_magnetic_moments().shape) == 1:
225            scalar_field = structure.get_initial_magnetic_moments()
226        else:
227            vector_field = structure.get_initial_magnetic_moments()
228
229    elements = structure.get_chemical_symbols()
230    atomic_numbers = structure.numbers
231    positions = structure.positions
232
233    # If `select_atoms` was given, visualize only a subset of the `parent_basis`
234    if select_atoms is not None:
235        select_atoms = np.array(select_atoms, dtype=int)
236        elements = elements[select_atoms]
237        atomic_numbers = atomic_numbers[select_atoms]
238        positions = positions[select_atoms]
239        if colors is not None:
240            colors = np.array(colors)
241            colors = colors[select_atoms]
242        if scalar_field is not None:
243            scalar_field = np.array(scalar_field)
244            scalar_field = scalar_field[select_atoms]
245        if vector_field is not None:
246            vector_field = np.array(vector_field)
247            vector_field = vector_field[select_atoms]
248        if vector_color is not None:
249            vector_color = np.array(vector_color)
250            vector_color = vector_color[select_atoms]
251
252    # Write the nglview protein-database-formatted string
253    struct = nglview.TextStructure(
254        _ngl_write_structure(elements, positions, structure.cell)
255    )
256
257    # Parse the string into the displayable widget
258    view = nglview.NGLWidget(struct)
259
260    if spacefill:
261        # Color by scheme
262        if color_scheme is not None:
263            if colors is not None:
264                warnings.warn("`color_scheme` is overriding `colors`")
265            if scalar_field is not None:
266                warnings.warn("`color_scheme` is overriding `scalar_field`")
267            view = _add_colorscheme_spacefill(
268                view, elements, atomic_numbers, particle_size, color_scheme
269            )
270        # Color by per-atom colors
271        elif colors is not None:
272            if scalar_field is not None:
273                warnings.warn("`colors` is overriding `scalar_field`")
274            view = _add_custom_color_spacefill(
275                view, atomic_numbers, particle_size, colors
276            )
277        # Color by per-atom scalars
278        elif scalar_field is not None:  # Color by per-atom scalars
279            colors = _scalars_to_hex_colors(
280                scalar_field, scalar_start, scalar_end, scalar_cmap
281            )
282            view = _add_custom_color_spacefill(
283                view, atomic_numbers, particle_size, colors
284            )
285        # Color by element
286        else:
287            view = _add_colorscheme_spacefill(
288                view, elements, atomic_numbers, particle_size
289            )
290        view.remove_ball_and_stick()
291    else:
292        view.add_ball_and_stick()
293
294    if show_cell:
295        if structure.cell is not None:
296            if all(np.max(structure.cell, axis=0) > 1e-2):
297                view.add_unitcell()
298
299    if vector_color is None and vector_field is not None:
300        vector_color = (
301                0.5
302                * np.array(vector_field)
303                / np.linalg.norm(vector_field, axis=-1)[:, np.newaxis]
304                + 0.5
305        )
306    elif (
307            vector_field is not None and vector_field is not None
308    ):  # WARNING: There must be a bug here...
309        try:
310            if vector_color.shape != np.ones((len(structure), 3)).shape:
311                vector_color = np.outer(
312                    np.ones(len(structure)),
313                    vector_color / np.linalg.norm(vector_color),
314                )
315        except AttributeError:
316            vector_color = np.ones((len(structure), 3)) * vector_color
317
318    if vector_field is not None:
319        for arr, pos, col in zip(vector_field, positions, vector_color):
320            view.shape.add_arrow(list(pos), list(pos + arr), list(col), 0.2)
321
322    if show_axes:  # Add axes
323        axes_origin = -np.ones(3)
324        arrow_radius = 0.1
325        text_size = 1
326        text_color = [0, 0, 0]
327        arrow_names = ["x", "y", "z"]
328
329        for n in [0, 1, 2]:
330            start = list(axes_origin)
331            shift = np.zeros(3)
332            shift[n] = 1
333            end = list(start + shift)
334            color = list(shift)
335            # We cast as list to avoid JSON warnings
336            view.shape.add_arrow(start, end, color, arrow_radius)
337            view.shape.add_text(end, text_color, text_size, arrow_names[n])
338
339    if camera != "perspective" and camera != "orthographic":
340        warnings.warn(
341            "Only perspective or orthographic is (likely to be) permitted for camera"
342        )
343
344    view.camera = camera
345    view.background = background
346
347    orientation = _get_flattened_orientation(
348        view_plane=view_plane, distance_from_camera=distance_from_camera * 14
349    )
350    view.control.orient(orientation)
351
352    return view
353
354
355def _plot3d_ase(
356        structure,
357        spacefill=True,
358        show_cell=True,
359        camera="perspective",
360        particle_size=0.5,
361        background="white",
362        color_scheme="element",
363        show_axes=True,
364):
365    """
366    Possible color schemes:
367      " ", "picking", "random", "uniform", "atomindex", "residueindex",
368      "chainindex", "modelindex", "sstruc", "element", "resname", "bfactor",
369      "hydrophobicity", "value", "volume", "occupancy"
370    Returns:
371    """
372    try:  # If the graphical packages are not available, the GUI will not work.
373        import nglview
374    except ImportError:
375        raise ImportError(
376            "The package nglview needs to be installed for the plot3d() function!"
377        )
378    # Always visualize the parent basis
379    view = nglview.show_ase(structure)
380    if spacefill:
381        view.add_spacefill(
382            radius_type="vdw", color_scheme=color_scheme, radius=particle_size
383        )
384        # view.add_spacefill(radius=1.0)
385        view.remove_ball_and_stick()
386    else:
387        view.add_ball_and_stick()
388    if show_cell:
389        if structure.cell is not None:
390            if all(np.max(structure.cell, axis=0) > 1e-2):
391                view.add_unitcell()
392    if show_axes:
393        view.shape.add_arrow([-2, -2, -2], [2, -2, -2], [1, 0, 0], 0.5)
394        view.shape.add_arrow([-2, -2, -2], [-2, 2, -2], [0, 1, 0], 0.5)
395        view.shape.add_arrow([-2, -2, -2], [-2, -2, 2], [0, 0, 1], 0.5)
396    if camera != "perspective" and camera != "orthographic":
397        print("Only perspective or orthographic is permitted")
398        return None
399    view.camera = camera
400    view.background = background
401    return view
402
403
404def _ngl_write_cell(a1, a2, a3, f1=90, f2=90, f3=90):
405    """
406    Writes a PDB-formatted line to represent the simulation cell.
407
408    Args:
409        a1, a2, a3 (float): Lengths of the cell vectors.
410        f1, f2, f3 (float): Angles between the cell vectors (which angles exactly?) (in degrees).
411
412    Returns:
413        (str): The line defining the cell in PDB format.
414    """
415    return "CRYST1 {:8.3f} {:8.3f} {:8.3f} {:6.2f} {:6.2f} {:6.2f} P 1\n".format(
416        a1, a2, a3, f1, f2, f3
417    )
418
419
420def _ngl_write_atom(
421        num,
422        species,
423        x,
424        y,
425        z,
426        group=None,
427        num2=None,
428        occupancy=1.0,
429        temperature_factor=0.0,
430):
431    """
432    Writes a PDB-formatted line to represent an atom.
433
434    Args:
435        num (int): Atomic index.
436        species (str): Elemental species.
437        x, y, z (float): Cartesian coordinates of the atom.
438        group (str): A...group name? (Default is None, repeat elemental species.)
439        num2 (int): An "alternate" index. (Don't ask me...) (Default is None, repeat first number.)
440        occupancy (float): PDB occupancy parameter. (Default is 1.)
441        temperature_factor (float): PDB temperature factor parameter. (Default is 0.
442
443    Returns:
444        (str): The line defining an atom in PDB format
445
446    Warnings:
447        * The [PDB docs](https://www.cgl.ucsf.edu/chimera/docs/UsersGuide/tutorials/pdbintro.html) indicate that
448            the xyz coordinates might need to be in some sort of orthogonal basis. If you have weird behaviour,
449            this might be a good place to investigate.
450    """
451    if group is None:
452        group = species
453    if num2 is None:
454        num2 = num
455    return "ATOM {:>6} {:>4} {:>4} {:>5} {:10.3f} {:7.3f} {:7.3f} {:5.2f} {:5.2f} {:>11} \n".format(
456        num, species, group, num2, x, y, z, occupancy, temperature_factor, species
457    )
458
459
460def _ngl_write_structure(elements, positions, cell):
461    """
462    Turns structure information into a NGLView-readable protein-database-formatted string.
463
464    Args:
465        elements (numpy.ndarray/list): Element symbol for each atom.
466        positions (numpy.ndarray/list): Vector of Cartesian atom positions.
467        cell (numpy.ndarray/list): Simulation cell Bravais matrix.
468
469    Returns:
470        (str): The PDB-formatted representation of the structure.
471    """
472    from ase.geometry import cell_to_cellpar, cellpar_to_cell
473
474    if cell is None or any(np.max(cell, axis=0) < 1e-2):
475        # Define a dummy cell if it doesn't exist (eg. for clusters)
476        max_pos = np.max(positions, axis=0) - np.min(positions, axis=0)
477        max_pos[np.abs(max_pos) < 1e-2] = 10
478        cell = np.eye(3) * max_pos
479    cellpar = cell_to_cellpar(cell)
480    exportedcell = cellpar_to_cell(cellpar)
481    rotation = np.linalg.solve(cell, exportedcell)
482
483    pdb_str = _ngl_write_cell(*cellpar)
484    pdb_str += "MODEL     1\n"
485
486    if rotation is not None:
487        positions = np.array(positions).dot(rotation)
488
489    for i, p in enumerate(positions):
490        pdb_str += _ngl_write_atom(i, elements[i], *p)
491
492    pdb_str += "ENDMDL \n"
493    return pdb_str
494
495
496def _atomic_number_to_radius(atomic_number, shift=0.2, slope=0.1, scale=1.0):
497    """
498    Give the atomic radius for plotting, which scales like the root of the atomic number.
499
500    Args:
501        atomic_number (int/float): The atomic number.
502        shift (float): A constant addition to the radius. (Default is 0.2.)
503        slope (float): A multiplier for the root of the atomic number. (Default is 0.1)
504        scale (float): How much to rescale the whole thing by.
505
506    Returns:
507        (float): The radius. (Not physical, just for visualization!)
508    """
509    return (shift + slope * np.sqrt(atomic_number)) * scale
510
511
512def _add_colorscheme_spacefill(
513        view, elements, atomic_numbers, particle_size, scheme="element"
514):
515    """
516    Set NGLView spacefill parameters according to a color-scheme.
517
518    Args:
519        view (NGLWidget): The widget to work on.
520        elements (numpy.ndarray/list): Elemental symbols.
521        atomic_numbers (numpy.ndarray/list): Integer atomic numbers for determining atomic size.
522        particle_size (float): A scale factor for the atomic size.
523        scheme (str): The scheme to use. (Default is "element".)
524
525        Possible NGLView color schemes:
526          " ", "picking", "random", "uniform", "atomindex", "residueindex",
527          "chainindex", "modelindex", "sstruc", "element", "resname", "bfactor",
528          "hydrophobicity", "value", "volume", "occupancy"
529
530    Returns:
531        (nglview.NGLWidget): The modified widget.
532    """
533    for elem, num in set(list(zip(elements, atomic_numbers))):
534        view.add_spacefill(
535            selection="#" + elem,
536            radius_type="vdw",
537            radius=_atomic_number_to_radius(num, scale=particle_size),
538            color_scheme=scheme,
539        )
540    return view
541
542
543def _add_custom_color_spacefill(view, atomic_numbers, particle_size, colors):
544    """
545    Set NGLView spacefill parameters according to per-atom colors.
546
547    Args:
548        view (NGLWidget): The widget to work on.
549        atomic_numbers (numpy.ndarray/list): Integer atomic numbers for determining atomic size.
550        particle_size (float): A scale factor for the atomic size.
551        colors (numpy.ndarray/list): A per-atom list of HTML or hex color codes.
552
553    Returns:
554        (nglview.NGLWidget): The modified widget.
555    """
556    for n, num in enumerate(atomic_numbers):
557        view.add_spacefill(
558            selection=[n],
559            radius_type="vdw",
560            radius=_atomic_number_to_radius(num, scale=particle_size),
561            color=colors[n],
562        )
563    return view
564
565
566def _scalars_to_hex_colors(scalar_field, start=None, end=None, cmap=None):
567    """
568    Convert scalar values to hex codes using a colormap.
569
570    Args:
571        scalar_field (numpy.ndarray/list): Scalars to convert.
572        start (float): Scalar value to map to the bottom of the colormap (values below are clipped). (Default is
573            None, use the minimal scalar value.)
574        end (float): Scalar value to map to the top of the colormap (values above are clipped).  (Default is
575            None, use the maximal scalar value.)
576        cmap (matplotlib.cm): The colormap to use. (Default is None, which gives a blue-red divergent map.)
577
578    Returns:
579        (list): The corresponding hex codes for each scalar value passed in.
580    """
581    if start is None:
582        start = np.amin(scalar_field)
583    if end is None:
584        end = np.amax(scalar_field)
585    interp = interp1d([start, end], [0, 1])
586    remapped_field = interp(np.clip(scalar_field, start, end))  # Map field onto [0,1]
587
588    if cmap is None:
589        try:
590            from seaborn import diverging_palette
591        except ImportError:
592            print(
593                "The package seaborn needs to be installed for the plot3d() function!"
594            )
595        cmap = diverging_palette(245, 15, as_cmap=True)  # A nice blue-red palette
596
597    return [
598        rgb2hex(cmap(scalar)[:3]) for scalar in remapped_field
599    ]  # The slice gets RGB but leaves alpha
600
601
602def _get_orientation(view_plane):
603    """
604    A helper method to plot3d, which generates a rotation matrix from the input `view_plane`, and returns a
605    flattened list of len = 16. This flattened list becomes the input argument to `view.control.orient`.
606
607    Args:
608        view_plane (numpy.ndarray/list): A Nx3-array/list (N = 1,2,3); the first 3d-component of the array
609            specifies which plane of the system to view (for example, [1, 0, 0], [1, 1, 0] or the [1, 1, 1] planes),
610            the second 3d-component (if specified, otherwise [1, 0, 0]) gives the horizontal direction, and the
611            third component (if specified) is the vertical component, which is ignored and calculated internally.
612            The orthonormality of the orientation is internally ensured, and therefore is not required in the
613            function call.
614
615    Returns:
616        (list): orientation tensor
617    """
618    if len(np.array(view_plane).flatten()) % 3 != 0:
619        raise ValueError(
620            "The shape of view plane should be (N, 3), where N = 1, 2 or 3. Refer docs for more info."
621        )
622    view_plane = np.array(view_plane).reshape(-1, 3)
623    rotation_matrix = np.roll(np.eye(3), -1, axis=0)
624    rotation_matrix[: len(view_plane)] = view_plane
625    rotation_matrix /= np.linalg.norm(rotation_matrix, axis=-1)[:, np.newaxis]
626    rotation_matrix[1] -= (
627            np.dot(rotation_matrix[0], rotation_matrix[1]) * rotation_matrix[0]
628    )  # Gran-Schmidt
629    rotation_matrix[2] = np.cross(
630        rotation_matrix[0], rotation_matrix[1]
631    )  # Specify third axis
632    if np.isclose(np.linalg.det(rotation_matrix), 0):
633        return np.eye(
634            3
635        )  # view_plane = [0,0,1] is the default view of NGLview, so we do not modify it
636    return np.roll(
637        rotation_matrix / np.linalg.norm(rotation_matrix, axis=-1)[:, np.newaxis],
638        2,
639        axis=0,
640    ).T
641
642
643def _get_flattened_orientation(view_plane, distance_from_camera):
644    """
645    A helper method to plot3d, which generates a rotation matrix from the input `view_plane`, and returns a
646    flattened list of len = 16. This flattened list becomes the input argument to `view.contol.orient`.
647
648    Args:
649        view_plane (numpy.ndarray/list): A Nx3-array/list (N = 1,2,3); the first 3d-component of the array
650            specifies which plane of the system to view (for example, [1, 0, 0], [1, 1, 0] or the [1, 1, 1] planes),
651            the second 3d-component (if specified, otherwise [1, 0, 0]) gives the horizontal direction, and the
652            third component (if specified) is the vertical component, which is ignored and calculated internally.
653            The orthonormality of the orientation is internally ensured, and therefore is not required in the
654            function call.
655        distance_from_camera (float): Distance of the camera from the structure. Higher = farther away.
656
657    Returns:
658        (list): Flattened list of len = 16, which is the input argument to `view.contol.orient`
659    """
660    if distance_from_camera <= 0:
661        raise ValueError("´distance_from_camera´ must be a positive float!")
662    flattened_orientation = np.eye(4)
663    flattened_orientation[:3, :3] = _get_orientation(view_plane)
664
665    return (distance_from_camera * flattened_orientation).ravel().tolist()
666
667
668@requires("nglview")
669def animate_trajectory(
670        trajectory: Trajectory,
671        spacefill: bool = True,
672        show_cell: bool = True,
673        center_of_mass: bool = False,
674        particle_size: float = 0.5,
675        camera: str = "orthographic",
676):
677    """
678    Animate a series of atomic structures.
679
680    :param trajectory: the trajectory to animate
681    :type trajectory: ase.io.Trajectory
682    :param spacefill: If True, then atoms are visualized in spacefill stype
683    :type spacefill: bool
684    :param show_cell: True if the cell boundaries of the structure is to be shown
685    :type show_cell: bool
686    :param particle_size: Scaling factor for the spheres representing the atoms. (The radius is determined by
687        the atomic number)
688    :type particle_size: float
689    :param center_of_mass: False (default) if the specified positions are w.r.t. the origin
690    :type center_of_mass: bool
691    :param camera: camera perspective, choose from "orthographic" or "perspective" (default is "orthographic")
692    :type camera: str
693    :return: nglview IPython widget
694    :rtype: nglview.NGLWidget
695    """
696
697    import nglview
698    animation = nglview.show_asetraj(trajectory)
699    if spacefill:
700        animation.add_spacefill(radius_type="vdw", scale=0.5, radius=particle_size)
701        animation.remove_ball_and_stick()
702    else:
703        animation.add_ball_and_stick()
704    if show_cell:
705        animation.add_unitcell()
706    animation.camera = camera
707    return animation
@requires('nglview')
def plot3d( structure, mode='NGLview', show_cell=True, show_axes=True, camera='orthographic', spacefill=True, particle_size=1.0, select_atoms=None, background='white', color_scheme=None, colors=None, scalar_field=None, scalar_start=None, scalar_end=None, scalar_cmap=None, vector_field=None, vector_color=None, magnetic_moments=False, view_plane=array([0, 0, 1]), distance_from_camera=1.0, opacity=1.0):
 25@requires("nglview")
 26def plot3d(
 27        structure,
 28        mode="NGLview",
 29        show_cell=True,
 30        show_axes=True,
 31        camera="orthographic",
 32        spacefill=True,
 33        particle_size=1.0,
 34        select_atoms=None,
 35        background="white",
 36        color_scheme=None,
 37        colors=None,
 38        scalar_field=None,
 39        scalar_start=None,
 40        scalar_end=None,
 41        scalar_cmap=None,
 42        vector_field=None,
 43        vector_color=None,
 44        magnetic_moments=False,
 45        view_plane=np.array([0, 0, 1]),
 46        distance_from_camera=1.0,
 47        opacity=1.0,
 48):
 49    """
 50    Plot3d relies on NGLView or plotly to visualize atomic structures. Here, we construct a string in the "protein database"
 51
 52    The final widget is returned. If it is assigned to a variable, the visualization is suppressed until that
 53    variable is evaluated, and in the meantime more NGL operations can be applied to it to modify the visualization.
 54
 55    Args:
 56        mode (str): `NGLView`, `plotly` or `ase`
 57        show_cell (bool): Whether or not to show the frame. (Default is True.)
 58        show_axes (bool): Whether or not to show xyz axes. (Default is True.)
 59        camera (str): 'perspective' or 'orthographic'. (Default is 'perspective'.)
 60        spacefill (bool): Whether to use a space-filling or ball-and-stick representation. (Default is True, use
 61            space-filling atoms.)
 62        particle_size (float): Size of the particles. (Default is 1.)
 63        select_atoms (numpy.ndarray): Indices of atoms to show, either as integers or a boolean array mask.
 64            (Default is None, show all atoms.)
 65        background (str): Background color. (Default is 'white'.)
 66        color_scheme (str): NGLView color scheme to use. (Default is None, color by element.)
 67        colors (numpy.ndarray): A per-atom array of HTML color names or hex color codes to use for atomic colors.
 68            (Default is None, use coloring scheme.)
 69        scalar_field (numpy.ndarray): Color each atom according to the array value (Default is None, use coloring
 70            scheme.)
 71        scalar_start (float): The scalar value to be mapped onto the low end of the color map (lower values are
 72            clipped). (Default is None, use the minimum value in `scalar_field`.)
 73        scalar_end (float): The scalar value to be mapped onto the high end of the color map (higher values are
 74            clipped). (Default is None, use the maximum value in `scalar_field`.)
 75        scalar_cmap (matplotlib.cm): The colormap to use. (Default is None, giving a blue-red divergent map.)
 76        vector_field (numpy.ndarray): Add vectors (3 values) originating at each atom. (Default is None, no
 77            vectors.)
 78        vector_color (numpy.ndarray): Colors for the vectors (only available with vector_field). (Default is None,
 79            vectors are colored by their direction.)
 80        magnetic_moments (bool): Plot magnetic moments as 'scalar_field' or 'vector_field'.
 81        view_plane (numpy.ndarray): A Nx3-array (N = 1,2,3); the first 3d-component of the array specifies
 82            which plane of the system to view (for example, [1, 0, 0], [1, 1, 0] or the [1, 1, 1] planes), the
 83            second 3d-component (if specified, otherwise [1, 0, 0]) gives the horizontal direction, and the third
 84            component (if specified) is the vertical component, which is ignored and calculated internally. The
 85            orthonormality of the orientation is internally ensured, and therefore is not required in the function
 86            call. (Default is np.array([0, 0, 1]), which is view normal to the x-y plane.)
 87        distance_from_camera (float): Distance of the camera from the structure. Higher = farther away.
 88            (Default is 14, which also seems to be the NGLView default value.)
 89
 90        Possible NGLView color schemes:
 91          " ", "picking", "random", "uniform", "atomindex", "residueindex",
 92          "chainindex", "modelindex", "sstruc", "element", "resname", "bfactor",
 93          "hydrophobicity", "value", "volume", "occupancy"
 94
 95    Returns:
 96        (nglview.NGLWidget): The NGLView widget itself, which can be operated on further or viewed as-is.
 97
 98    Warnings:
 99        * Many features only work with space-filling atoms (e.g. coloring by a scalar field).
100        * The colour interpretation of some hex codes is weird, e.g. 'green'.
101    """
102    if mode == "NGLview":
103        return _plot3d(
104            structure=structure,
105            show_cell=show_cell,
106            show_axes=show_axes,
107            camera=camera,
108            spacefill=spacefill,
109            particle_size=particle_size,
110            select_atoms=select_atoms,
111            background=background,
112            color_scheme=color_scheme,
113            colors=colors,
114            scalar_field=scalar_field,
115            scalar_start=scalar_start,
116            scalar_end=scalar_end,
117            scalar_cmap=scalar_cmap,
118            vector_field=vector_field,
119            vector_color=vector_color,
120            magnetic_moments=magnetic_moments,
121            view_plane=view_plane,
122            distance_from_camera=distance_from_camera,
123        )
124    elif mode == "ase":
125        return _plot3d_ase(
126            structure=structure,
127            show_cell=show_cell,
128            show_axes=show_axes,
129            camera=camera,
130            spacefill=spacefill,
131            particle_size=particle_size,
132            background=background,
133            color_scheme=color_scheme,
134        )
135    else:
136        raise ValueError("plot method not recognized")

Plot3d relies on NGLView or plotly to visualize atomic structures. Here, we construct a string in the "protein database"

The final widget is returned. If it is assigned to a variable, the visualization is suppressed until that variable is evaluated, and in the meantime more NGL operations can be applied to it to modify the visualization.

Args: mode (str): NGLView, plotly or ase show_cell (bool): Whether or not to show the frame. (Default is True.) show_axes (bool): Whether or not to show xyz axes. (Default is True.) camera (str): 'perspective' or 'orthographic'. (Default is 'perspective'.) spacefill (bool): Whether to use a space-filling or ball-and-stick representation. (Default is True, use space-filling atoms.) particle_size (float): Size of the particles. (Default is 1.) select_atoms (numpy.ndarray): Indices of atoms to show, either as integers or a boolean array mask. (Default is None, show all atoms.) background (str): Background color. (Default is 'white'.) color_scheme (str): NGLView color scheme to use. (Default is None, color by element.) colors (numpy.ndarray): A per-atom array of HTML color names or hex color codes to use for atomic colors. (Default is None, use coloring scheme.) scalar_field (numpy.ndarray): Color each atom according to the array value (Default is None, use coloring scheme.) scalar_start (float): The scalar value to be mapped onto the low end of the color map (lower values are clipped). (Default is None, use the minimum value in scalar_field.) scalar_end (float): The scalar value to be mapped onto the high end of the color map (higher values are clipped). (Default is None, use the maximum value in scalar_field.) scalar_cmap (matplotlib.cm): The colormap to use. (Default is None, giving a blue-red divergent map.) vector_field (numpy.ndarray): Add vectors (3 values) originating at each atom. (Default is None, no vectors.) vector_color (numpy.ndarray): Colors for the vectors (only available with vector_field). (Default is None, vectors are colored by their direction.) magnetic_moments (bool): Plot magnetic moments as 'scalar_field' or 'vector_field'. view_plane (numpy.ndarray): A Nx3-array (N = 1,2,3); the first 3d-component of the array specifies which plane of the system to view (for example, [1, 0, 0], [1, 1, 0] or the [1, 1, 1] planes), the second 3d-component (if specified, otherwise [1, 0, 0]) gives the horizontal direction, and the third component (if specified) is the vertical component, which is ignored and calculated internally. The orthonormality of the orientation is internally ensured, and therefore is not required in the function call. (Default is np.array([0, 0, 1]), which is view normal to the x-y plane.) distance_from_camera (float): Distance of the camera from the structure. Higher = farther away. (Default is 14, which also seems to be the NGLView default value.)

Possible NGLView color schemes:
  " ", "picking", "random", "uniform", "atomindex", "residueindex",
  "chainindex", "modelindex", "sstruc", "element", "resname", "bfactor",
  "hydrophobicity", "value", "volume", "occupancy"

Returns: (nglview.NGLWidget): The NGLView widget itself, which can be operated on further or viewed as-is.

Warnings: * Many features only work with space-filling atoms (e.g. coloring by a scalar field). * The colour interpretation of some hex codes is weird, e.g. 'green'.

@requires('nglview')
def animate_trajectory( trajectory: <function Trajectory>, spacefill: bool = True, show_cell: bool = True, center_of_mass: bool = False, particle_size: float = 0.5, camera: str = 'orthographic'):
669@requires("nglview")
670def animate_trajectory(
671        trajectory: Trajectory,
672        spacefill: bool = True,
673        show_cell: bool = True,
674        center_of_mass: bool = False,
675        particle_size: float = 0.5,
676        camera: str = "orthographic",
677):
678    """
679    Animate a series of atomic structures.
680
681    :param trajectory: the trajectory to animate
682    :type trajectory: ase.io.Trajectory
683    :param spacefill: If True, then atoms are visualized in spacefill stype
684    :type spacefill: bool
685    :param show_cell: True if the cell boundaries of the structure is to be shown
686    :type show_cell: bool
687    :param particle_size: Scaling factor for the spheres representing the atoms. (The radius is determined by
688        the atomic number)
689    :type particle_size: float
690    :param center_of_mass: False (default) if the specified positions are w.r.t. the origin
691    :type center_of_mass: bool
692    :param camera: camera perspective, choose from "orthographic" or "perspective" (default is "orthographic")
693    :type camera: str
694    :return: nglview IPython widget
695    :rtype: nglview.NGLWidget
696    """
697
698    import nglview
699    animation = nglview.show_asetraj(trajectory)
700    if spacefill:
701        animation.add_spacefill(radius_type="vdw", scale=0.5, radius=particle_size)
702        animation.remove_ball_and_stick()
703    else:
704        animation.add_ball_and_stick()
705    if show_cell:
706        animation.add_unitcell()
707    animation.camera = camera
708    return animation

Animate a series of atomic structures.

Parameters
  • trajectory: the trajectory to animate
  • spacefill: If True, then atoms are visualized in spacefill stype
  • show_cell: True if the cell boundaries of the structure is to be shown
  • particle_size: Scaling factor for the spheres representing the atoms. (The radius is determined by the atomic number)
  • center_of_mass: False (default) if the specified positions are w.r.t. the origin
  • camera: camera perspective, choose from "orthographic" or "perspective" (default is "orthographic")
Returns

nglview IPython widget