Skip to content

symmetry_operation

SymmetryOperation

Class to represent a crystallographic symmetry operation, composed of a rotation and a translation.

Attributes:

Name Type Description
rotation ndarray

(3, 3) rotation matrix in fractional coordinates

translation ndarray

(3) translation vector in fractional coordinates

Source code in chmpy/crystal/symmetry_operation.py
class SymmetryOperation:
    """
    Class to represent a crystallographic symmetry operation,
    composed of a rotation and a translation.

    Attributes:
        rotation (np.ndarray): (3, 3) rotation matrix in fractional coordinates
        translation (np.ndarray): (3) translation vector in fractional coordinates
    """

    rotation: np.ndarray
    translation: np.ndarray

    def __init__(self, rotation, translation):
        """
        Construct a new symmetry operation from a rotation matrix and
        a translation vector

        Arguments:
            rotation (np.ndarray): (3, 3) rotation matrix
            translation (np.ndarray): (3) translation vector

        Returns:
            SymmetryOperation: a new SymmetryOperation
        """
        self.rotation = rotation
        self.translation = translation % 1

    @property
    def seitz_matrix(self) -> np.ndarray:
        "The Seitz matrix form of this SymmetryOperation"
        s = np.eye(4, dtype=np.float64)
        s[:3, :3] = self.rotation
        s[:3, 3] = self.translation
        return s

    @property
    def integer_code(self) -> int:
        "Represent this SymmetryOperation as a packed integer"
        if not hasattr(self, "_integer_code"):
            self._integer_code = encode_symm_int(self.rotation, self.translation)
        return self._integer_code

    @property
    def cif_form(self) -> str:
        "Represent this SymmetryOperation in string form e.g. '+x,+y,+z'"
        return str(self)

    @property
    def rotation_order(self) -> int:
        """
        The order n of the rotational (linear) part: the smallest n > 0 such
        that R**n is the identity.

        Note for rotoinversions the order is that of the matrix, e.g. a -3
        (3-bar) operation has rotation_order 6.

        Examples:
            >>> SymmetryOperation.from_string_code("-x,-y,z").rotation_order
            2
            >>> SymmetryOperation.from_string_code("-x,-y,-z").rotation_order
            2
        """
        if not hasattr(self, "_rotation_order"):
            self._rotation_order = _rotation_order(_integer_rotation(self.rotation))
        return self._rotation_order

    def _exact_intrinsic_translation(self) -> tuple:
        "The intrinsic translation as exact `Fraction`s (cached)."
        if not hasattr(self, "_intrinsic_translation_exact"):
            rotation = _integer_rotation(self.rotation)
            translation = _exact_translation(self.translation)
            self._intrinsic_translation_exact = _intrinsic_translation(
                rotation, translation, self.rotation_order
            )
        return self._intrinsic_translation_exact

    @property
    def intrinsic_translation(self) -> np.ndarray:
        """
        The origin-independent (screw/glide) component of the translation.

        This is the projection of the translation onto the +1 eigenspace of the
        rotation, i.e. the part that cannot be removed by any choice of origin.
        It is zero for pure rotations, mirrors, rotoinversions and the identity,
        and non-zero for screw axes, glide planes and lattice translations.

        Returns:
            np.ndarray: (3,) intrinsic translation vector, reduced into [0, 1)

        Examples:
            >>> op = SymmetryOperation.from_string_code("-x,1/2+y,-z")
            >>> op.intrinsic_translation.tolist()
            [0.0, 0.5, 0.0]
            >>> SymmetryOperation.from_string_code(
            ...     "-x,-y,z"
            ... ).intrinsic_translation.tolist()
            [0.0, 0.0, 0.0]
        """
        return np.array([float(c) for c in self._exact_intrinsic_translation()])

    @property
    def has_intrinsic_translation(self) -> bool:
        """
        True if this operation has a non-zero intrinsic (screw/glide)
        translation, i.e. it is a screw axis, glide plane or lattice translation.

        Examples:
            >>> SymmetryOperation.from_string_code(
            ...     "-x,1/2+y,-z"
            ... ).has_intrinsic_translation
            True
            >>> SymmetryOperation.from_string_code("-x,-y,z").has_intrinsic_translation
            False
        """
        return any(c != 0 for c in self._exact_intrinsic_translation())

    @property
    def rotation_symbol(self) -> str:
        """
        The Hermann-Mauguin symbol of the rotational (linear) part of this
        operation, ignoring any translation.

        One of "1", "2", "3", "4", "6" (proper rotations), or "-1", "m", "-3",
        "-4", "-6" (improper: inversion, reflection, rotoinversions).

        Examples:
            >>> SymmetryOperation.from_string_code("-x,-y,-z").rotation_symbol
            '-1'
            >>> SymmetryOperation.from_string_code("x,-y,z").rotation_symbol
            'm'
            >>> SymmetryOperation.from_string_code("-x,1/2+y,-z").rotation_symbol
            '2'
        """
        rotation = _integer_rotation(self.rotation)
        trace = int(np.trace(rotation))
        if int(round(np.linalg.det(rotation))) == 1:
            return _PROPER_ROTATION_SYMBOLS[trace]
        return _IMPROPER_ROTATION_SYMBOLS[trace]

    @property
    def geometric_type(self) -> str:
        """
        Classify this operation as a crystallographic symmetry element.

        Returns one of:
            "identity", "translation", "rotation", "screw", "inversion",
            "mirror", "glide", "rotoinversion"

        The distinction between rotation/screw and mirror/glide is made on the
        intrinsic translation (see `has_intrinsic_translation`). Use
        `rotation_symbol` to recover the order (e.g. to distinguish a -3 from a
        -4 rotoinversion). The classification reflects the operation as written;
        in centered cells an operation that combines a rotation with a centering
        translation is still reported as a screw or glide.

        Examples:
            >>> SymmetryOperation.from_string_code("x,y,z").geometric_type
            'identity'
            >>> SymmetryOperation.from_string_code("-x,1/2+y,-z").geometric_type
            'screw'
            >>> SymmetryOperation.from_string_code("x,-y,1/2+z").geometric_type
            'glide'
            >>> SymmetryOperation.from_string_code("-x,-y,-z").geometric_type
            'inversion'
        """
        rotation = _integer_rotation(self.rotation)
        trace = int(np.trace(rotation))
        moving = self.has_intrinsic_translation
        if int(round(np.linalg.det(rotation))) == 1:
            if trace == 3:  # identity rotational part
                return "translation" if moving else "identity"
            return "screw" if moving else "rotation"
        if trace == -3:  # -1
            return "inversion"
        if trace == 1:  # m
            return "glide" if moving else "mirror"
        return "rotoinversion"  # -3, -4, -6

    def inverted(self):
        """ "
        A copy of this symmetry operation under inversion

        Returns:
            SymmetryOperation: an inverted copy of this symmetry operation
        """
        return SymmetryOperation(-self.rotation, -self.translation)

    def __add__(self, value: np.ndarray):
        """
        Add a vector to this symmetry operation's translation vector.

        Returns:
            SymmetryOperation: a copy of this symmetry operation under
                additional translation"
        """
        return SymmetryOperation(self.rotation, self.translation + value)

    def __sub__(self, value: np.ndarray):
        """
        Subtract a vector from this symmetry operation's translation.

        Returns:
            SymmetryOperation: a copy of this symmetry operation under
                additional translation"
        """
        return SymmetryOperation(self.rotation, self.translation - value)

    def apply(self, coordinates: np.ndarray) -> np.ndarray:
        """
        Apply this symmetry operation to a set of fractional coordinates.

        Args:
            coordinates (np.ndarray): (N,3) or (N,4) array of fractional
                coordinates or homogeneous fractional coordinates.

        Returns:
            np.ndarray: (N, 3) array of transformed coordinates
        """
        if coordinates.shape[1] == 4:
            return np.dot(coordinates, self.seitz_matrix.T)
        else:
            return np.dot(coordinates, self.rotation.T) + self.translation

    def __str__(self):
        if not hasattr(self, "_string_code"):
            self._string_code = encode_symm_str(self.rotation, self.translation)
        return self._string_code

    def __lt__(self, other):
        return self.integer_code < other.integer_code

    def __eq__(self, other):
        return self.integer_code == other.integer_code

    def __hash__(self):
        return int(self.integer_code)

    def __repr__(self):
        return f"<{self.__class__.__name__}: {self}>"

    def __call__(self, coordinates):
        return self.apply(coordinates)

    @classmethod
    def from_integer_code(cls, code: int):
        """
        Alternative constructor from an integer-encoded
        symmetry operation e.g. 16484

        See also  the `encode_symm_int`, `decode_symm_int` methods.

        Args:
            code (int): integer-encoded symmetry operation

        Returns:
            SymmetryOperation: a new symmetry operation from the provided integer code
        """

        rot, trans = decode_symm_int(code)
        s = SymmetryOperation(rot, trans)
        s._integer_code = code
        return s

    @classmethod
    def from_string_code(cls, code: str):
        """
        Alternative constructor from a string encoded
        symmetry operation e.g. '+x,+y,+z'.

        See also the `encode_symm_str`, `decode_symm_str` methods.

        Args:
            code (str): string-encoded symmetry operation

        Returns:
            SymmetryOperation: a new symmetry operation from the provided string code
        """
        rot, trans = decode_symm_str(code)
        s = SymmetryOperation(rot, trans)
        s._string_code = code
        return s

    def is_identity(self) -> bool:
        "Returns true if this is the identity symmetry operation '+x,+y,+z'"
        return self.integer_code == 16484

    @classmethod
    def identity(cls):
        "Alternative constructor for the the identity symop i.e. x,y,z"
        return cls.from_integer_code(16484)

    def compose(self, other: "SymmetryOperation") -> "SymmetryOperation":
        """
        Compose this symmetry operation with another: self ∘ other.

        The result is a new symmetry operation that first applies `other`,
        then applies `self`. In matrix form: (R_self, t_self) ∘ (R_other, t_other)
        = (R_self @ R_other, R_self @ t_other + t_self)

        Args:
            other: The symmetry operation to compose with

        Returns:
            SymmetryOperation: The composed operation (self ∘ other)
        """
        # Compose rotations: R_new = R_self @ R_other
        new_rotation = np.dot(self.rotation, other.rotation)

        # Compose translations: t_new = R_self @ t_other + t_self
        new_translation = np.dot(self.rotation, other.translation) + self.translation

        return SymmetryOperation(new_rotation, new_translation)

    def __mul__(self, other: "SymmetryOperation") -> "SymmetryOperation":
        """
        Multiplication operator for symmetry operations: self * other = self ∘ other.

        This is equivalent to `self.compose(other)`.
        """
        return self.compose(other)

    def inverse(self) -> "SymmetryOperation":
        """
        Return the inverse of this symmetry operation.

        For a symmetry operation (R, t), the inverse is (R^-1, -R^-1 @ t).

        Returns:
            SymmetryOperation: The inverse operation
        """
        # For crystallographic rotations in fractional coordinates,
        # R is integer with det ±1 so R^-1 is also integer.
        # Note: R^T = R^-1 only holds in Cartesian coordinates;
        # in fractional coordinates (e.g. hexagonal systems) the
        # rotation matrices are generally not orthogonal.
        inv_rotation = np.round(np.linalg.inv(self.rotation))

        # t_inv = -R^-1 @ t
        inv_translation = -np.dot(inv_rotation, self.translation)

        return SymmetryOperation(inv_rotation, inv_translation)

