from __future__ import annotations
from typing import overload
import numpy as np
from bsym.configuration import Configuration
[docs]
def is_square(m: np.ndarray) -> bool:
"""
Test whether a numpy matrix is square.
Args:
m: The matrix.
Returns:
True if matrix is square, False otherwise.
"""
return bool(m.shape[0] == m.shape[1])
[docs]
def is_permutation_matrix(m: np.ndarray) -> bool:
"""
Test whether a numpy array is a `permutation matrix`_.
.. _permutation_matrix: https://en.wikipedia.org/wiki/Permutation_matrix
Args:
m: The matrix.
Returns:
True if matrix is a permutation matrix, False otherwise.
"""
m = np.asanyarray(m)
return bool(
m.ndim == 2 and m.shape[0] == m.shape[1] and
(m.sum(axis=0) == 1).all() and
(m.sum(axis=1) == 1).all() and
((m == 1) | (m == 0)).all()
)
[docs]
class SymmetryOperation:
"""
`SymmetryOperation` class.
"""
def __init__(
self,
matrix: np.ndarray | list[list[int]],
label: str | None = None
) -> None:
"""
Initialise a `SymmetryOperation` object
Args:
matrix: Square 2D array as a ``numpy.ndarray``, ``list``, or
(deprecated) ``numpy.matrix``.
label: Optional string label for this ``SymmetryOperation`` object.
Raises:
TypeError: if matrix is not ``numpy.ndarray``, ``list``, or
``numpy.matrix``.
ValueError: if matrix is not square.
ValueError: if matrix is not a `permutation matrix`_.
.. _permutation_matrix: https://en.wikipedia.org/wiki/Permutation_matrix
Notes:
To construct a `SymmetryOperation` object from a vector of site mappings
use the `SymmetryOperation.from_vector()` method.
Returns:
None
"""
if not isinstance(matrix, (np.matrix, np.ndarray, list)):
raise TypeError
self.matrix = np.asarray(matrix)
if not is_square(self.matrix):
raise ValueError('Not a square matrix')
if not is_permutation_matrix(self.matrix):
raise ValueError('Not a permutation matrix')
self.label = label
self.index_mapping: np.ndarray = np.argmax(self.matrix, axis=1)
@overload
def __mul__(self, other: SymmetryOperation) -> SymmetryOperation: ...
@overload
def __mul__(self, other: Configuration) -> Configuration: ...
def __mul__(self, other: SymmetryOperation | Configuration) -> SymmetryOperation | Configuration:
"""
Multiply this `SymmetryOperation` matrix with another `SymmetryOperation`.
Args:
other: The other symmetry operation or configuration
for the matrix multiplication self * other.
Returns:
A new `SymmetryOperation` instance with the resultant matrix,
or a `Configuration` if `other` is a `Configuration`.
"""
if isinstance(other, SymmetryOperation):
return SymmetryOperation(self.matrix.dot(other.matrix))
elif isinstance(other, Configuration):
return self.operate_on(other)
else:
raise TypeError
[docs]
def invert(self, label: str | None = None) -> SymmetryOperation:
"""
Invert this `SymmetryOperation` object.
Args:
label: Optional label for the inverted symmetry operation.
Returns:
A new `SymmetryOperation` object corresponding to the inverse matrix operation.
"""
return SymmetryOperation(self.matrix.T.copy(), label=label)
[docs]
@classmethod
def from_vector(
cls,
vector: list[int],
count_from_zero: bool = False,
label: str | None = None
) -> SymmetryOperation:
"""
Initialise a SymmetryOperation object from a vector of site mappings.
Args:
vector: Vector of integers defining a symmetry operation mapping.
count_from_zero: Set to True if the site index counts from zero.
label: Optional string label for this `SymmetryOperation` object.
Returns:
A new SymmetryOperation object
"""
if not count_from_zero:
vector = [x - 1 for x in vector]
dim = len(vector)
matrix = np.zeros((dim, dim))
for index, element in enumerate(vector):
matrix[element, index] = 1
new_symmetry_operation = cls(matrix, label=label)
return new_symmetry_operation
[docs]
def operate_on(self, configuration: Configuration) -> Configuration:
"""
Return the Configuration generated by applying this symmetry operation
Args:
configuration: The configuration / occupation vector to operate on
Returns:
The new configuration obtained by operating on configuration with this
symmetry operation.
"""
if not isinstance(configuration, Configuration):
raise TypeError
return Configuration(configuration.vector[self.index_mapping])
[docs]
def character(self) -> int:
"""
Return the character of this symmetry operation (the trace of `self.matrix`).
Args:
None
Returns:
The trace of self.matrix
"""
return int(np.trace(self.matrix))
[docs]
def as_vector(self, count_from_zero: bool = False) -> list[int]:
"""
Return a vector representation of this symmetry operation
Args:
count_from_zero: Set to True if the vector representation counts from zero
Returns:
A vector representation of this symmetry operation (as a list)
"""
offset = 0 if count_from_zero else 1
return (np.argmax(self.matrix, axis=0) + offset).tolist() # type: ignore[no-any-return]
[docs]
def set_label(self, label: str) -> SymmetryOperation:
"""
Set the label for this symmetry operation.
Args:
label: Label to set for this symmetry operation
Returns:
self
"""
self.label = label
return self
[docs]
def pprint(self) -> None:
"""
Pretty print for this symmetry operation
Args:
None
Returns:
None
"""
label = self.label if self.label else '---'
print(label + ' : ' + ' '.join([str(e) for e in self.as_vector()]))
def __repr__(self) -> str:
label = self.label if self.label else '---'
return 'SymmetryOperation\nlabel(' + label + ")\n" + self.matrix.__repr__()