Source code for ase.utils.xrdebye

"""Definition of the XrDebye class.

This module defines the XrDebye class for calculation
of X-ray scattering properties from atomic cluster
using Debye formula.
Also contains routine for calculation of atomic form factors and
X-ray wavelength dict.
"""

from math import acos, cos, exp, pi, sin, sqrt

import numpy as np

from ase.data import atomic_numbers

# Table (1) of
# D. WAASMAIER AND A. KIRFEL, Acta Cryst. (1995). A51, 416-431
waasmaier = {
    # a1 b1 a2 b2 a3 b3 a4 b4 a5 b5 c
    'C': [
        2.657506,
        14.780758,
        1.078079,
        0.776775,
        1.490909,
        42.086843,
        -4.241070,
        -0.000294,
        0.713791,
        0.239535,
        4.297983,
    ],
    'N': [
        11.893780,
        0.000158,
        3.277479,
        10.232723,
        1.858092,
        30.344690,
        0.858927,
        0.656065,
        0.912985,
        0.217287,
        -11.804902,
    ],
    'O': [
        2.960427,
        14.182259,
        2.5088111,
        5.936858,
        0.637053,
        0.112726,
        0.722838,
        34.958481,
        1.142756,
        0.390240,
        0.027014,
    ],
    'P': [
        1.950541,
        0.908139,
        4.146930,
        27.044953,
        1.494560,
        0.071280,
        1.522042,
        67.520190,
        5.729711,
        1.981173,
        0.155233,
    ],
    'S': [
        6.372157,
        1.514347,
        5.154568,
        22.092528,
        1.473732,
        0.061373,
        1.635073,
        55.445176,
        1.209372,
        0.646925,
        0.154722,
    ],
    'Cl': [
        1.446071,
        0.052357,
        6.870609,
        1.193165,
        6.151801,
        18.343416,
        1.750347,
        46.398394,
        0.634168,
        0.401005,
        0.146773,
    ],
    'Ni': [
        13.521865,
        4.077277,
        6.947285,
        0.286763,
        3.866028,
        14.622634,
        2.135900,
        71.966078,
        4.284731,
        0.004437,
        -2.762697,
    ],
    'Cu': [
        14.014192,
        3.738280,
        4.784577,
        0.003744,
        5.056806,
        13.034982,
        1.457971,
        72.554793,
        6.932996,
        0.265666,
        -3.774477,
    ],
    'Pd': [
        6.121511,
        0.062549,
        4.784063,
        0.784031,
        16.631683,
        8.751391,
        4.318258,
        34.489983,
        13.246773,
        0.784031,
        0.883099,
    ],
    'Ag': [
        6.073874,
        0.055333,
        17.155437,
        7.896512,
        4.173344,
        28.443739,
        0.852238,
        110.376108,
        17.988685,
        0.716809,
        0.756603,
    ],
    'Pt': [
        31.273891,
        1.316992,
        18.445441,
        8.797154,
        17.063745,
        0.124741,
        5.555933,
        40.177994,
        1.575270,
        1.316997,
        4.050394,
    ],
    'Au': [
        16.777389,
        0.122737,
        19.317156,
        8.621570,
        32.979682,
        1.256902,
        5.595453,
        38.008821,
        10.576854,
        0.000601,
        -6.279078,
    ],
}

wavelengths = {
    'CuKa1': 1.5405981,
    'CuKa2': 1.54443,
    'CuKb1': 1.39225,
    'WLa1': 1.47642,
    'WLa2': 1.48748,
}


