def get_pattern(self, structure, scaled=True, two_theta_range=(0, 90)): """ Calculates the diffraction pattern for a structure. Args: structure (Structure): Input structure scaled (bool): Whether to return scaled intensities. The maximum peak is set to a value of 100. Defaults to True. Use False if you need the absolute values to combine XRD plots. two_theta_range ([float of length 2]): Tuple for range of two_thetas to calculate in degrees. Defaults to (0, 90). Set to None if you want all diffracted beams within the limiting sphere of radius 2 / wavelength. Returns: (XRDPattern) """ if self.symprec: finder = SpacegroupAnalyzer(structure, symprec=self.symprec) structure = finder.get_refined_structure() wavelength = self.wavelength latt = structure.lattice is_hex = latt.is_hexagonal() # Obtained from Bragg condition. Note that reciprocal lattice # vector length is 1 / d_hkl. min_r, max_r = (0, 2 / wavelength) if two_theta_range is None else \ [2 * sin(radians(t / 2)) / wavelength for t in two_theta_range] # Obtain crystallographic reciprocal lattice points within range recip_latt = latt.reciprocal_lattice_crystallographic recip_pts = recip_latt.get_points_in_sphere( [[0, 0, 0]], [0, 0, 0], max_r) if min_r: recip_pts = [pt for pt in recip_pts if pt[1] >= min_r] # Create a flattened array of zs, coeffs, fcoords and occus. This is # used to perform vectorized computation of atomic scattering factors # later. Note that these are not necessarily the same size as the # structure as each partially occupied specie occupies its own # position in the flattened array. zs = [] coeffs = [] fcoords = [] occus = [] dwfactors = [] for site in structure: for sp, occu in site.species.items(): zs.append(sp.Z) try: c = ATOMIC_SCATTERING_PARAMS[sp.symbol] except KeyError: raise ValueError("Unable to calculate XRD pattern as " "there is no scattering coefficients for" " %s." % sp.symbol) coeffs.append(c) dwfactors.append(self.debye_waller_factors.get(sp.symbol, 0)) fcoords.append(site.frac_coords) occus.append(occu) zs = np.array(zs) coeffs = np.array(coeffs) fcoords = np.array(fcoords) occus = np.array(occus) dwfactors = np.array(dwfactors) peaks = {} two_thetas = [] for hkl, g_hkl, ind, _ in sorted( recip_pts, key=lambda i: (i[1], -i[0][0], -i[0][1], -i[0][2])): # Force miller indices to be integers. hkl = [int(round(i)) for i in hkl] if g_hkl != 0: d_hkl = 1 / g_hkl # Bragg condition theta = asin(wavelength * g_hkl / 2) # s = sin(theta) / wavelength = 1 / 2d = |ghkl| / 2 (d = # 1/|ghkl|) s = g_hkl / 2 # Store s^2 since we are using it a few times. s2 = s ** 2 # Vectorized computation of g.r for all fractional coords and # hkl. g_dot_r = np.dot(fcoords, np.transpose([hkl])).T[0] # Highly vectorized computation of atomic scattering factors. # Equivalent non-vectorized code is:: # # for site in structure: # el = site.specie # coeff = ATOMIC_SCATTERING_PARAMS[el.symbol] # fs = el.Z - 41.78214 * s2 * sum( # [d[0] * exp(-d[1] * s2) for d in coeff]) fs = zs - 41.78214 * s2 * np.sum( coeffs[:, :, 0] * np.exp(-coeffs[:, :, 1] * s2), axis=1) dw_correction = np.exp(-dwfactors * s2) # Structure factor = sum of atomic scattering factors (with # position factor exp(2j * pi * g.r and occupancies). # Vectorized computation. f_hkl = np.sum(fs * occus * np.exp(2j * pi * g_dot_r) * dw_correction) # Lorentz polarization correction for hkl lorentz_factor = (1 + cos(2 * theta) ** 2) / \ (sin(theta) ** 2 * cos(theta)) # Intensity for hkl is modulus square of structure factor. i_hkl = (f_hkl * f_hkl.conjugate()).real two_theta = degrees(2 * theta) if is_hex: # Use Miller-Bravais indices for hexagonal lattices. hkl = (hkl[0], hkl[1], - hkl[0] - hkl[1], hkl[2]) # Deal with floating point precision issues. ind = np.where(np.abs(np.subtract(two_thetas, two_theta)) < AbstractDiffractionPatternCalculator.TWO_THETA_TOL) if len(ind[0]) > 0: peaks[two_thetas[ind[0][0]]][0] += i_hkl * lorentz_factor peaks[two_thetas[ind[0][0]]][1].append(tuple(hkl)) else: peaks[two_theta] = [i_hkl * lorentz_factor, [tuple(hkl)], d_hkl] two_thetas.append(two_theta) # Scale intensities so that the max intensity is 100. max_intensity = max([v[0] for v in peaks.values()]) x = [] y = [] hkls = [] d_hkls = [] for k in sorted(peaks.keys()): v = peaks[k] fam = get_unique_families(v[1]) if v[0] / max_intensity * 100 > AbstractDiffractionPatternCalculator.SCALED_INTENSITY_TOL: x.append(k) y.append(v[0]) hkls.append([{"hkl": hkl, "multiplicity": mult} for hkl, mult in fam.items()]) d_hkls.append(v[2]) xrd = DiffractionPattern(x, y, hkls, d_hkls) if scaled: xrd.normalize(mode="max", value=100) return xrd
def get_pattern(self, structure, scale_intensity=True, two_theta_range=None): """ Calculates the diffraction pattern for a structure. Args: structure (Structure): Input structure scale_intensity (bool): Whether to return scaled intensities. The maximum peak is set to a value of 100. Defaults to True. Use False if you need the absolute values to combine XRD plots. two_theta_range ([float of length 2]): Tuple for range of two_thetas to calculate in degrees. Defaults to (0, 90). Set to None if you want all diffracted beams within the limiting sphere of radius 2 / wavelength. Returns: XRDPattern, list of features for point cloud representation, recip_latt """ try: assert(self.symprec == 0) except: print('symprec is not zero, terminate the process and check your input..') if self.symprec: finder = SpacegroupAnalyzer(structure, symprec=self.symprec) structure = finder.get_refined_structure() latt = structure.lattice volume = structure.volume is_hex = latt.is_hexagonal() # Obtained from Bragg condition. Note that reciprocal lattice # vector length is 1 / d_hkl. try: assert(two_theta_range == None) except: print('two theta range is not None, terminate the process and check your input..') min_r, max_r = (0., 2. / self.wavelength) if two_theta_range is None else \ [2 * math.sin(math.radians(t / 2)) / self.wavelength for t in two_theta_range] # Obtain crystallographic reciprocal lattice points within range # recip_pts entry: [coord, distance, index, image] recip_latt = latt.reciprocal_lattice_crystallographic recip_pts = recip_latt.get_points_in_sphere( [[0, 0, 0]], [0, 0, 0], max_r) if min_r: recip_pts = [pt for pt in recip_pts if pt[1] >= min_r] # Create a flattened array of zs, coeffs, fcoords and occus. This is # used to perform vectorized computation of atomic scattering factors # later. Note that these are not necessarily the same size as the # structure as each partially occupied specie occupies its own # position in the flattened array. zs = [] coeffs = [] fcoords = [] occus = [] for site in structure: # do not consider mixed species at the same site try: assert(len(site.species.items()) == 1) except: print('mixed species at the same site detected, abort..') for sp, occu in site.species.items(): zs.append(sp.Z) try: c = ATOMIC_SCATTERING_PARAMS[sp.symbol] except KeyError: raise ValueError("Unable to calculate XRD pattern as " "there is no scattering coefficients for" " %s." % sp.symbol) coeffs.append(c) fcoords.append(site.frac_coords) occus.append(occu) zs = np.array(zs) coeffs = np.array(coeffs) fcoords = np.array(fcoords) occus = np.array(occus) try: assert(np.max(occus) == np.min(occus) == 1) # all sites should be singly occupied except: print('check occupancy values...') peaks = {} two_thetas = [] total_electrons = sum(zs) features = [[0, 0, 0, float(total_electrons/volume)**2]] for hkl, g_hkl, _, _ in sorted(recip_pts, key=lambda i: (i[1], -i[0][0], -i[0][1], -i[0][2])): # skip origin and points on the limiting sphere to avoid precision problems if (g_hkl < 1e-4) or (g_hkl > 2./self.wavelength): continue d_hkl = 1. / g_hkl # Bragg condition theta = math.asin(self.wavelength * g_hkl / 2) # s = sin(theta) / wavelength = 1 / 2d = |ghkl| / 2 (d = # 1/|ghkl|) s = g_hkl / 2 # Store s^2 since we are using it a few times. s2 = s ** 2 # Vectorized computation of g.r for all fractional coords and # hkl. Output size is N_atom g_dot_r = np.dot(fcoords, np.transpose([hkl])).T[0] # Highly vectorized computation of atomic scattering factors. # Equivalent non-vectorized code is:: # # for site in structure: # el = site.specie # coeff = ATOMIC_SCATTERING_PARAMS[el.symbol] # fs = el.Z - 41.78214 * s2 * sum( # [d[0] * exp(-d[1] * s2) for d in coeff]) fs = zs - 41.78214 * s2 * np.sum( coeffs[:, :, 0] * np.exp(-coeffs[:, :, 1] * s2), axis=1) # Structure factor = sum of atomic scattering factors (with # position factor exp(2j * pi * g.r and occupancies). # Vectorized computation. f_hkl = np.sum(fs * occus * np.exp(2j * math.pi * g_dot_r)) # Lorentz polarization correction for hkl lorentz_factor = (1 + math.cos(2 * theta) ** 2) / \ (math.sin(theta) ** 2 * math.cos(theta)) # Intensity for hkl is modulus square of structure factor. i_hkl = (f_hkl * f_hkl.conjugate()).real try: assert(i_hkl < total_electrons**2) except: print('assertion failed, check I_hkl values..') i_hkl_out = i_hkl / volume**2 # add to features features.append([hkl[0], hkl[1], hkl[2], i_hkl_out]) ### for diffractin pattern plotting only two_theta = math.degrees(2 * theta) if is_hex: # Use Miller-Bravais indices for hexagonal lattices. hkl = (hkl[0], hkl[1], - hkl[0] - hkl[1], hkl[2]) # Deal with floating point precision issues. ind = np.where(np.abs(np.subtract(two_thetas, two_theta)) < AbstractDiffractionPatternCalculator.TWO_THETA_TOL) if len(ind[0]) > 0: peaks[two_thetas[ind[0][0]]][0] += i_hkl * lorentz_factor peaks[two_thetas[ind[0][0]]][1].append(tuple(hkl)) else: peaks[two_theta] = [i_hkl * lorentz_factor, [tuple(hkl)], d_hkl] two_thetas.append(two_theta) # Scale intensities so that the max intensity is 100. max_intensity = max([v[0] for v in peaks.values()]) x = [] y = [] hkls = [] d_hkls = [] for k in sorted(peaks.keys()): v = peaks[k] fam = get_unique_families(v[1]) if v[0] / max_intensity * 100 > AbstractDiffractionPatternCalculator.SCALED_INTENSITY_TOL: x.append(k) y.append(v[0]) hkls.append([{"hkl": hkl, "multiplicity": mult} for hkl, mult in fam.items()]) d_hkls.append(v[2]) xrd = DiffractionPattern(x, y, hkls, d_hkls) if scale_intensity: xrd.normalize(mode="max", value=100) return xrd, recip_latt.matrix, features
def get_pattern(self, structure, scaled=True, two_theta_range=None): """ Calculates the powder neutron diffraction pattern for a structure. Args: structure (Structure): Input structure scaled (bool): Whether to return scaled intensities. The maximum peak is set to a value of 100. Defaults to True. Use False if you need the absolute values to combine ND plots. two_theta_range ([float of length 2]): Tuple for range of two_thetas to calculate in degrees. Defaults to (0, 90). Set to None if you want all diffracted beams within the limiting sphere of radius 2 / wavelength. Returns: (NDPattern) """ try: assert(self.symprec == 0) except: print('symprec is not zero, terminate the process and check your input..') if self.symprec: finder = SpacegroupAnalyzer(structure, symprec=self.symprec) structure = finder.get_refined_structure() skip = False # in case element does not have neutron scattering length wavelength = self.wavelength latt = structure.lattice volume = structure.volume is_hex = latt.is_hexagonal() # Obtained from Bragg condition. Note that reciprocal lattice # vector length is 1 / d_hkl. try: assert(two_theta_range == None) except: print('two theta range is not None, terminate the process and check your input..') min_r, max_r = ( (0, 2 / wavelength) if two_theta_range is None else [2 * math.sin(radians(t / 2)) / wavelength for t in two_theta_range] ) # Obtain crystallographic reciprocal lattice points within range recip_latt = latt.reciprocal_lattice_crystallographic recip_pts = recip_latt.get_points_in_sphere([[0, 0, 0]], [0, 0, 0], max_r) if min_r: recip_pts = [pt for pt in recip_pts if pt[1] >= min_r] # Create a flattened array of coeffs, fcoords and occus. This is # used to perform vectorized computation of atomic scattering factors # later. Note that these are not necessarily the same size as the # structure as each partially occupied specie occupies its own # position in the flattened array. coeffs = [] fcoords = [] occus = [] dwfactors = [] for site in structure: # do not consider mixed species at the same site try: assert(len(site.species.items()) == 1) except: print('mixed species at the same site detected, abort..') for sp, occu in site.species.items(): try: c = ATOMIC_SCATTERING_LEN[sp.symbol] except KeyError: print("Unable to calculate ND pattern as " "there is no scattering coefficients for" " {}.".format(sp.symbol)) # quick return return None, None, None coeffs.append(c) fcoords.append(site.frac_coords) occus.append(occu) dwfactors.append(self.debye_waller_factors.get(sp.symbol, 0)) coeffs = np.array(coeffs) fcoords = np.array(fcoords) occus = np.array(occus) try: assert(np.max(occus) == np.min(occus) == 1) # all sites should be singly occupied except: print('check occupancy values...') dwfactors = np.array(dwfactors) peaks = {} two_thetas = [] total_coeffs = sum(coeffs) features = [[0, 0, 0, float(total_coeffs/volume)**2]] for hkl, g_hkl, ind, _ in sorted(recip_pts, key=lambda i: (i[1], -i[0][0], -i[0][1], -i[0][2])): # skip origin and points on the limiting sphere to avoid precision problems if (g_hkl < 1e-4) or (g_hkl > 2./self.wavelength): continue # Force miller indices to be integers. hkl = [int(round(i)) for i in hkl] d_hkl = 1 / g_hkl # Bragg condition theta = math.asin(wavelength * g_hkl / 2) # s = sin(theta) / wavelength = 1 / 2d = |ghkl| / 2 (d = # 1/|ghkl|) s = g_hkl / 2 # Calculate Debye-Waller factor dw_correction = np.exp(-dwfactors * (s ** 2)) # Vectorized computation of g.r for all fractional coords and # hkl. g_dot_r = np.dot(fcoords, np.transpose([hkl])).T[0] # Structure factor = sum of atomic scattering factors (with # position factor exp(2j * pi * g.r and occupancies). # Vectorized computation. f_hkl = np.sum( #coeffs * occus * np.exp(2j * math.pi * g_dot_r) * dw_correction coeffs * occus * np.exp(2j * math.pi * g_dot_r) ) # Lorentz polarization correction for hkl lorentz_factor = 1 / (math.sin(theta) ** 2 * math.cos(theta)) # Intensity for hkl is modulus square of structure factor. i_hkl = (f_hkl * f_hkl.conjugate()).real i_hkl_out = i_hkl / volume**2 # add to features features.append([hkl[0], hkl[1], hkl[2], i_hkl_out]) two_theta = math.degrees(2 * theta) if is_hex: # Use Miller-Bravais indices for hexagonal lattices. hkl = (hkl[0], hkl[1], -hkl[0] - hkl[1], hkl[2]) # Deal with floating point precision issues. ind = np.where( np.abs(np.subtract(two_thetas, two_theta)) < self.TWO_THETA_TOL ) if len(ind[0]) > 0: peaks[two_thetas[ind[0][0]]][0] += i_hkl * lorentz_factor peaks[two_thetas[ind[0][0]]][1].append(tuple(hkl)) else: peaks[two_theta] = [i_hkl * lorentz_factor, [tuple(hkl)], d_hkl] two_thetas.append(two_theta) # Scale intensities so that the max intensity is 100. max_intensity = max([v[0] for v in peaks.values()]) x = [] y = [] hkls = [] d_hkls = [] for k in sorted(peaks.keys()): v = peaks[k] fam = get_unique_families(v[1]) if v[0] / max_intensity * 100 > self.SCALED_INTENSITY_TOL: x.append(k) y.append(v[0]) hkls.append( [{"hkl": hkl, "multiplicity": mult} for hkl, mult in fam.items()] ) d_hkls.append(v[2]) nd = DiffractionPattern(x, y, hkls, d_hkls) if scaled: nd.normalize(mode="max", value=100) return nd, recip_latt.matrix, features
def get_pattern(self, structure, scaled=True, two_theta_range=(0, 90)): """ Calculates the diffraction pattern for a structure. Args: structure (Structure): Input structure scaled (bool): Whether to return scaled intensities. The maximum peak is set to a value of 100. Defaults to True. Use False if you need the absolute values to combine XRD plots. two_theta_range ([float of length 2]): Tuple for range of two_thetas to calculate in degrees. Defaults to (0, 90). Set to None if you want all diffracted beams within the limiting sphere of radius 2 / wavelength. Returns: (XRDPattern) """ if self.symprec: finder = SpacegroupAnalyzer(structure, symprec=self.symprec) structure = finder.get_refined_structure() wavelength = self.wavelength latt = structure.lattice is_hex = latt.is_hexagonal() # Obtained from Bragg condition. Note that reciprocal lattice # vector length is 1 / d_hkl. min_r, max_r = ( (0, 2 / wavelength) if two_theta_range is None else [2 * sin(radians(t / 2)) / wavelength for t in two_theta_range] ) # Obtain crystallographic reciprocal lattice points within range recip_latt = latt.reciprocal_lattice_crystallographic recip_pts = recip_latt.get_points_in_sphere([[0, 0, 0]], [0, 0, 0], max_r) if min_r: recip_pts = [pt for pt in recip_pts if pt[1] >= min_r] peaks = {} two_thetas = [] for hkl, g_hkl, ind, _ in sorted( recip_pts, key=lambda i: (i[1], -i[0][0], -i[0][1], -i[0][2]) ): # Force miller indices to be integers. hkl = [int(round(i)) for i in hkl] if g_hkl != 0: d_hkl = 1 / g_hkl # Bragg condition theta = asin(wavelength * g_hkl / 2) two_theta = degrees(2 * theta) if is_hex: # Use Miller-Bravais indices for hexagonal lattices. hkl = (hkl[0], hkl[1], -hkl[0] - hkl[1], hkl[2]) # Deal with floating point precision issues. ind = np.where( np.abs(np.subtract(two_thetas, two_theta)) < AbstractDiffractionPatternCalculator.TWO_THETA_TOL ) if len(ind[0]) > 0: peaks[two_thetas[ind[0][0]]][0] += 1.0 peaks[two_thetas[ind[0][0]]][1].append(tuple(hkl)) else: peaks[two_theta] = [1.0, [tuple(hkl)], d_hkl] two_thetas.append(two_theta) x = [] y = [] hkls = [] d_hkls = [] for k in sorted(peaks.keys()): v = peaks[k] fam = get_unique_families(v[1]) x.append(k) y.append(v[0]) hkls.append( [{"hkl": hkl, "multiplicity": mult} for hkl, mult in fam.items()] ) d_hkls.append(v[2]) xrd = DiffractionPattern(x, y, hkls, d_hkls) return xrd