cif_form property

Represent this SymmetryOperation in string form e.g. '+x,+y,+z'

geometric_type property

Classify this operation as a crystallographic symmetry element.

Returns one of

"identity", "translation", "rotation", "screw", "inversion", "mirror", "glide", "rotoinversion"

The distinction between rotation/screw and mirror/glide is made on the intrinsic translation (see has_intrinsic_translation). Use rotation_symbol to recover the order (e.g. to distinguish a -3 from a -4 rotoinversion). The classification reflects the operation as written; in centered cells an operation that combines a rotation with a centering translation is still reported as a screw or glide.

Examples:

>>> SymmetryOperation.from_string_code("x,y,z").geometric_type
'identity'
>>> SymmetryOperation.from_string_code("-x,1/2+y,-z").geometric_type
'screw'
>>> SymmetryOperation.from_string_code("x,-y,1/2+z").geometric_type
'glide'
>>> SymmetryOperation.from_string_code("-x,-y,-z").geometric_type
'inversion'

has_intrinsic_translation property

True if this operation has a non-zero intrinsic (screw/glide) translation, i.e. it is a screw axis, glide plane or lattice translation.

Examples:

>>> SymmetryOperation.from_string_code(
...     "-x,1/2+y,-z"
... ).has_intrinsic_translation
True
>>> SymmetryOperation.from_string_code("-x,-y,z").has_intrinsic_translation
False

