Source code for moldesign.widgets.symmetry

# Copyright 2016 Autodesk Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import collections

import ipywidgets as ipy
import numpy as np

import moldesign as mdt
from moldesign import units as u


def exports(o):
    __all__.append(o.__name__)
    return o
__all__ = []


@exports
[docs]class Symmetrizer(ipy.Box): def __init__(self, mol): self._current_shapes = [] self.mol = mol self.tolerance = 0.3 * u.angstrom self.original_coords = mol.positions.copy() self.showing = ipy.HTML() self.viewer = mol.draw3d() """:type viewer: moldesign.viewer.GeometryViewer""" self.description = ipy.HTML() self.symm_selector = ipy.Select() self.symm_selector.observe(self.show_symmetry, names='value') self.apply_button = ipy.Button(description='Symmetrize') self.apply_button.on_click(self.apply_selected_symmetry) self.reset_button = ipy.Button(description='Reset') self.reset_button.on_click(self.reset_coords) self.apply_all_button = ipy.Button(description='Apply all', padding=10) self.apply_all_button.on_click(self.set_highest_symmetry) self.tolerance_descrip = ipy.HTML(u'<small>tolerance/\u212B</small>',) self.tolerance_chooser = ipy.BoundedFloatText(value=self.tolerance.value_in(u.angstrom), min=0.0) self.recalculate_button = ipy.Button(description='Recalculate') self.recalculate_button.on_click(self.coords_changed) self.symm_pane = ipy.VBox([self.description, self.symm_selector, ipy.HBox([self.apply_button, self.reset_button]), self.apply_all_button, ipy.HBox([self.tolerance_chooser, self.recalculate_button]), self.tolerance_descrip], width=325) self.symmetry = None self.coords_changed() self.hbox = ipy.HBox([ipy.VBox([self.viewer, self.showing]), self.symm_pane]) super(Symmetrizer, self).__init__([self.hbox])
[docs] def reset_coords(self, *args): self.mol.positions = self.original_coords self.viewer.append_frame(positions=self.original_coords) self.coords_changed()
[docs] def coords_changed(self, *args): with self.symm_selector.hold_trait_notifications(): self.symm_selector.options = {} self.description.value = 'Finding symmetries ...' self.tolerance = self.tolerance_chooser.value * u.angstrom self.symmetry = mdt.geom.get_symmetry(self.mol, tolerance=self.tolerance) options = collections.OrderedDict() for elem in self.symmetry.elems: if elem.max_diff.magnitude != 0.0: key = '{0}) {1} (error={2.magnitude:.4f} {2.units})'.format(elem.idx, elem.symbol, elem.max_diff) else: key = '{0}) {1} (exact)'.format(elem.idx, elem.symbol, elem.max_diff) options[key] = elem with self.symm_selector.hold_trait_notifications(): self.symm_selector.options = options descrip = 'Highest symmetry group: <b>%s</b><br>' % self.symmetry.symbol if self.symmetry.rms.magnitude == 0.0: descrip += '(Exact)' else: descrip += 'RMS Error = {:.03P}'.format(self.symmetry.rms) self.description.value = descrip self.viewer.append_frame(positions=self.symmetry.orientation) self.viewer.center()
[docs] def apply_selected_symmetry(self, *args): idx = self.symm_selector.value.idx elem = self.symmetry.elems[idx] newcoords = self.symmetry.get_symmetrized_coords(elem) self.mol.atoms.position = newcoords if not np.allclose(newcoords, self.symmetry.orientation, atol=1.0e-10): self.viewer.append_frame(positions=newcoords) self.coords_changed()
[docs] def show_symmetry(self, *args): self.showing.value = '' if self._current_shapes: for s in self._current_shapes: self.viewer.remove(s, render=False) self._current_shapes = [] if self.symm_selector.value is None: self.viewer.render() return elem = self.symm_selector.value symbol = elem.symbol self.showing.value = '%s visualization not implemented' % symbol if symbol == 'C1': self.viewer.render() self.showing.value = 'Identity operation' return elif symbol == 'Ci': inversion = self.viewer.draw_sphere(np.zeros(3) * u.angstrom, color='0x4AB4C4', radius=0.5 * u.angstrom, opacity=0.85, render=False) self._current_shapes.append(inversion) self.showing.value = 'Inversion center' elif symbol == 'Cs' or (symbol[0] == 'S' and symbol[1].isdigit()): axis = elem.get_axis() rad = 2.5 * max(self.symmetry.orientation.max(), 3.0 * u.angstrom) plane = self.viewer.draw_circle(np.zeros(3), axis, radius=rad, opacity=0.6, color='0xAB00FE', render=False) self._current_shapes.append(plane) self.showing.value = 'Mirror plane (normal = %s)' % axis if symbol[0] in 'SC' and symbol[1].isdigit(): axis = elem.get_axis() nrot = int(symbol[1]) projections = self.symmetry.orientation.dot(axis) top = axis * max(3.25 * projections.max(), 3.0*u.angstrom) bottom = axis * min(2.5 * projections.min(), -2.5*u.angstrom) arrow = self.viewer.draw_arrow(start=bottom, end=top, color='0x00FE03', render=False, opacity=0.8) self._current_shapes.append(arrow) if symbol[0] == 'S': self.showing.value = '%d-fold improper rotation axis (%s)' % (nrot, axis) else: self.showing.value = '%d-fold rotation axis (%s)' % (nrot, axis) self.viewer.render()
[docs] def set_highest_symmetry(self, *args): raise NotImplementedError()