import numpy as np
from hexrd.utils.decorators import numba_njit_if_available
from hexrd.sampleOrientations.conversions import cu2ro, ro2qu
from hexrd.sampleOrientations.rfz import insideFZ
from hexrd import constants
if constants.USE_NUMBA:
from numba import prange
else:
prange = range
@numba_njit_if_available(cache=True, nogil=True, parallel=True)
def _sample(pgnum,
N,
delta,
shift,
ap_2):
N3 = (2*N+1)**3
res = np.full((N3, 4), np.nan, dtype=np.float64)
for ii in prange(-N, N+1):
xx = (ii + shift) * delta
for jj in prange(-N, N+1):
yy = (jj + shift) * delta
for kk in prange(-N, N+1):
zz = (kk + shift) * delta
cu = np.array([xx, yy, zz])
ma = np.max(np.abs(cu))
if ma <= ap_2:
ro = cu2ro(cu)
if insideFZ(ro, pgnum):
idx = (ii+N)*(2*N+1)**2 + (jj+N)*(2*N+1) + (kk+N)
res[idx,:] = ro2qu(ro)
return res
[docs]class sampleRFZ:
"""This class samples the rodrigues fundamental zone
of a point group uniformly in the density sense and
returns a list of orientations which are spaced,
on an average, to the user specified angular spacing
@author Saransh Singh, LLNL
@date 11/28/2022 1.0 original
Note
----
Details can be found in:
S. Singh and M. De Graef, "Orientation sampling for
dictionary-based diffraction pattern indexing methods".
MSMSE 24, 085013 (2016)
Attributes
----------
pgnum: int
point group number of crystal
average_angular_spacing" float
average angular spacing of sampling (in degrees)
"""
def __init__(self,
pgnum,
sampling_type='default',
average_angular_spacing=3.0):
"""__init__ method of the sampleRFZ class.
Parameters
----------
pgnum : int
point group number
sampling_type : str
default sampling with origin
special which is shifted to mid-points
of each cubochoric cell
average_angular_spacing : float
average angular spacing in degrees
"""
self.ap_2 = constants.cuA_2
self.pgnum = pgnum
self.sampling_type = sampling_type
self.avg_ang_spacing = average_angular_spacing
[docs] def sampling_N(self):
"""Get the number of sampling steps in the cubochoric
cube based on the average angular spacing requested.
Uses eqns. 9 and 10 of S. Singh and M. De Graef MSMSE 24,
085013 (2016)
"""
if self.sampling_type.lower() == 'default':
return np.rint(131.97049 / (self.avg_ang_spacing - 0.03732)).astype(np.int32)
elif self.sampling_type.lower() == 'special':
return np.rint(125.70471 / (self.avg_ang_spacing - 0.07127)).astype(np.int32)
[docs] def sample(self):
res = _sample(self.pgnum,
self.cubN,
self.delta,
self.shift,
self.ap_2)
mask = ~np.isnan(res[:,0])
res = res[mask,:]
self.orientations = res
[docs] def sample_if_possible(self):
required_attributes = ('pgnum', 'avg_ang_spacing', 'sampling_type')
if not all(hasattr(self, x) for x in required_attributes):
return
self.sample()
@property
def pgnum(self):
return self._pgnum
@pgnum.setter
def pgnum(self, pgn):
self._pgnum = pgn
self.sample_if_possible()
@property
def sampling_type(self):
return self._sampling_type
@sampling_type.setter
def sampling_type(self, stype):
self._sampling_type = stype
self.sample_if_possible()
@property
def avg_ang_spacing(self):
return self._avg_ang_spacing
@avg_ang_spacing.setter
def avg_ang_spacing(self, ang):
self._avg_ang_spacing = ang
self.sample_if_possible()
@property
def cubN(self):
return self.sampling_N()
@property
def shift(self):
if self.sampling_type == 'default':
return 0.0
elif self.sampling_type == 'special':
return 0.5
@property
def delta(self):
return self.ap_2 / self.cubN