integer_code property

Represent this SymmetryOperation as a packed integer

intrinsic_translation property

The origin-independent (screw/glide) component of the translation.

This is the projection of the translation onto the +1 eigenspace of the rotation, i.e. the part that cannot be removed by any choice of origin. It is zero for pure rotations, mirrors, rotoinversions and the identity, and non-zero for screw axes, glide planes and lattice translations.

Returns:

Type Description
ndarray

np.ndarray: (3,) intrinsic translation vector, reduced into [0, 1)

Examples:

>>> op = SymmetryOperation.from_string_code("-x,1/2+y,-z")
>>> op.intrinsic_translation.tolist()
[0.0, 0.5, 0.0]
>>> SymmetryOperation.from_string_code(
...     "-x,-y,z"
... ).intrinsic_translation.tolist()
[0.0, 0.0, 0.0]

rotation_order property

The order n of the rotational (linear) part: the smallest n > 0 such that R**n is the identity.

Note for rotoinversions the order is that of the matrix, e.g. a -3 (3-bar) operation has rotation_order 6.

Examples:

>>> SymmetryOperation.from_string_code("-x,-y,z").rotation_order
2
>>> SymmetryOperation.from_string_code("-x,-y,-z").rotation_order
2

rotation_symbol property

The Hermann-Mauguin symbol of the rotational (linear) part of this operation, ignoring any translation.