[docs] class XrDebye: """ Class for calculation of XRD or SAXS patterns. """ def __init__( self, atoms, wavelength, damping=0.04, method='Iwasa', alpha=1.01, warn=True, ): """ Initilize the calculation of X-ray diffraction patterns Parameters ---------- atoms: ase.Atoms atoms object for which calculation will be performed. wavelength: float, Angstrom X-ray wavelength in Angstrom. Used for XRD and to setup dumpings. damping : float, Angstrom**2 thermal damping factor parameter (B-factor). method: {'Iwasa'} method of calculation (damping and atomic factors affected). If set to 'Iwasa' than angular damping and q-dependence of atomic factors are used. For any other string there will be only thermal damping and constant atomic factors (`f_a(q) = Z_a`). alpha: float parameter for angular damping of scattering intensity. Close to 1.0 for unplorized beam. warn: boolean flag to show warning if atomic factor can't be calculated """ self.wavelength = wavelength self.damping = damping self.mode = '' self.method = method self.alpha = alpha self.warn = warn self.twotheta_list = [] self.q_list = [] self.intensity_list = [] self.atoms = atoms # TODO: setup atomic form factors if method != 'Iwasa'
[docs] def set_damping(self, damping): """set B-factor for thermal damping""" self.damping = damping
[docs] def get(self, s): r"""Get the powder x-ray (XRD) scattering intensity using the Debye-Formula at single point. Parameters ---------- s: float, in inverse Angstrom scattering vector value (`s = q / 2\pi`). Returns ------- Intensity at given scattering vector `s`. """ pre = exp(-self.damping * s**2 / 2) if self.method == 'Iwasa': sinth = self.wavelength * s / 2.0 positive = 1.0 - sinth**2 if positive < 0: positive = 0 costh = sqrt(positive) cos2th = cos(2.0 * acos(costh)) pre *= costh / (1.0 + self.alpha * cos2th**2) f = {} def atomic(symbol): """ get atomic factor, using cache. """ if symbol not in f: if self.method == 'Iwasa': f[symbol] = self.get_waasmaier(symbol, s) else: f[symbol] = atomic_numbers[symbol] return f[symbol] I = 0.0 fa = [] # atomic factors list for a in self.atoms: fa.append(atomic(a.symbol)) pos = self.atoms.get_positions() # positions of atoms fa = np.array(fa) # atomic factors array for i in range(len(self.atoms)): vr = pos - pos[i] I += np.sum( fa[i] * fa * np.sinc(2 * s * np.sqrt(np.sum(vr * vr, axis=1))) ) return pre * I
[docs] def get_waasmaier(self, symbol, s): r"""Scattering factor for free atoms. Parameters ---------- symbol: string atom element symbol. s: float, in inverse Angstrom scattering vector value (`s = q / 2\pi`). Returns ------- Intensity at given scattering vector `s`. Note: for hydrogen will be returned zero value.""" if symbol == 'H': # XXXX implement analytical H return 0 elif symbol in waasmaier: abc = waasmaier[symbol] f = abc[10] s2 = s * s for i in range(5): f += abc[2 * i] * exp(-abc[2 * i + 1] * s2) return f if self.warn: print('<xrdebye::get_atomic> Element', symbol, 'not available') return 0
[docs] def calc_pattern(self, x=None, mode='XRD', verbose=False): r""" Calculate X-ray diffraction pattern or small angle X-ray scattering pattern. Parameters ---------- x: float array points where intensity will be calculated. XRD - 2theta values, in degrees; SAXS - q values in 1/A (`q = 2 \pi \cdot s = 4 \pi \sin( \theta) / \lambda`). If ``x`` is ``None`` then default values will be used. mode: {'XRD', 'SAXS'} the mode of calculation: X-ray diffraction (XRD) or small-angle scattering (SAXS). Returns ------- list of intensities calculated for values given in ``x``. """ self.mode = mode.upper() assert mode in ['XRD', 'SAXS'] result = [] if mode == 'XRD': if x is None: self.twotheta_list = np.linspace(15, 55, 100) else: self.twotheta_list = x self.q_list = [] if verbose: print('#2theta\tIntensity') for twotheta in self.twotheta_list: s = 2 * sin(twotheta * pi / 180 / 2.0) / self.wavelength result.append(self.get(s)) if verbose: print(f'{twotheta:.3f}\t{result[-1]:f}') elif mode == 'SAXS': if x is None: self.twotheta_list = np.logspace(-3, -0.3, 100) else: self.q_list = x self.twotheta_list = [] if verbose: print('#q\tIntensity') for q in self.q_list: s = q / (2 * pi) result.append(self.get(s)) if verbose: print(f'{q:.4f}\t{result[-1]:f}') self.intensity_list = np.array(result) return self.intensity_list
[docs] def write_pattern(self, filename): """Save calculated data to file specified by ``filename`` string.""" with open(filename, 'w') as fd: self._write_pattern(fd)
def _write_pattern(self, fd): fd.write('# Wavelength = %f\n' % self.wavelength) if self.mode == 'XRD': x, y = self.twotheta_list, self.intensity_list fd.write('# 2theta \t Intesity\n') elif self.mode == 'SAXS': x, y = self.q_list, self.intensity_list fd.write('# q(1/A)\tIntesity\n') else: raise Exception('No data available, call calc_pattern() first.') for i in range(len(x)): fd.write(f' {x[i]:f}\t{y[i]:f}\n')
[docs] def plot_pattern(self, filename=None, show=False, ax=None): """Plot XRD or SAXS depending on filled data Uses Matplotlib to plot pattern. Use *show=True* to show the figure and *filename='abc.png'* or *filename='abc.eps'* to save the figure to a file. Returns ------- ``matplotlib.axes.Axes`` object.""" import matplotlib.pyplot as plt if ax is None: plt.clf() # clear figure ax = plt.gca() if self.mode == 'XRD': x, y = np.array(self.twotheta_list), np.array(self.intensity_list) ax.plot(x, y / np.max(y), '.-') ax.set_xlabel('2$\\theta$') ax.set_ylabel('Intensity') elif self.mode == 'SAXS': x, y = np.array(self.q_list), np.array(self.intensity_list) ax.loglog(x, y / np.max(y), '.-') ax.set_xlabel('q, 1/Angstr.') ax.set_ylabel('Intensity') else: raise Exception('No data available, call calc_pattern() first') if show: plt.show() if filename is not None: fig = ax.get_figure() fig.savefig(filename) return ax