import numpy as np
from InterPhon import error
# Searching lattice point group operations
W_candidate = [np.array([[0, 1],
[1, 0]]), np.array([[0, 1],
[-1, 0]]), np.array([[0, -1],
[1, 0]]), np.array([[0, -1],
[-1, 0]]),
np.array([[0, 1],
[1, 1]]), np.array([[0, 1],
[1, -1]]), np.array([[0, 1],
[-1, 1]]), np.array([[0, -1],
[1, 1]]), np.array([[0, -1],
[-1, 1]]), np.array([[0, -1],
[1, -1]]), np.array([[0, 1],
[-1, -1]]), np.array([[0, -1],
[-1, -1]]),
np.array([[1, 0],
[0, 1]]), np.array([[1, 0],
[0, -1]]), np.array([[-1, 0],
[0, 1]]), np.array([[-1, 0],
[0, -1]]),
np.array([[1, 0],
[1, 1]]), np.array([[1, 0],
[1, -1]]), np.array([[1, 0],
[-1, 1]]), np.array([[-1, 0],
[1, 1]]), np.array([[-1, 0],
[-1, 1]]), np.array([[-1, 0],
[1, -1]]), np.array([[1, 0],
[-1, -1]]), np.array([[-1, 0],
[-1, -1]]),
np.array([[1, 1],
[0, 1]]), np.array([[1, 1],
[0, -1]]), np.array([[1, -1],
[0, 1]]), np.array([[-1, 1],
[0, 1]]), np.array([[-1, -1],
[0, 1]]), np.array([[-1, 1],
[0, -1]]), np.array([[1, -1],
[0, -1]]), np.array([[-1, -1],
[0, -1]]),
np.array([[1, 1],
[1, 0]]), np.array([[1, 1],
[-1, 0]]), np.array([[1, -1],
[1, 0]]), np.array([[-1, 1],
[1, 0]]), np.array([[-1, -1],
[1, 0]]), np.array([[-1, 1],
[-1, 0]]), np.array([[1, -1],
[-1, 0]]), np.array([[-1, -1],
[-1, 0]]),
]
[docs]class Symmetry2D(object):
"""
Symmetry class for searching and leveraging the 2D in-plane symmetry operations.
The symmetry of a given crystal structure is analyzed using the instance methods,
and resulting information is stored in the instance variables of this class.
:param unit_cell: Instance of UnitCell class
:type unit_cell: :class:`core.UnitCell`
:param super_cell: Instance of SuperCell class
:type super_cell: :class:`core.SuperCell`
:param user_arg: Instance of UserArgument class
:type user_arg: :class:`core.PreArgument` or :class:`core.PostArgument`
"""
def __init__(self, unit_cell,
super_cell,
user_arg):
"""
Constructor of Symmetry2D class.
"""
self.unit_cell = unit_cell
self.super_cell = super_cell
self.user_arg = user_arg
self.W_select = []
self.w_select = []
self.same_index_select = []
self.point_group = None
self.point_group_ind = []
self.require_atom = []
self.not_require_atom = []
self.same_supercell_index_select = []
self.point_group_for_self_require_atom = []
self.independent_by_W_index = []
self.independent_by_W_displacement_cart = []
self.independent_by_single_displacement_cart = []
self.independent_additional_displacement_cart = []
# @property
# def user_arg(self):
# return self.__user_arg
#
# @user_arg.setter
# def user_arg(self, _user_arg):
# if isinstance(_user_arg, PreArgument):
# self.__user_arg = _user_arg
# else:
# ValueError(
# "'{0}' should be the instance of <class 'InterPhon.core.pre_check.PreArgument'>".format(_user_arg))
#
# @property
# def unit_cell(self):
# return self.__unit_cell
#
# @unit_cell.setter
# def unit_cell(self, _unit_cell):
# if isinstance(_unit_cell, UnitCell):
# self.__unit_cell = _unit_cell
# else:
# ValueError(
# "'{0}' should be the instance of <class 'InterPhon.core.unit_cell.UnitCell'>".format(_unit_cell))
#
# @property
# def super_cell(self):
# return self.__super_cell
#
# @super_cell.setter
# def super_cell(self, _super_cell):
# if isinstance(_super_cell, SuperCell):
# self.__super_cell = _super_cell
# else:
# ValueError(
# "'{0}' should be the instance of <class 'InterPhon.core.super_cell.SuperCell'>".format(_super_cell))
[docs] def search_point_group(self):
"""
Search point group operations of its instance crystal structure (**self.unit_cell**).
This instance method returns:
**1) self.W_select**: rotation part of an affine mapping (W, w) belonging to symmetry operations of the crystal.
**2) self.w_select**: translation part of an affine mapping (W, w) belonging to symmetry operations of the crystal.
**3) self.same_index_select**: index of image atoms mapped onto by applying an (W, w) to atoms in unit cell.
:return: **self.W_select**, **self.w_select**, **self.same_index_select**
:rtype: tuple
"""
# metric tensor
G_metric = np.dot(self.unit_cell.lattice_matrix.copy()[np.ix_(self.user_arg.periodicity.nonzero()[0],
self.user_arg.periodicity.nonzero()[0])],
np.transpose(self.unit_cell.lattice_matrix.copy()[np.ix_(self.user_arg.periodicity.nonzero()[0],
self.user_arg.periodicity.nonzero()[0])]))
# Search lattice point group operations
rot_ind = []
for ind, rot in enumerate(W_candidate):
G_rotate = np.dot(np.transpose(rot), np.dot(G_metric, rot))
if np.allclose(G_metric, G_rotate, atol=1e-06):
rot_ind.append(ind)
# Search space group operations
atom_original = np.transpose(self.unit_cell.atom_direct.copy())
atom_true_original = atom_original[:, self.unit_cell.atom_true]
w_for_given_rot = []
same_index = []
for ind in rot_ind:
_W_candidate = np.identity(3)
_W_candidate[np.ix_(self.user_arg.periodicity.nonzero()[0],
self.user_arg.periodicity.nonzero()[0])] = W_candidate.copy()[ind]
atom_rot = np.dot(_W_candidate, atom_original)
atom_true_rot = np.dot(_W_candidate, atom_true_original)
# Candidates of translation part
# if selective, take an atom from bulk region!
if len(self.unit_cell.atom_true) < len(self.unit_cell.atom_type):
tmp_index = [i for i in range(len(self.unit_cell.atom_type)) if i not in self.unit_cell.atom_true][0]
else:
tmp_index = 0
other_atom_ind = [i for i in range(len(self.unit_cell.atom_type))]
other_atom_ind.remove(tmp_index)
w_candidate = [np.array([0.0, 0.0, 0.0])]
for other_index, other_ind in enumerate(other_atom_ind):
if self.unit_cell.atom_type[other_ind] == self.unit_cell.atom_type[tmp_index]:
w = np.round_(atom_original[:, other_index] - atom_rot[:, tmp_index], 6)
if w[np.argwhere(self.user_arg.periodicity == 0)[0]] == 0.0:
w_, _ = np.modf(w)
if not np.allclose(w_, np.zeros([3, ]), atol=1e-06):
w_candidate.append(w_)
# Selection of translation part
trans_for_given_rot = []
_same_index = []
for w in w_candidate:
atom_transform = atom_true_rot + w.reshape([3, 1])
__same_index = []
for index, value in enumerate(self.unit_cell.atom_true):
same_atom_type = [ind_ for ind_, val_ in enumerate(self.unit_cell.atom_true)
if self.unit_cell.atom_type[val_] == self.unit_cell.atom_type[value]]
for _, same_atom_index in enumerate(same_atom_type):
delta_x = atom_transform[:, index] - atom_true_original[:, same_atom_index] # atom-to-atom compare
delta_x_cart = np.matmul(np.transpose(self.unit_cell.lattice_matrix.copy()), delta_x - np.rint(delta_x))
if np.allclose(delta_x_cart, np.zeros([3, ]), atol=1e-04):
# if same_atom_index not in __same_index:
__same_index.append(same_atom_index)
# break
if len(__same_index) == len(self.unit_cell.atom_true):
trans_for_given_rot.append(w)
_same_index.append(__same_index)
w_for_given_rot.append(trans_for_given_rot)
same_index.append(_same_index)
# num of following point-group operations: m, 1, 2, 3, 4, 6
look_up_table = np.array([0, 0, 0, 0, 0, 0])
for ind_ind, _rot_ind in enumerate(rot_ind):
if w_for_given_rot[ind_ind]:
_W_candidate = np.identity(3)
_W_candidate[np.ix_(self.user_arg.periodicity.nonzero()[0],
self.user_arg.periodicity.nonzero()[0])] = W_candidate.copy()[_rot_ind]
self.W_select.append(_W_candidate)
self.w_select.append(w_for_given_rot[ind_ind])
self.same_index_select.append(same_index[ind_ind])
look_up = (np.trace(W_candidate[_rot_ind]), np.linalg.det(W_candidate[_rot_ind]))
if look_up == (0.0, -1.0):
look_up_table[0] += 1
elif look_up == (2.0, 1.0):
look_up_table[1] += 1
elif look_up == (-2.0, 1.0):
look_up_table[2] += 1
elif look_up == (-1.0, 1.0):
look_up_table[3] += 1
elif look_up == (0.0, 1.0):
look_up_table[4] += 1
elif look_up == (1.0, 1.0):
look_up_table[5] += 1
else:
print('What is this operation?')
assert False
if np.allclose(look_up_table, np.array([0, 1, 0, 0, 0, 0])):
# print('Point group = 1')
self.point_group = '1'
elif np.allclose(look_up_table, np.array([0, 1, 1, 0, 0, 0])):
# print('Point group = 2')
self.point_group = '2'
elif np.allclose(look_up_table, np.array([1, 1, 0, 0, 0, 0])):
# print('Point group = m')
self.point_group = 'm'
elif np.allclose(look_up_table, np.array([2, 1, 1, 0, 0, 0])):
# print('Point group = 2mm')
self.point_group = '2mm'
elif np.allclose(look_up_table, np.array([2, 2, 0, 0, 0, 0])):
# print('Point group = m (cm)')
self.point_group = 'm (cm)'
elif np.allclose(look_up_table, np.array([4, 2, 2, 0, 0, 0])):
# print('Point group = 2mm (c2mm)')
self.point_group = '2mm (c2mm)'
elif np.allclose(look_up_table, np.array([0, 1, 1, 0, 2, 0])):
# print('Point group = 4')
self.point_group = '4'
elif np.allclose(look_up_table, np.array([4, 1, 1, 0, 2, 0])):
# print('Point group = 4mm')
self.point_group = '4mm'
elif np.allclose(look_up_table, np.array([0, 1, 0, 2, 0, 0])):
# print('Point group = 3')
self.point_group = '3'
elif np.allclose(look_up_table, np.array([3, 1, 0, 2, 0, 0])):
# print('Point group = 3m')
self.point_group = '3m'
elif np.allclose(look_up_table, np.array([0, 1, 1, 2, 0, 2])):
# print('Point group = 6')
self.point_group = '6'
elif np.allclose(look_up_table, np.array([6, 1, 1, 2, 0, 2])):
# print('Point group = 6mm')
self.point_group = '6mm'
else:
raise error.Cannot_Search_Point_Group(look_up_table)
return self.W_select, self.w_select, self.same_index_select
[docs] def search_image_atom(self):
"""
Search index of image atoms transformed by the point group operations of its instance crystal structure (**self.unit_cell**).
This instance method returns:
**1) self.point_group_ind**: index of point group operation W belonging to symmetry operations of the crystal.
**2) self.require_atom**: index of atoms in unit cell to which displacements are required.
**3) self.not_require_atom**: index of atoms in unit cell to which displacements are not required.
**4) self.same_supercell_index_select**: index of image atoms mapped onto by applying an (W, w) to atoms in super cell.
:return: **self.point_group_ind**, **self.require_atom**, **self.not_require_atom**, **self.same_supercell_index_select**
:rtype: tuple
"""
for _ind, _ in enumerate(self.unit_cell.atom_true):
if self.require_atom:
found_flag = False
for W_ind, same in enumerate(self.same_index_select):
if same[0][_ind] in self.require_atom:
self.point_group_ind.append(W_ind)
self.not_require_atom.append(_ind)
found_flag = True
break
if not found_flag:
self.require_atom.append(_ind)
else:
self.require_atom.append(_ind)
for _W_ind, _W_select in enumerate(self.W_select):
_same_supercell_image_index = self.search_cell_image_index(_W_select, self.super_cell)
self.same_supercell_index_select.append(_same_supercell_image_index)
return self.point_group_ind, self.require_atom, self.not_require_atom, self.same_supercell_index_select
[docs] def search_cell_image_index(self, W_direct,
cell):
"""
Search index of image atoms, in the given cell, transformed by the given point group operation.
This instance method returns:
**1) _same_cell_index**: index of image atoms mapped onto by applying the given point group operation to atoms in the given cell.
:param W_direct: Point group operation W represented in direct coordinates
:type W_direct: np.ndarray[int]
:param cell: Instance of UnitCell or SuperCell class
:type cell: :class:`core.UnitCell` or :class:`core.SuperCell`
:return: Index of image atoms mapped onto by applying an (W, w) to atoms in the given cell
:rtype: List[int]
"""
# conserving the image index in primitive cell
_enlarge = int(len(cell.atom_type) / len(self.unit_cell.atom_type))
W_ind = self.find_point_group_index(W_direct)
satom_true_original = np.transpose(cell.atom_direct.copy()[cell.atom_true, :]) # [3, satom_true]
_same_cell_index = []
for _atom_index, _image_index in enumerate(self.same_index_select[W_ind][0]):
_satom_in_primitive = satom_true_original[:, _atom_index * _enlarge] # [3, ]
_satom_in_primitive_transform = satom_true_original[:, _image_index * _enlarge] # [3, ]
__same_cell_index = []
for _satom_index, value in enumerate(cell.atom_true):
_min_vector = satom_true_original[:, _satom_index] - _satom_in_primitive
_min_vector_rot = np.dot(W_direct, _min_vector)
# for w in self.w_select[W_ind]:
# _satom_in_primitive_transform = _satom_in_primitive_rot + w.reshape([3, ])
same_satom_type = [ind_ for ind_, val_ in enumerate(cell.atom_true)
if cell.atom_type[val_] == cell.atom_type[value]]
for _, same_satom_index in enumerate(same_satom_type):
delta_x = _satom_in_primitive_transform + _min_vector_rot - satom_true_original[:, same_satom_index]
delta_x_cart = np.matmul(np.transpose(cell.lattice_matrix), delta_x - np.rint(delta_x))
if np.allclose(delta_x_cart, np.zeros([3, ]), atol=1e-04):
# if same_satom_index not in __same_supercell_index:
__same_cell_index.append(same_satom_index)
# break
if len(__same_cell_index) == len(cell.atom_true):
_same_cell_index.append(__same_cell_index)
return _same_cell_index
[docs] def search_self_image_atom(self):
"""
Search index of point group operation by which atoms in **self.require_atom** are mapped onto the identical atoms.
"""
for _require in self.require_atom:
_sym_point_for_require = []
for _W_ind, _same_index_select in enumerate(self.same_index_select):
if _same_index_select[0][_require] == _require:
_sym_point_for_require.append(_W_ind)
self.point_group_for_self_require_atom.append(_sym_point_for_require)
[docs] def search_independent_displacement(self):
"""
Set independent displacement vectors by applying the point group operations to an arbitrary displacement vector.
To generate a force constant matrix, three linearly independent displacement vectors are required for each atom in unit cell.
"""
_original_basis = np.transpose(self.unit_cell.lattice_matrix.copy())
to_cart_coord = _original_basis / np.linalg.norm(_original_basis, axis=0)
to_direct_coord = np.linalg.inv(to_cart_coord)
_random_direction_cart = np.array([1, 0, 1]) / np.linalg.norm(np.array([1, 0, 1]))
for _point_group_for_self_require in self.point_group_for_self_require_atom:
__W_displacement_cart = []
__require_displacement_cart = []
for __point_group_for_self_require in _point_group_for_self_require:
tmp_W = np.identity(3)
set_W = [tmp_W]
tmp_W = tmp_W @ self.W_select[__point_group_for_self_require]
while not np.allclose(tmp_W, np.identity(3)):
set_W.append(tmp_W)
tmp_W = tmp_W @ self.W_select[__point_group_for_self_require]
set_W_in_cart = [to_cart_coord @ W_select @ to_direct_coord for W_select in set_W]
for _W_in_cart in set_W_in_cart:
__W_displacement_cart.append(_W_in_cart)
_image_direction_cart = _W_in_cart @ _random_direction_cart.copy()
__require_displacement_cart.append(_image_direction_cart)
_W_displacement_cart = [__W_displacement_cart[0]]
_W_index = [self.find_point_group_index(to_direct_coord @ __W_displacement_cart[0] @ to_cart_coord)]
_require_displacement_cart = [__require_displacement_cart[0]]
for i in range(1, len(__require_displacement_cart)):
independent = True
for j in range(0, i):
# independence test by Cauchy–Schwarz inequality
_inner_product = np.dot(__require_displacement_cart[i], __require_displacement_cart[j])
_diff = np.dot(__require_displacement_cart[i], __require_displacement_cart[i]) \
* np.dot(__require_displacement_cart[j], __require_displacement_cart[j]) \
- np.dot(_inner_product, _inner_product)
if _diff < 1e-06:
independent = False
break
if independent:
if len(_require_displacement_cart) < 3:
_W_displacement_cart.append(__W_displacement_cart[i])
_W_index.append(self.find_point_group_index(to_direct_coord @ __W_displacement_cart[i] @ to_cart_coord))
_require_displacement_cart.append(__require_displacement_cart[i])
else:
break
self.independent_by_W_index.append(_W_index)
self.independent_by_W_displacement_cart.append(_W_displacement_cart)
self.independent_by_single_displacement_cart.append(_require_displacement_cart)
[docs] def gen_additional_displacement(self):
"""
Set additional displacement vectors to make a total of three linearly independent displacement vectors.
"""
for _independent_by_single_displacement_cart in self.independent_by_single_displacement_cart:
_independent_additional_displacement_cart = []
additional_displacement_candidate = [np.array([0, 1, 1]) / np.linalg.norm(np.array([0, 1, 1])),
np.array([1, 1, 0]) / np.linalg.norm(np.array([1, 1, 0]))]
if len(_independent_by_single_displacement_cart) == 3:
pass
elif len(_independent_by_single_displacement_cart) == 2:
for _additional_displacement in additional_displacement_candidate:
independent = True
for __independent_by_single_displacement in _independent_by_single_displacement_cart:
_inner_product = np.dot(_additional_displacement, __independent_by_single_displacement)
_diff = np.dot(_additional_displacement, _additional_displacement) \
* np.dot(__independent_by_single_displacement, __independent_by_single_displacement) \
- np.dot(_inner_product, _inner_product)
if _diff < 1e-06:
independent = False
break
if independent:
_independent_additional_displacement_cart.append(_additional_displacement)
break
elif len(_independent_by_single_displacement_cart) == 1:
for _additional_displacement in additional_displacement_candidate:
_independent_additional_displacement_cart.append(_additional_displacement)
self.independent_additional_displacement_cart.append(_independent_additional_displacement_cart)
[docs] def find_point_group_index(self, W_direct):
"""
Search index of point group operation of the given point group operation.
This instance method returns:
**1) _W_ind**: index of point group operation.
:param W_direct: Point group operation W represented in direct
:type W_direct: np.ndarray[int]
:return: Index of point group operation
:rtype: int
"""
found_flag = False
for _W_ind, _W_select in enumerate(self.W_select):
if np.allclose(_W_select, W_direct, atol=1e-06):
found_flag = True
return _W_ind
if not found_flag:
assert False