One of "1", "2", "3", "4", "6" (proper rotations), or "-1", "m", "-3", "-4", "-6" (improper: inversion, reflection, rotoinversions).

Examples:

>>> SymmetryOperation.from_string_code("-x,-y,-z").rotation_symbol
'-1'
>>> SymmetryOperation.from_string_code("x,-y,z").rotation_symbol
'm'
>>> SymmetryOperation.from_string_code("-x,1/2+y,-z").rotation_symbol
'2'

seitz_matrix property

The Seitz matrix form of this SymmetryOperation

__add__(value)

Add a vector to this symmetry operation's translation vector.

Returns:

Name Type Description
SymmetryOperation

a copy of this symmetry operation under additional translation"

Source code in chmpy/crystal/symmetry_operation.py
def __add__(self, value: np.ndarray):
    """
    Add a vector to this symmetry operation's translation vector.

    Returns:
        SymmetryOperation: a copy of this symmetry operation under
            additional translation"
    """
    return SymmetryOperation(self.rotation, self.translation + value)

__init__(rotation, translation)

Construct a new symmetry operation from a rotation matrix and a translation vector

Parameters:

Name Type Description Default
rotation ndarray

(3, 3) rotation matrix

required
translation ndarray

(3) translation vector

required

Returns:

Name Type Description
SymmetryOperation

a new SymmetryOperation

Source code in chmpy/crystal/symmetry_operation.py
def __init__(self, rotation, translation):
    """
    Construct a new symmetry operation from a rotation matrix and
    a translation vector

    Arguments:
        rotation (np.ndarray): (3, 3) rotation matrix
        translation (np.ndarray): (3) translation vector

    Returns:
        SymmetryOperation: a new SymmetryOperation
    """
    self.rotation = rotation
    self.translation = translation % 1

__mul__(other)

Multiplication operator for symmetry operations: self * other = self ∘ other.

This is equivalent to self.compose(other).

Source code in chmpy/crystal/symmetry_operation.py
def __mul__(self, other: "SymmetryOperation") -> "SymmetryOperation":
    """
    Multiplication operator for symmetry operations: self * other = self ∘ other.

    This is equivalent to `self.compose(other)`.
    """
    return self.compose(other)

__sub__(value)

Subtract a vector from this symmetry operation's translation.

Returns:

Name Type Description
SymmetryOperation

a copy of this symmetry operation under additional translation"

Source code in chmpy/crystal/symmetry_operation.py
def __sub__(self, value: np.ndarray):
    """
    Subtract a vector from this symmetry operation's translation.

    Returns:
        SymmetryOperation: a copy of this symmetry operation under
            additional translation"
    """
    return SymmetryOperation(self.rotation, self.translation - value)

apply(coordinates)

Apply this symmetry operation to a set of fractional coordinates.

Parameters:

Name Type Description Default
coordinates ndarray

(N,3) or (N,4) array of fractional coordinates or homogeneous fractional coordinates.

required

Returns:

Type Description
ndarray

np.ndarray: (N, 3) array of transformed coordinates

Source code in chmpy/crystal/symmetry_operation.py
def apply(self, coordinates: np.ndarray) -> np.ndarray:
    """
    Apply this symmetry operation to a set of fractional coordinates.

    Args:
        coordinates (np.ndarray): (N,3) or (N,4) array of fractional
            coordinates or homogeneous fractional coordinates.

    Returns:
        np.ndarray: (N, 3) array of transformed coordinates
    """
    if coordinates.shape[1] == 4:
        return np.dot(coordinates, self.seitz_matrix.T)
    else:
        return np.dot(coordinates, self.rotation.T) + self.translation

compose(other)

Compose this symmetry operation with another: self ∘ other.

The result is a new symmetry operation that first applies other, then applies self. In matrix form: (R_self, t_self) ∘ (R_other, t_other) = (R_self @ R_other, R_self @ t_other + t_self)

