Source code for hikari.symmetry.hall

import re
from typing import List, Match, Union

import numpy as np

from hikari.symmetry.operations import BoundedOperation
from hikari.utility import dict_union


[docs] class HallSymbol: """Parse, interpret, and convert Hall symbol to `hikari.symmetry.Group`"""
[docs] class HallSymbolException(Exception): """Exception raised when group can't be generated from the symbol"""
REGEX = re.compile( r"""(-?)([pabcirstf])_(-?)(\d)([*xyz]?)([12345abcnuvwd]*)""" r"""(?:_(-?)(\d)(['"xyz]?)([12345abcnuvwd]*))?""" r"""(?:_(-?)(\d)\*?([12345abcnuvwd]*))?""" r"""(?:_-?(\d)([12345abcnuvwd]*))?(?:_\((\d)_(\d)_(\d)\))?""") r""" This is a regex which matches every possible lowercase Hall symbol for classical 3D space groups. It matches up to 17 groups in total as follows: 1. Centrosymmetry: `[-]?` 2. Lattice symbol: `[pabcirstf]` (required) 3. Generator 1 inversion component: `[-]?` 4. Generator 1 rotation fold: `\d` (required) 5. Generator 1 rotation direction: `[*xyz]?` 6. Generator 1 translation components: `[12345abcnuvwd]*` 7. Generator 2 inversion component: `[-]?` 8. Generator 2 rotation fold: `\d` 9. Generator 2 rotation direction: `['"xyz]?` 10. Generator 2 translation components: `[12345abcnuvwd]*` 11. Generator 3 inversion component: `[-]?` 12. Generator 3 rotation fold: `\d` 13. Generator 3 translation components: `[12345abcnuvwd]*` 14. Generator 4 rotation fold: `\d` 15. Generator 4 translation components: `[12345abcnuvwd]*` 16. Origin shift constituent 1 expressed as count of 1/12 shifts: `\d` 17. Origin shift constituent 2 expressed as count of 1/12 shifts: `\d` 18. Origin shift constituent 3 expressed as count of 1/12 shifts: `\d` """ DIRECTION_SYMBOLS = ("'", '"', '*') TRANSLATION_SYMBOLS = '12345abcnuvwd' LATTICE_GENERATORS = { 'p': (BoundedOperation(np.eye(3)),), 'a': (BoundedOperation(np.eye(3)), BoundedOperation(np.eye(3), (0, 1 / 2, 1 / 2))), 'b': (BoundedOperation(np.eye(3)), BoundedOperation(np.eye(3), (1 / 2, 0, 1 / 2))), 'c': (BoundedOperation(np.eye(3)), BoundedOperation(np.eye(3), (1 / 2, 1 / 2, 0))), 'i': (BoundedOperation(np.eye(3)), BoundedOperation(np.eye(3), (1 / 2, 1 / 2, 1 / 2))), 'r': (BoundedOperation(np.eye(3)), BoundedOperation(np.eye(3), (2 / 3, 1 / 3, 1 / 3)), BoundedOperation(np.eye(3), (1 / 3, 2 / 3, 2 / 3))), 's': (BoundedOperation(np.eye(3)), BoundedOperation(np.eye(3), (1 / 3, 1 / 3, 2 / 3)), BoundedOperation(np.eye(3), (2 / 3, 2 / 3, 1 / 3))), 't': (BoundedOperation(np.eye(3)), BoundedOperation(np.eye(3), (1 / 3, 2 / 3, 1 / 3)), BoundedOperation(np.eye(3), (2 / 3, 1 / 3, 2 / 3))), 'f': (BoundedOperation(np.eye(3)), BoundedOperation(np.eye(3), (0, 1 / 2, 1 / 2)), BoundedOperation(np.eye(3), (1 / 2, 0, 1 / 2)), BoundedOperation(np.eye(3), (1 / 2, 1 / 2, 0))), } UNIVERSAL_MATRICES = { '1': np.eye(3), } PRINCIPAL_ROTATIONS = { 'x': {'1': np.eye(3), '2': np.diag([1, -1, -1]), '3': np.array([(1, 0, 0), (0, 0, -1), (0, 1, -1)]), '4': np.array([(1, 0, 0), (0, 0, -1), (0, 1, 0)]), '6': np.array([(1, 0, 0), (0, 1, -1), (0, 1, 0)]),}, 'y': {'1': np.eye(3), '2': np.diag([-1, 1, -1]), '3': np.array([(-1, 0, 1), (0, 1, 0), (-1, 0, 0)]), '4': np.array([(0, 0, 1), (0, 1, 0), (-1, 0, 0)]), '6': np.array([(0, 0, 1), (0, 1, 0), (-1, 0, 1)]),}, 'z': {'1': np.eye(3), '2': np.diag([-1, -1, 1]), '3': np.array([(0, -1, 0), (1, -1, 0), (0, 0, 1)]), '4': np.array([(0, -1, 0), (1, 0, 0), (0, 0, 1)]), '6': np.array([(1, -1, 0), (1, 0, 0), (0, 0, 1)]),}, '*': {'3': np.array([(0, 0, 1), (1, 0, 0), (0, 1, 0)]),} } FACE_X_DIAGONAL_ROTATIONS = { "1'": np.eye(3), '2"': np.array([(-1, 0, 0), (0, 0, 1), (0, 1, 0)]), "2'": np.array([(-1, 0, 0), (0, 0, -1), (0, -1, 0)]), } FACE_Y_DIAGONAL_ROTATIONS = { "1'": np.eye(3), '2"': np.array([(0, 0, 1), (0, -1, 0), (1, 0, 0)]), "2'": np.array([(0, 0, -1), (0, -1, 0), (-1, 0, 0)]), } FACE_Z_DIAGONAL_ROTATIONS = { "1'": np.eye(3), '2"': np.array([(0, 1, 0), (1, 0, 0), (0, 0, -1)]), "2'": np.array([(0, -1, 0), (-1, 0, 0), (0, 0, -1)]), } BODY_DIAGONAL_ROTATIONS = { '1': np.eye(3), '3': np.array([(0, 0, 1), (1, 0, 0), (0, 1, 0)]), } STATIC_TRANSLATIONS = { 'a': np.array([1/2, 0, 0]), 'b': np.array([0, 1/2, 0]), 'c': np.array([0, 0, 1/2]), 'n': np.array([1/2, 1/2, 1/2]), 'u': np.array([1/4, 0, 0]), 'v': np.array([0, 1/4, 0]), 'w': np.array([0, 0, 1/4]), 'd': np.array([1 / 4, 1 / 4, 1 / 4]), } DYNAMIC_TRANSLATIONS = { '3x': {'1': np.array([1 / 3, 0, 0]), '2': np.array([2 / 3, 0, 0])}, '3y': {'1': np.array([0, 1 / 3, 0]), '2': np.array([0, 2 / 3, 0])}, '3z': {'1': np.array([0, 0, 1 / 3]), '2': np.array([0, 0, 2 / 3])}, '4x': {'1': np.array([1 / 4, 0, 0]), '3': np.array([3 / 4, 0, 0])}, '4y': {'1': np.array([0, 1 / 4, 0]), '3': np.array([0, 3 / 4, 0])}, '4z': {'1': np.array([0, 0, 1 / 4]), '3': np.array([0, 0, 3 / 4])}, '6x': {'1': np.array([1 / 6, 0, 0]), '2': np.array([1 / 3, 0, 0]), '4': np.array([2 / 3, 0, 0]), '5': np.array([5 / 6, 0, 0])}, '6y': {'1': np.array([0, 1 / 6, 0]), '2': np.array([0, 1 / 3, 0]), '4': np.array([0, 2 / 3, 0]), '5': np.array([0, 5 / 6, 0])}, '6z': {'1': np.array([0, 0, 1 / 6]), '2': np.array([0, 0, 1 / 3]), '4': np.array([0, 0, 2 / 3]), '5': np.array([0, 0, 5 / 6])}, } def __init__(self, hall_symbol: str) -> None: self.symbol = hall_symbol @property def elements(self) -> Union[None, Match]: """Return `self.symbol` elements indexed as in `self.HALL_REGEX` doc""" return self.REGEX.match(self.symbol) @property def symbol(self) -> str: return self._symbol @symbol.setter def symbol(self, symbol: str) -> None: self._symbol = symbol.lower().replace(' ', '_') @property def generators(self) -> List[BoundedOperation]: elements: re.Match = self.elements # lattice / centering generators generators: List[BoundedOperation] = list(self.LATTICE_GENERATORS[elements[2]]) if elements[1] == '-': generators.append(BoundedOperation(-np.eye(3))) # generator 1 gen1_inv, gen1_fold, gen1_dir, gen1_tl = elements.group(3, 4, 5, 6) tf1 = -np.eye(3) if gen1_inv else np.eye(3) gen1_dir = gen1_dir or 'z' tf1 = tf1 @ self.PRINCIPAL_ROTATIONS[gen1_dir][gen1_fold] tl1 = np.array([0., 0., 0.]) gen1_translation_dicts = [self.STATIC_TRANSLATIONS] if gen1_fold in '346': screw_tls = self.DYNAMIC_TRANSLATIONS.get(gen1_fold + gen1_dir, {}) gen1_translation_dicts.append(screw_tls) gen1_translations = dict_union(*gen1_translation_dicts) for tl in gen1_tl: tl1 += gen1_translations[tl] generators.append(BoundedOperation(tf1, tl1)) # generator 2 gen2_inv, gen2_fold, gen2_dir, gen2_tl = elements.group(7, 8, 9, 10) if gen2_fold: tf2 = -np.eye(3) if gen2_inv else np.eye(3) if gen1_fold in '24': gen2_dir = gen2_dir or "x" tf2 = tf2 @ self.PRINCIPAL_ROTATIONS[gen2_dir][gen2_fold] else: # if gen1_fold in '36' gen2_dir = gen2_dir or "'" diagonal_rots = self.FACE_X_DIAGONAL_ROTATIONS if gen1_dir == 'x' \ else self.FACE_Y_DIAGONAL_ROTATIONS if gen1_dir == 'y' \ else self.FACE_Z_DIAGONAL_ROTATIONS tf2 = tf2 @ diagonal_rots[gen2_fold + gen2_dir] tl2 = np.array([0., 0., 0.]) for tl in gen2_tl: tl2 += self.STATIC_TRANSLATIONS[tl] generators.append(BoundedOperation(tf2, tl2)) # generator 3 gen3_inv, gen3_fold, gen3_tl = elements.group(11, 12, 13) if gen3_fold: tf3 = -np.eye(3) if gen3_inv else np.eye(3) tf3 = tf3 @ self.BODY_DIAGONAL_ROTATIONS[gen3_fold] tl3 = np.array([0., 0., 0.]) for tl in gen3_tl: tl3 += self.STATIC_TRANSLATIONS[tl] generators.append(BoundedOperation(tf3, tl3)) # generator 4 gen4_fold, gen4_tl = elements.group(14, 15) if gen4_fold: tl4 = np.array([0., 0., 0.]) for tl in gen4_tl: tl4 += self.STATIC_TRANSLATIONS[tl] generators.append(BoundedOperation(-np.eye(3), tl4)) if any(elements.group(16, 17, 18)): shift_ints = [int(i) for i in elements.group(16, 17, 18)] shift_vector = np.array(shift_ints, dtype=float) / 12 shift_op_left = BoundedOperation(np.eye(3), shift_vector) shift_op_right = BoundedOperation(np.eye(3), -shift_vector) generators = [BoundedOperation.from_matrix(shift_op_left.matrix @ g.matrix @ shift_op_right.matrix) for g in generators] return generators