Source code for hexrd.material.symmetry

# -*- coding: utf-8 -*-
# =============================================================================
# Copyright (c) 2012, Lawrence Livermore National Security, LLC.
# Produced at the Lawrence Livermore National Laboratory.
# Written by Joel Bernier <bernier2@llnl.gov> and others.
# LLNL-CODE-529294.
# All rights reserved.
#
# This file is part of HEXRD. For details on dowloading the source,
# see the file COPYING.
#
# Please also see the file LICENSE.
#
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License (as published by the Free
# Software Foundation) version 2.1 dated February 1999.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the IMPLIED WARRANTY OF MERCHANTABILITY
# or FITNESS FOR A PARTICULAR PURPOSE. See the terms and conditions of the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this program (see file LICENSE); if not, write to
# the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
# Boston, MA 02111-1307 USA or visit <http://www.gnu.org/licenses/>.
# =============================================================================
# -*-python-*-
#
# Module containing functions relevant to symmetries

import numpy as np
from numba import njit
from numpy import (array, sqrt, pi,
                   vstack, c_, dot,
                   argmax)

# from hexrd.rotations import quatOfAngleAxis, quatProductMatrix, fixQuat
from hexrd import rotations as rot
from hexrd import constants
from hexrd.utils.decorators import memoize

# Imports in case others are importing from here
from hexrd.rotations import (toFundamentalRegion,
                             ltypeOfLaueGroup,
                             quatOfLaueGroup)


# =============================================================================
# Module vars
# =============================================================================

eps = constants.sqrt_epsf
sq3by2 = sqrt(3.)/2.
piby2 = pi/2.
piby3 = pi/3.
piby4 = pi/4.
piby6 = pi/6.


# =============================================================================
# Functions
# =============================================================================


[docs]def GeneratorString(sgnum): ''' these rhombohedral space groups have a hexagonal setting with different symmetry matrices and generator strings 146: 231 148: 232 ... and so on ''' sg = sgnum-1 # sgdict = {146:231, 148:232, 155:233, 160:234, 161:235, 166:236, 167:237} # if(sgnum in sgdict): # sg = sgdict[sgnum]-1 return constants.SYM_GL[sg]
[docs]def MakeGenerators(genstr, setting): t = 'aOOO' mat = SYM_fillgen(t) genmat = mat # genmat[0,:,:] = constants.SYM_GENERATORS['a'] centrosymmetric = False # check if space group has inversion symmetry if(genstr[0] == '1'): t = 'hOOO' mat = SYM_fillgen(t) genmat = np.concatenate((genmat, mat)) centrosymmetric = True n = int(genstr[1]) if(n > 0): for i in range(n): istart = 2 + i * 4 istop = 2 + (i+1) * 4 t = genstr[istart:istop] mat = SYM_fillgen(t) genmat = np.concatenate((genmat, mat)) else: istop = 2 ''' if there is an alternate setting for this space group check if the alternate setting needs to be used ''' if(genstr[istop] != '0'): if(setting != 0): t = genstr[istop+1:istop+4] t = 'a' + t # get the translation without any rotation sym = np.squeeze(SYM_fillgen(t, sgn=-1)) sym2 = np.squeeze(SYM_fillgen(t)) for i in range(1, genmat.shape[0]): generator = np.dot(sym2, np.dot( np.squeeze(genmat[i, :, :]), sym)) frac = np.modf(generator[0:3, 3])[0] frac[frac < 0.] += 1. frac[np.abs(frac) < 1E-5] = 0.0 frac[np.abs(frac-1.0) < 1E-5] = 0.0 generator[0:3, 3] = frac genmat[i, :, :] = generator return genmat, centrosymmetric
[docs]def SYM_fillgen(t, sgn=1): mat = np.zeros([4, 4]) mat[3, 3] = 1. mat[0:3, 0:3] = constants.SYM_GENERATORS[t[0]] mat[0:3, 3] = sgn*np.array([constants.SYM_GENERATORS[t[1]], constants.SYM_GENERATORS[t[2]], constants.SYM_GENERATORS[t[3]] ]) mat = np.broadcast_to(mat, [1, 4, 4]) return mat
[docs]@memoize(maxsize=20) def GenerateSGSym(sgnum, setting=0): ''' get the generators for a space group using the generator string ''' genstr = GeneratorString(sgnum) genmat, centrosymmetric = MakeGenerators(genstr, setting) symmorphic = False if(sgnum in constants.sgnum_symmorphic): symmorphic = True ''' use the generator string to get the rest of the factor group genmat has shape ngenerators x 4 x 4 ''' nsym = genmat.shape[0] SYM_SG = genmat ''' generate the factor group ''' k1 = 0 while k1 < nsym: g1 = np.squeeze(SYM_SG[k1, :, :]) k2 = k1 while k2 < nsym: g2 = np.squeeze(SYM_SG[k2, :, :]) gnew = np.dot(g1, g2) # only fractional parts frac = np.modf(gnew[0:3, 3])[0] frac[frac < 0.] += 1. frac[np.abs(frac) < 1E-5] = 0.0 frac[np.abs(frac-1.0) < 1E-5] = 0.0 gnew[0:3, 3] = frac if(isnew(gnew, SYM_SG)): gnew = np.broadcast_to(gnew, [1, 4, 4]) SYM_SG = np.concatenate((SYM_SG, gnew)) nsym += 1 if (nsym >= 192): k2 = nsym k1 = nsym k2 += 1 k1 += 1 SYM_PG_d = GeneratePGSym(SYM_SG) SYM_PG_d_laue = GeneratePGSym_Laue(SYM_PG_d) for s in SYM_PG_d: if(np.allclose(-np.eye(3), s)): centrosymmetric = True return SYM_SG, SYM_PG_d, SYM_PG_d_laue, centrosymmetric, symmorphic
[docs]def GeneratePGSym(SYM_SG): ''' calculate the direct space point group symmetries from the space group symmetry. the direct point group symmetries are merely the space group symmetries with zero translation part. The reciprocal ones are calculated from the direct symmetries by using the metric tensors, but that is done in the unitcell class ''' nsgsym = SYM_SG.shape[0] # first fill the identity rotation SYM_PG_d = SYM_SG[0, 0:3, 0:3] SYM_PG_d = np.broadcast_to(SYM_PG_d, [1, 3, 3]) for i in range(1, nsgsym): g = SYM_SG[i, :, :] t = g[0:3, 3] g = g[0:3, 0:3] if(isnew(g, SYM_PG_d)): g = np.broadcast_to(g, [1, 3, 3]) SYM_PG_d = np.concatenate((SYM_PG_d, g)) return SYM_PG_d.astype(np.int32)
[docs]def GeneratePGSym_Laue(SYM_PG_d): ''' generate the laue group symmetry for the given set of point group symmetry matrices. this function just adds the inversion symmetry and goes through the group action to generate the entire laue group for the direct point point group matrices ''' ''' first check if the group already has the inversion symmetry ''' for s in SYM_PG_d: if(np.allclose(s, -np.eye(3))): return SYM_PG_d ''' if we get here, then the inversion symmetry is not present add the inversion symmetry ''' SYM_PG_d_laue = SYM_PG_d g = np.broadcast_to(-np.eye(3).astype(np.int32), [1, 3, 3]) SYM_PG_d_laue = np.concatenate((SYM_PG_d_laue, g)) ''' now go through the group actions and see if its a new matrix if it is then add it to the group ''' nsym = SYM_PG_d_laue.shape[0] k1 = 0 while k1 < nsym: g1 = np.squeeze(SYM_PG_d_laue[k1, :, :]) k2 = k1 while k2 < nsym: g2 = np.squeeze(SYM_PG_d_laue[k2, :, :]) gnew = np.dot(g1, g2) if(isnew(gnew, SYM_PG_d_laue)): gnew = np.broadcast_to(gnew, [1, 3, 3]) SYM_PG_d_laue = np.concatenate((SYM_PG_d_laue, gnew)) nsym += 1 if (nsym >= 48): k2 = nsym k1 = nsym k2 += 1 k1 += 1 return SYM_PG_d_laue
[docs]@njit(cache=True, nogil=True) def isnew(mat, sym_mats): for g in sym_mats: diff = np.sum(np.abs(mat - g)) if diff < 1e-5: return False return True
[docs]def latticeType(sgnum): if(sgnum <= 2): return 'triclinic' elif(sgnum > 2 and sgnum <= 15): return 'monoclinic' elif(sgnum > 15 and sgnum <= 74): return 'orthorhombic' elif(sgnum > 74 and sgnum <= 142): return 'tetragonal' elif(sgnum > 142 and sgnum <= 167): return 'trigonal' elif(sgnum > 167 and sgnum <= 194): return 'hexagonal' elif(sgnum > 194 and sgnum <= 230): return 'cubic' else: raise RuntimeError('symmetry.latticeType: unknown space group number')
[docs]def MakeGenerators_PGSYM(pggenstr): ''' @AUTHOR Saransh Singh, Lawrence Livermore National Lab, saransh1@llnl.gov @DATE 11/23/2020 SS 1.0 original @DETAIL. these are the supporting routine to generate the ppint group symmetry for any point group. this is needed for the coloring routines ''' ngen = int(pggenstr[0]) SYM_GEN_PG = np.zeros([ngen, 3, 3]) for i in range(ngen): s = pggenstr[i+1] SYM_GEN_PG[i, :, :] = constants.SYM_GENERATORS[s] return SYM_GEN_PG
[docs]def GeneratePGSYM(pgsym): ''' @AUTHOR Saransh Singh, Lawrence Livermore National Lab, saransh1@llnl.gov @DATE 11/23/2020 SS 1.0 original @DETAIL. generate the point group symmetry given the point group symbol ''' pggenstr = constants.SYM_GL_PG[pgsym] SYM_GEN_PG = MakeGenerators_PGSYM(pggenstr) ''' generate the powers of the group ''' ''' now go through the group actions and see if its a new matrix if it is then add it to the group ''' nsym = SYM_GEN_PG.shape[0] k1 = 0 while k1 < nsym: g1 = np.squeeze(SYM_GEN_PG[k1, :, :]) k2 = k1 while k2 < nsym: g2 = np.squeeze(SYM_GEN_PG[k2, :, :]) gnew = np.dot(g1, g2) if(isnew(gnew, SYM_GEN_PG)): gnew = np.broadcast_to(gnew, [1, 3, 3]) SYM_GEN_PG = np.concatenate((SYM_GEN_PG, gnew)) nsym += 1 if (nsym >= 48): k2 = nsym k1 = nsym k2 += 1 k1 += 1 SYM_GEN_PG[np.abs(SYM_GEN_PG) < eps] = 0. return SYM_GEN_PG