Parameters:

Name Type Description Default
other SymmetryOperation

The symmetry operation to compose with

required

Returns:

Name Type Description
SymmetryOperation SymmetryOperation

The composed operation (self ∘ other)

Source code in chmpy/crystal/symmetry_operation.py
def compose(self, other: "SymmetryOperation") -> "SymmetryOperation":
    """
    Compose this symmetry operation with another: self ∘ other.

    The result is a new symmetry operation that first applies `other`,
    then applies `self`. In matrix form: (R_self, t_self) ∘ (R_other, t_other)
    = (R_self @ R_other, R_self @ t_other + t_self)

    Args:
        other: The symmetry operation to compose with

    Returns:
        SymmetryOperation: The composed operation (self ∘ other)
    """
    # Compose rotations: R_new = R_self @ R_other
    new_rotation = np.dot(self.rotation, other.rotation)

    # Compose translations: t_new = R_self @ t_other + t_self
    new_translation = np.dot(self.rotation, other.translation) + self.translation

    return SymmetryOperation(new_rotation, new_translation)

from_integer_code(code) classmethod

Alternative constructor from an integer-encoded symmetry operation e.g. 16484

See also the encode_symm_int, decode_symm_int methods.

Parameters:

Name Type Description Default
code int

integer-encoded symmetry operation

required

Returns:

Name Type Description
SymmetryOperation

a new symmetry operation from the provided integer code

Source code in chmpy/crystal/symmetry_operation.py
@classmethod
def from_integer_code(cls, code: int):
    """
    Alternative constructor from an integer-encoded
    symmetry operation e.g. 16484

    See also  the `encode_symm_int`, `decode_symm_int` methods.

    Args:
        code (int): integer-encoded symmetry operation

    Returns:
        SymmetryOperation: a new symmetry operation from the provided integer code
    """

    rot, trans = decode_symm_int(code)
    s = SymmetryOperation(rot, trans)
    s._integer_code = code
    return s

from_string_code(code) classmethod

Alternative constructor from a string encoded symmetry operation e.g. '+x,+y,+z'.

See also the encode_symm_str, decode_symm_str methods.

Parameters:

Name Type Description Default
code str

string-encoded symmetry operation

required

Returns:

Name Type Description
SymmetryOperation

a new symmetry operation from the provided string code

Source code in chmpy/crystal/symmetry_operation.py
@classmethod
def from_string_code(cls, code: str):
    """
    Alternative constructor from a string encoded
    symmetry operation e.g. '+x,+y,+z'.

    See also the `encode_symm_str`, `decode_symm_str` methods.

    Args:
        code (str): string-encoded symmetry operation

    Returns:
        SymmetryOperation: a new symmetry operation from the provided string code
    """
    rot, trans = decode_symm_str(code)
    s = SymmetryOperation(rot, trans)
    s._string_code = code
    return s

identity() classmethod

Alternative constructor for the the identity symop i.e. x,y,z

Source code in chmpy/crystal/symmetry_operation.py
@classmethod
def identity(cls):
    "Alternative constructor for the the identity symop i.e. x,y,z"
    return cls.from_integer_code(16484)

inverse()

Return the inverse of this symmetry operation.

For a symmetry operation (R, t), the inverse is (R^-1, -R^-1 @ t).

Returns:

Name Type Description
SymmetryOperation SymmetryOperation

The inverse operation

Source code in chmpy/crystal/symmetry_operation.py
def inverse(self) -> "SymmetryOperation":
    """
    Return the inverse of this symmetry operation.

    For a symmetry operation (R, t), the inverse is (R^-1, -R^-1 @ t).

    Returns:
        SymmetryOperation: The inverse operation
    """
    # For crystallographic rotations in fractional coordinates,
    # R is integer with det ±1 so R^-1 is also integer.
    # Note: R^T = R^-1 only holds in Cartesian coordinates;
    # in fractional coordinates (e.g. hexagonal systems) the
    # rotation matrices are generally not orthogonal.
    inv_rotation = np.round(np.linalg.inv(self.rotation))

    # t_inv = -R^-1 @ t
    inv_translation = -np.dot(inv_rotation, self.translation)

    return SymmetryOperation(inv_rotation, inv_translation)

inverted()

" A copy of this symmetry operation under inversion

Returns:

Name Type Description
SymmetryOperation

an inverted copy of this symmetry operation

