"""Module sym_entity of viperleed.calc.classes.
Part of the functionality defined here used to be in the slab module.
Defines the SymPlane class, which represent a mirror/glide operations
with a 2D location.
"""
__authors__ = (
'Florian Kraushofer (@fkraushofer)',
'Michele Riva (@michele-riva)',
)
__copyright__ = 'Copyright (c) 2019-2024 ViPErLEED developers'
__created__ = '2023-02-16'
__license__ = 'GPLv3+'
import numpy as np
from viperleed.calc.lib.base import add_edges_and_corners, dist_from_line
[docs]class SymPlane:
"""Candidate plane for a symmetry operation. 'ty' pre-defines a type
(mirror or glide), 'index2' allows the (1,2) and (2,1) directions if True,
and collapse moves pos into the (0,0) unit cell if True."""
[docs] def __init__(self, pos, dr, abt, ty="none", index2=False, collapse=True):
if collapse: # collapse to (0,0) cell
self.pos = np.dot(abt.T, (np.dot(np.linalg.inv(abt.T), pos) % 1.0))
else:
self.pos = pos
self.dir = dr/np.linalg.norm(dr)
# normalized vector perpendicular to pos = in-plane
self.type = ty
self.par = []
optionlist = [(1, 0), (0, 1), (1, 1), (1, -1)]
if index2:
optionlist.extend([(2, 1), (1, 2)])
for (i, j) in optionlist:
if abs((abs(np.dot(self.dir, (i*abt[0]+j*abt[1])))
/ (np.linalg.norm(self.dir)
* np.linalg.norm(i*abt[0]+j*abt[1])))-1.0) < 0.001:
self.par = np.array([i, j])
def __str__(self):
"""Return a string representation of this SymPlane."""
return f'SymPlane(pos={self.pos}, par={self.par})'
@property
def is_glide(self):
"""Return whether this is a mirror plane."""
return self.type == 'glide'
@property
def is_mirror(self):
"""Return whether this is a mirror plane."""
return self.type == 'mirror'
@property
def normal(self):
"""Return a unit vector normal to this plane (even without perp)."""
return np.array((self.dir[1], -self.dir[0]))
def distanceFromOrigin(self, abt):
pointlist = [(0, 0), (1, 0), (0, 1), (1, 1)]
return min([dist_from_line(self.pos, self.pos+self.dir,
p[0]*abt[0]+p[1]*abt[1])
for p in pointlist])
def isEquivalent(self, pl2, abt, eps=0.001):
"""Checks whether two symmetry planes have the same position and
direction (including duplicates in next unit cell)"""
if not np.array_equal(self.par, pl2.par):
return False
# If we're close to an edge or corner, also check translations
fpos = np.dot(np.linalg.inv(np.transpose(abt)), self.pos) % 1.0
releps = eps / np.linalg.norm(abt, axis=1)
complist, _ = add_edges_and_corners([self.pos], (fpos,), releps, abt)
for p in complist:
if dist_from_line(pl2.pos, pl2.pos+pl2.dir, p) < eps:
return True
return False
def point_operation(self, n_dim=2):
"""Return a matrix for mirroring across this plane."""
# See https://en.wikipedia.org/wiki/Householder_transformation
normal = self.normal
mirror = np.identity(n_dim)
mirror[:2, :2] -= 2*np.outer(normal, normal)
return mirror