def pair_scattervec_plane(self): """pair the recorded scattering vectors and the indexation results""" old_scatter_vec = np.array(self.scatter_vec) if self.peak.shape[0] < old_scatter_vec.shape[0]: old_peaks = np.zeros((2, self.scatter_vec.shape[1])) else: old_peaks = np.array(self.peak) new_scatter_vec = np.zeros(self.plane.shape) new_peak = np.zeros((2, self.plane.shape[1])) qs = normalize( old_scatter_vec, axis=0) # normalize each scatter vector (column stacked) q0 = normalize(np.dot(self.recip_base, self.plane), axis=0) for i in range(self.plane.shape[1]): angular_diff = np.absolute(1.0 - np.dot(q0[:, i].T, qs)) # pair q0 and qs with the smallest angular difference idx = np.argmin(angular_diff) new_scatter_vec[:, i] = old_scatter_vec[:, idx] new_peak[:, i] = old_peaks[:, idx] # remove the paired entry qs = np.delete(qs, idx, axis=1) old_scatter_vec = np.delete(old_scatter_vec, idx, axis=1) old_peaks = np.delete(old_peaks, idx, axis=1) # update scatter vectors self.scatter_vec = new_scatter_vec self.peak = new_peak return None
def eulers(self): """ Calculate the Bunge Euler angle representation""" astar = self.recip_base[:, 0] bstar = self.recip_base[:, 1] cstar = self.recip_base[:, 2] # calcualte the real base c = normalize(np.cross(astar, bstar)) a = normalize(np.cross(bstar, cstar)) b = normalize(np.cross(c, a)) # get the rotation matrix representation r = np.column_stack((a, b, c)) return OrientationMatrix(r.T).toEulers()
def calc_visible_peaks( q_xtal, # orientation (quanternion) k0, # incident beam (lab) n_detector, # detector normal (lab) E_xray, # in KeV lattice_constants, # in nm diffractable_hkls, # list of potential HKLs detector_angularRange=22, # in degrees, ): """ Return a sorted list of hkls that is theoretically visible on the detector with given k0 and detector normal. """ # calculate wave length range based on given energy (in A) # https://en.wikibooks.org/wiki/Xray_Crystallography/Foundations # https://en.wikipedia.org/wiki/Photon_energy wavenumber_low, wavenumber_high = np.sort(np.array(E_xray) / 12.398) # in 1/A detectable = np.cos(np.radians(detector_angularRange)) # expanding lattice constants a, b, c, alpha, beta, gamma = lattice_constants recipUnit = 1.0 / (a * 10.0) # nm -> 1/A # rotate k0 and n_detector to xtal frame (easy to workwith) k0 = q_xtal * k0 n_detector = q_xtal * n_detector # go over each hkl to see if it hits the detector visible_peaks = [] for i, hkl in enumerate(diffractable_hkls): recip_hkl = hkl * recipUnit # distance to the large Ewald sphere center dist2LEWcntr = np.linalg.norm(recip_hkl - wavenumber_high * (-k0)) # distance to the small Ewald sphere center dist2SEWcntr = np.linalg.norm(recip_hkl - wavenumber_low * (-k0)) # test if within Ewald spheres if dist2LEWcntr <= wavenumber_high and dist2SEWcntr >= wavenumber_low: # test if it hits detector n_hkl = recip_hkl / np.linalg.norm(recip_hkl) # quick hack for cubic materials wavenumber_hkl = np.linalg.norm(recip_hkl) / 2.0 / np.dot( -k0, n_hkl) k = wavenumber_hkl * k0 + recip_hkl n_k = k / np.linalg.norm(k) if np.dot(n_k, n_detector) > detectable: visible_peaks.append(hkl) thePeaks = [] # use collinearity to prune duplicated peaks visible_peaks.sort(key=lambda x: np.dot(x, x), reverse=True) for i, hkl in enumerate(visible_peaks): hasDup = False for j, duplicate in enumerate(visible_peaks[i + 1:]): hasDup = abs( 1.0 - abs(np.dot(normalize(hkl), normalize(duplicate)))) < 1e-4 if hasDup: break if not hasDup: thePeaks.append(hkl) # sorted to make the low index one at the head of the list thePeaks.sort(key=lambda x: np.dot(x, x)) return thePeaks
def grad_student(jobConfig): """The poor grad student who will perform the DAXM experiment""" n_voxels = int(jobConfig["n_voxels"]) hkls = pd.read_csv(jobConfig["DAXMConfig"]["hkllist"], sep='\t')[['h', 'k', 'l']].as_matrix() # extract config for each part DAXMConfig = jobConfig["DAXMConfig"] labConfig = jobConfig["labConfig"] StrainCalcConfig = jobConfig["StrainCalcConfig"] # setup virtual DAXM lab k0 = np.array(labConfig['k0']) # incident beam direction n_CCD = np.array(labConfig['n_CCD']) # detector normal E_xray = np.array(labConfig["X-ray Energy(KeV)"]) # in KeV detector_angularRange = float( labConfig["detector angular range"]) # in degrees # material: Al # NOTE: the shortcut used in the visible peak calculation requires a cubic material lc_AL = np.array([0.4050, 0.4050, 0.4050, 90, 90, 90]) # [nm,nm,nm,degree,degree,degree] # write header for the output file if ARGS["--new"]: with open(jobConfig["dataFileName"], 'w') as f: headerList = [ "voxelName", "angR", "magU", "n_visible", "n_fullq", "peakNoiseSigma", ] headerList += ["{}_F0".format(i + 1) for i in range(9)] # target F headerList += ["{}_FL2".format(i + 1) for i in range(9)] # least-squares guess headerList += ["{}_Fopt".format(i + 1) for i in range(9)] headerList += ["opt_successful", "opt_fun", "opt_nfev"] f.write("\t".join(headerList) + "\n") # convert the parameter range to log space _angR_lb, _angR_ub = map(np.log10, DAXMConfig["angR range"]) _magU_lb, _magU_ub = map(np.log10, DAXMConfig["magU range"]) for i in range(n_voxels): # _DAXMConfig is a per Voxel configuration _DAXMConfig = deepcopy(DAXMConfig) # generate visible peaks (planes, hkls) ## get n_visiblePeaks and n_fullQ: N(n_indexedPeaks) >= n(n_fullQ) unacceptable = True while unacceptable: n_indexedPeaks = int( np.random.choice(DAXMConfig["n_indexedPeaks"], 1)) n_fullQ = int(np.random.choice(DAXMConfig["n_fullQ"], 1)) if n_fullQ <= n_indexedPeaks: unacceptable = False ## get list of index planes based on a random crystal orientation unacceptable = True while unacceptable: xtal_orientation = Quaternion.fromRandom() visible_hkls = calc_visible_peaks( xtal_orientation, k0, n_CCD, E_xray, lc_AL, hkls, detector_angularRange=detector_angularRange, ) if len(visible_hkls) > n_indexedPeaks: indexed_plane = random_select_npeaks(visible_hkls, n_indexedPeaks) # prevent degenerated matrix due to parallel hkls if np.linalg.matrix_rank(indexed_plane) >= 3: unacceptable = False ## get scattering vectors # NOTE: # Since the crystal orientation is only useful for selecting diffractable planes, # the rest of the calculation (q and q0) can be directly done within xtal lattice # frame (XTL) regardless of the actual crystal orientation (makes the calcualtion # a lot simpler). ### frist, randomly generate a deformation gradient in xtal frame _DAXMConfig["angR"] = np.power( 10, np.random.random() * (_angR_ub - _angR_lb) + _angR_lb) _DAXMConfig["magU"] = np.power( 10, np.random.random() * (_magU_ub - _magU_lb) + _magU_lb) defgrad = make_random_defgrad(_DAXMConfig['angR'], _DAXMConfig['magU']) ### use the randomly generated defgrad to strain the scattering vectors recip_base = get_reciprocal_base(lc_AL) T, Bstar = defgrad, recip_base Tstar = np.transpose(np.linalg.inv(T)) Bstar_strained = np.dot(Tstar, Bstar) qs = np.zeros((3, n_indexedPeaks)) # random select n_fullQ to be full scattering vectors idx_fullq = np.random.choice(n_indexedPeaks, n_fullQ, replace=False) for arrayidx, milleridx in enumerate(indexed_plane): q_strained = np.dot(Bstar_strained, milleridx) qs[:, arrayidx] = q_strained if arrayidx in idx_fullq else normalize( q_strained) # setup DAXMConfig for each run # NOTE: # Noise analysis can be done when we have more access to the facility _DAXMConfig["whiteNoiseLevel"] = np.random.choice( np.array(DAXMConfig["peakPositionUncertainty/deg"])) qs_correct = np.copy(qs) for qsIdx in range(qs.shape[1]): qs[:, qsIdx] = perturb_vector( qs[:, qsIdx], np.random.normal(scale=_DAXMConfig["whiteNoiseLevel"]), ) # construct the voxel thisVoxel = DAXMvoxel( name="voxel_{}".format(i), ref_frame='XTL', coords=np.zeros(3), pattern_image="voxel_{}".format(i), scatter_vec=qs, plane=indexed_plane.T, recip_base=recip_base, peak=np.random.random((2, n_indexedPeaks)), depth=0, lattice_constant=lc_AL, ) # save the voxel to H5 archive thisVoxel.write(h5file=jobConfig["voxelArchive"]) # calculate the deformation gradient # NOTE: # notice the much cleaner and simpler interface in terms of strain quantification defgrad_L2 = thisVoxel.deformation_gradientL2() defgrad_opt = thisVoxel.deformation_gradient_opt(eps=1e0) # export data to file for analysis data = [ thisVoxel.name, _DAXMConfig["angR"], _DAXMConfig["magU"], n_indexedPeaks, n_fullQ, _DAXMConfig["whiteNoiseLevel"], ] data += list(defgrad.flatten()) # in XTAL frame!!! data += list(defgrad_L2.flatten()) # in XTAL frame!!! data += list(defgrad_opt.flatten()) # in XTAL frame!!! data += [ int(thisVoxel.opt_rst.success), # is optimization successful thisVoxel.opt_rst.fun, # fitness function final val thisVoxel.opt_rst.nfev, # number of iteration used ] with open(jobConfig["dataFileName"], 'a') as f: f.write("\t".join(map(str, data)) + "\n") # interactive monitoring if jobConfig["monitor"] and i % 1000 == 0: sys.stderr.write("|") elif jobConfig["monitor"] and i % 100 == 0: sys.stderr.write("*")