Source code in chmpy/crystal/symmetry_operation.py
def inverted(self):
    """ "
    A copy of this symmetry operation under inversion

    Returns:
        SymmetryOperation: an inverted copy of this symmetry operation
    """
    return SymmetryOperation(-self.rotation, -self.translation)

is_identity()

Returns true if this is the identity symmetry operation '+x,+y,+z'

Source code in chmpy/crystal/symmetry_operation.py
def is_identity(self) -> bool:
    "Returns true if this is the identity symmetry operation '+x,+y,+z'"
    return self.integer_code == 16484

decode_symm_int(coded_integer)

Decode an integer encoded symmetry operation.

A space group operation is compressed using ternary numerical system for rotation and duodecimal system for translation. This is achieved because each element of rotation matrix can have only one of {-1,0,1}, and the translation can have one of {0,2,3,4,6,8,9,10} divided by 12. Therefore 3^9 * 12^3 = 34012224 different values can map space group operations. In principle, octal numerical system can be used for translation, but duodecimal system is more convenient.

encode_symm_str(*decode_symm_int(16484)) '+x,+y,+z'

Parameters:

Name Type Description Default
coded_integer int

integer encoding a symmetry operation

required

Returns:

Type Description

Tuple[np.ndarray, np.ndarray]: (3,3) rotation matrix, (3) translation vector

