def parse_symmetry(session, group, center=None, axis=None, molecule=None): # Handle products of symmetry groups. groups = group.split('*') from chimerax.geometry import Places ops = Places() for g in groups: ops = ops * group_symmetries(session, g, molecule) # Apply center and axis transformation. if center is not None or axis is not None: from chimerax.geometry import Place, vector_rotation, translation tf = Place() if center is not None and tuple(center) != (0, 0, 0): tf = translation([-c for c in center]) if axis is not None and tuple(axis) != (0, 0, 1): tf = vector_rotation(axis, (0, 0, 1)) * tf if not tf.is_identity(): ops = ops.transform_coordinates(tf) return ops
def unit_cell_and_sym_axes(session, unit_cell): cell = unit_cell.cell from chimerax.core.models import Model from chimerax.geometry import Places, Place from collections import defaultdict import numpy from math import sqrt from fractions import Fraction from chimerax.clipper.clipper_python import (Coord_frac, Coord_orth, RTop_frac, Vec3_double as Vec3, Symop, Mat33_double as Mat33) axis_defs = defaultdict(lambda: list()) m = Model('Unit cell and symmetry', session) # box_origin_frac = unit_cell.origin.coord_frac(unit_cell.grid)-Coord_frac([1,1,1]) # box_origin_xyz = box_origin_frac.coord_orth(cell).as_numpy() box_size = unit_cell.grid.dim * 3 syms = unit_cell.symops #syms = Places(place_array = unit_cell.all_symops_in_box(box_origin_xyz, box_size).all_matrices_orth(unit_cell.cell, '3x4')) #syms = Places(place_array = unit_cell.symops.all_matrices_orth(unit_cell.cell, '3x4')) identity = Mat33.identity() # Need to check all operators covering a 3x3 unit cell block to ensure we # get them all. Generate all possible length-3 combinations of -1, 0 and 1 u = v = w = numpy.linspace(-1, 1, 3) frac_offsets = numpy.array(numpy.meshgrid(u, v, w)).T.reshape(-1, 3) frac_offsets = [Vec3(f) for f in frac_offsets] rot44 = numpy.identity(4) for sym_index, s in enumerate(syms): if s.rot.equals(identity, 1e-6): # No rotation, therefore no symmetry axis continue rot = s.rot trn = s.trn ss = Symop(s) so = ss.rtop_orth(cell) pr = Place(axes=so.rot.as_numpy().T) if pr.is_identity(tolerance=1e-5): continue central_axis, angle = pr.rotation_axis_and_angle() central_axis /= numpy.linalg.norm(central_axis) angle = abs(round(angle)) if angle == 0: # Pure translation continue if angle not in (60, 90, 120, 180): err_str = ( 'This transform has a rotation angle of {} degrees, ' 'which is not compatible with crystallographic symmetry. ' 'In real crystals, only rotations yielding 2, 3, 4 or 6-fold ' 'symmetry are possible.').format(angle) raise RuntimeError(err_str) fold_symmetry = 360 // angle ca_frac = Coord_orth(100 * central_axis).coord_frac(cell).unit() #screw_component = Coord_orth(so.screw_translation).coord_frac(cell) # screw_component_orth = numpy.dot(so.trn.as_numpy(), central_axis) * central_axis # screw_component = Coord_orth(screw_component_orth).coord_frac(cell).lattice_copy_zero() #screw_component = Coord_frac((Vec3.dot(ss.trn, ca_frac) * ca_frac)).lattice_copy_zero() #print('Symop: {}, symmetry: {}, fractional_translation: {}, central_axis_orth: {}, central_axis_frac: {}, screw_component_frac: {}'.format( # sym_index, fold_symmetry, ss.trn.as_numpy(), central_axis, ca_frac, screw_component)) # screw_mag = numpy.linalg.norm(screw_component.as_numpy()) # print('Screw magnitude before normalisation: {}'.format(screw_mag)) # # if screw_mag > 1/12: # # print('Non-zero screw translation vector: {} along axis: {} for fractional transform number {}: {}'.format(screw_component, central_axis, sym_index, s)) # screw_mag = numpy.linalg.norm([float(Fraction(s).limit_denominator(12)) for s in screw_component.as_numpy()])*sqrt(3) # LCM of possible fractional screw translations 1/(2,3,4 or 6) # print('Screw magnitude after normalisation: {}'.format(screw_mag)) for offset in frac_offsets: this_trn = trn + offset tf = RTop_frac(rot, this_trn).rtop_orth(unit_cell.cell) trn_orth = tf.trn.as_numpy() screw_orth = numpy.dot(trn_orth, central_axis) * central_axis perp_orth = trn_orth - screw_orth # Remove any whole-unit-cell translation component screw_frac = Coord_orth(screw_orth).coord_frac( cell).lattice_copy_zero().as_numpy() normalised_screw_frac = [ Fraction(s).limit_denominator(12) for s in screw_frac ] normalised_screw_frac_float = numpy.array( [float(s) for s in normalised_screw_frac]) screw_mag = numpy.linalg.norm(normalised_screw_frac_float) rot44[:3, :3] = tf.rot.as_numpy() rot44[:3, 3] = perp_orth origin, slope = rotation_axis(rot44) origin = Coord_orth(origin).coord_frac(cell).as_numpy() slope = Coord_orth(slope * 100).coord_frac(cell).as_numpy() axis_defs[(fold_symmetry, screw_mag)].append([origin, slope]) plane_points, plane_normals = unit_cell_planes(unit_cell, fractional_coords=True, pad=0.001) plane_points = numpy.array([pp[0] for pp in plane_points]) for (fold_symmetry, screw_component), axes in axis_defs.items(): uvw0, uvw1 = plane_intersections_in_unit_cell(axes, plane_points, plane_normals) if uvw0 is None: continue from chimerax.clipper.clipper_python import Coord_frac axyz0 = numpy.array([ Coord_frac(uvw).coord_orth(unit_cell.cell).as_numpy() for uvw in uvw0 ]) axyz1 = numpy.array([ Coord_frac(uvw).coord_orth(unit_cell.cell).as_numpy() for uvw in uvw1 ]) d = sym_axis_drawing(fold_symmetry, screw_component, axyz0, axyz1) if d is not None: m.add_drawing(d) bd = unit_cell_box_drawing(unit_cell_corners(unit_cell)) m.add_drawing(bd) return m