Source code in chmpy/crystal/symmetry_operation.py
def decode_symm_int(coded_integer):
    """
    Decode an integer encoded symmetry operation.

    A space group operation is compressed using ternary numerical system for
    rotation and duodecimal system for translation. This is achieved because
    each element of rotation matrix can have only one of {-1,0,1}, and the
    translation can have one of {0,2,3,4,6,8,9,10} divided by 12.  Therefore
    3^9 * 12^3 = 34012224 different values can map space group operations. In
    principle, octal numerical system can be used for translation, but
    duodecimal system is more convenient.

    >>> encode_symm_str(*decode_symm_int(16484))
    '+x,+y,+z'

    Args:
        coded_integer (int): integer encoding a symmetry operation

    Returns:
        Tuple[np.ndarray, np.ndarray]: (3,3) rotation matrix, (3) translation vector
    """
    r = coded_integer % 19683  # 19683 = 3**9
    shift = 6561  # 6561 = 3**8
    rotation = np.empty((3, 3), dtype=np.float64)
    translation = np.empty(3, dtype=np.float64)
    for i in (0, 1, 2):
        for j in (0, 1, 2):
            # we need integer division here
            rotation[i, j] = (r % (shift * 3)) // shift - 1
            shift //= 3

    t = coded_integer // 19683
    shift = 144
    for i in (0, 1, 2):
        # we need integer division here by shift
        translation[i] = ((t % (shift * 12)) // shift) / 12
        shift //= 12
    return rotation, translation

decode_symm_str(s)

Decode a symmetry operation represented in the string form e.g. '1/2 + x, y, -z -0.25' into a rotation matrix and translation vector.

encode_symm_str(decode_symm_str("x,y,z")) '+x,+y,+z' encode_symm_str(decode_symm_str("1/2 - x,y-0.3333333,z")) '1/2-x,2/3+y,+z'

Parameters:

Name Type Description Default
s str

the encoded symmetry operation string

required

Returns:

Type Description

Tuple[np.ndarray, np.ndarray]: a (3,3) rotation matrix and a (3) translation vector

Source code in chmpy/crystal/symmetry_operation.py
def decode_symm_str(s):
    """
    Decode a symmetry operation represented in the string
    form e.g. '1/2 + x, y, -z -0.25' into a rotation matrix
    and translation vector.

    >>> encode_symm_str(*decode_symm_str("x,y,z"))
    '+x,+y,+z'
    >>> encode_symm_str(*decode_symm_str("1/2 - x,y-0.3333333,z"))
    '1/2-x,2/3+y,+z'

    Args:
        s (str): the encoded symmetry operation string

    Returns:
        Tuple[np.ndarray, np.ndarray]: a (3,3) rotation matrix and a (3)
            translation vector
    """
    rotation = np.zeros((3, 3), dtype=np.float64)
    translation = np.zeros((3,), dtype=np.float64)
    tokens = s.lower().replace(" ", "").split(",")
    for i, row in enumerate(tokens):
        fac = 1
        row = row.strip()
        symbols = re.findall(SYMM_STR_SYMBOL_REGEX, row)
        for symbol in symbols:
            if "x" in symbol:
                idx = 0
                fac = -1 if "-x" in symbol else 1
                rotation[i, idx] = fac
            elif "y" in symbol:
                idx = 1
                fac = -1 if "-y" in symbol else 1
                rotation[i, idx] = fac
            elif "z" in symbol:
                idx = 2
                fac = -1 if "-z" in symbol else 1
                rotation[i, idx] = fac
            else:
                if "/" in symbol:
                    numerator, denominator = symbol.split("/")
                    translation[i] = Fraction(
                        Fraction(numerator), Fraction(denominator)
                    )
                else:
                    translation[i] += float(Fraction(symbol))
    translation = translation % 1
    return rotation, translation

encode_symm_int(rotation, translation)

Encode an integer encoded symmetry from a rotation matrix and translation vector.

A space group operation is compressed using ternary numerical system for rotation and duodecimal system for translation. This is achieved because each element of rotation matrix can have only one of {-1,0,1}, and the translation can have one of {0,2,3,4,6,8,9,10} divided by 12. Therefore 3^9 * 12^3 = 34012224 different values can map space group operations. In principle, octal numerical system can be used for translation, but duodecimal system is more convenient.

encode_symm_int(((1, 0, 0), (0, 1, 0), (0, 0, 1)), (0, 0, 0)) 16484 encode_symm_int(((1, 0, 0), (0, 1, 0), (0, 1, 1)), (0, 0.5, 0)) 1433663

Args: rotation (array_like): (3,3) matrix of -1, 0, or 1s encoding the rotation component of the symmetry operation translation (array_like): (3) vector of rational numbers encoding the translation component of the symmetry operation

Returns:

Name Type Description
int

the encoded symmetry operation

Source code in chmpy/crystal/symmetry_operation.py
def encode_symm_int(rotation, translation):
    """
    Encode an integer encoded symmetry from a rotation matrix and translation
    vector.

    A space group operation is compressed using ternary numerical system for
    rotation and duodecimal system for translation. This is achieved because
    each element of rotation matrix can have only one of {-1,0,1}, and the
    translation can have one of {0,2,3,4,6,8,9,10} divided by 12.  Therefore
    3^9 * 12^3 = 34012224 different values can map space group operations. In
    principle, octal numerical system can be used for translation, but
    duodecimal system is more convenient.

    >>> encode_symm_int(((1, 0, 0), (0, 1, 0), (0, 0, 1)), (0, 0, 0))
    16484
    >>> encode_symm_int(((1, 0, 0), (0, 1, 0), (0, 1, 1)), (0, 0.5, 0))
    1433663

     Args:
        rotation (array_like): (3,3) matrix of -1, 0, or 1s encoding
            the rotation component of the symmetry operation
        translation (array_like): (3) vector of rational numbers encoding
            the translation component of the symmetry operation

    Returns:
        int: the encoded symmetry operation
    """

    r = 0
    shift = 1
    # encode rotation component
    rotation = np.round(np.array(rotation)).astype(int) + 1
    for i in (2, 1, 0):
        for j in (2, 1, 0):
            r += rotation[i, j] * shift
            shift *= 3
    t = 0
    shift = 1
    translation = np.round(np.array(translation) * 12).astype(int)
    for i in (2, 1, 0):
        t += translation[i] * shift
        shift *= 12
    return int(r + t * 19683)

encode_symm_str(rotation, translation)

Encode a rotation matrix (of -1, 0, 1s) and (rational) translation vector into string form e.g. 1/2-x,z-1/3,-y-1/6

encode_symm_str(((-1, 0, 0), (0, 0, 1), (0, 1, 0)), (0, 0.5, 1 / 3)) '-x,1/2+z,1/3+y' encode_symm_str(((1, 1, 1), (1, 0, 1), (0, 1, 0)), (0, 0.5, 1 / 3)) '+x+y+z,1/2+x+z,1/3+y'

Parameters:

Name Type Description Default
rotation array_like

(3,3) matrix of -1, 0, or 1s encoding the rotation component of the symmetry operation

required
translation array_like

(3) vector of rational numbers encoding the translation component of the symmetry operation

required

Returns:

Name Type Description
str

the encoded symmetry operation

Source code in chmpy/crystal/symmetry_operation.py
def encode_symm_str(rotation, translation):
    """
    Encode a rotation matrix (of -1, 0, 1s) and (rational) translation vector
    into string form e.g. 1/2-x,z-1/3,-y-1/6

    >>> encode_symm_str(((-1, 0, 0), (0, 0, 1), (0, 1, 0)), (0, 0.5, 1 / 3))
    '-x,1/2+z,1/3+y'
    >>> encode_symm_str(((1, 1, 1), (1, 0, 1), (0, 1, 0)), (0, 0.5, 1 / 3))
    '+x+y+z,1/2+x+z,1/3+y'

    Args:
        rotation (array_like): (3,3) matrix of -1, 0, or 1s encoding
            the rotation component of the symmetry operation
        translation (array_like): (3) vector of rational numbers encoding
            the translation component of the symmetry operation

    Returns:
        str: the encoded symmetry operation
    """
    symbols = "xyz"
    res = []
    for i in (0, 1, 2):
        t = Fraction(translation[i]).limit_denominator(12)
        v = ""
        if t != 0:
            v += str(t)
        for j in range(0, 3):
            c = rotation[i][j]
            if c != 0:
                s = "-" if c < 0 else "+"
                v += s + symbols[j]
        res.append(v)
    res = ",".join(res)
    return res

expanded_symmetry_list(reduced_symops, lattice_type)

Create an expanded list of symmetry operations from the minimum specification given a certain lattice type.

Parameters:

Name Type Description Default
reduced_symops List[SymmetryOperation]

reduced list of symmetry operations

required
lattice_type int

integer encoded lattice type with SHELX conventions, i.e.

1: P,
2: I,
3: rhombohedral obverse on hexagonal axes,
4: F,
5: A,
6: B,
7: C

required

Returns: List[SymmetryOperation]: an expanded list of symmetry operations given lattice type

Source code in chmpy/crystal/symmetry_operation.py
def expanded_symmetry_list(reduced_symops, lattice_type):
    """
    Create an expanded list of symmetry operations from the minimum
    specification given a certain lattice type.

    Args:
        reduced_symops (List[SymmetryOperation]): reduced list of symmetry operations
        lattice_type (int): integer encoded lattice type with SHELX conventions, i.e.
            ```
            1: P,
            2: I,
            3: rhombohedral obverse on hexagonal axes,
            4: F,
            5: A,
            6: B,
            7: C
            ```
    Returns:
        List[SymmetryOperation]: an expanded list of symmetry operations
            given lattice type
    """
    lattice_type_value = abs(lattice_type)
    translations = LATTICE_TYPE_TRANSLATIONS[lattice_type_value]

    identity = SymmetryOperation.identity()
    if identity not in reduced_symops:
        LOG.debug("Appending identity symop %s to reduced_symops")
        reduced_symops.append(identity)
    LOG.debug("Reduced symmetry list contains %d symops", len(reduced_symops))

    full_symops = []

    for symop in reduced_symops:
        full_symops.append(symop)
        for t in translations:
            full_symops.append(symop + t)

    if lattice_type > 0:
        full_symops += [x.inverted() for x in full_symops]

    LOG.debug("Expanded symmetry list contains %d symops", len(full_symops))
    return full_symops

reduced_symmetry_list(full_symops, lattice_type)

Reduce an expanded list of symmetry operations to the minimum specification given a certain lattice type.

Parameters:

Name Type Description Default
full_symops List[SymmetryOperation]

list of symmetry operations

required
lattice_type int

integer encoded lattice type with SHELX conventions, i.e.

1: P,
2: I,
3: rhombohedral obverse on hexagonal axes,
4: F,
5: A,
6: B,
7: C

required

Returns: List[SymmetryOperation]: minimal list of symmetry operations given lattice type

Source code in chmpy/crystal/symmetry_operation.py
def reduced_symmetry_list(full_symops, lattice_type):
    """
    Reduce an expanded list of symmetry operations to the minimum
    specification given a certain lattice type.

    Args:
        full_symops (List[SymmetryOperation]): list of symmetry operations
        lattice_type (int): integer encoded lattice type with SHELX conventions, i.e.
            ```
            1: P,
            2: I,
            3: rhombohedral obverse on hexagonal axes,
            4: F,
            5: A,
            6: B,
            7: C
            ```
    Returns:
        List[SymmetryOperation]: minimal list of symmetry operations given lattice type
    """
    lattice_type_value = abs(lattice_type)
    translations = LATTICE_TYPE_TRANSLATIONS[lattice_type_value]

    reduced_symops = [SymmetryOperation.identity()]
    symops_to_process = list(full_symops)

    inversion = lattice_type > 0

    while symops_to_process:
        next_symop = symops_to_process.pop(0)
        if next_symop in reduced_symops:
            continue
        if inversion and next_symop.inverted() in reduced_symops:
            continue
        for t in translations:
            x = next_symop + t
            if inversion and x.inverted() in reduced_symops:
                break
            if x in reduced_symops:
                break
        else:
            reduced_symops.append(next_symop)

    LOG.debug("Reduced symmetry list contains %d symops", len(reduced_symops))
    return reduced_symops