Example #1
0
    def dSpacing(self):
        """
        Returns the lattice spacing d in A.
        :return: Lattice spacing.
        """

        # Retrieve lattice spacing d from xraylib in Angstrom.
        d_spacing = xraylib.Crystal_dSpacing(self._crystal, self.millerH(),
                                             self.millerK(), self.millerL())

        return d_spacing
Example #2
0
def calc_rocking_curve_shift(crystal='Si',
                             energy=8,
                             h=1,
                             k=1,
                             l=1,
                             rel_angle=1,
                             debye_temp_factor=1):
    '''
    Calculates the angular shift of the Rocking Curve. It uses xraylib.
    
    Parameters:
        
    - crystal: crystal material. [str]
    - energy: energy in keV. [float]
    - h, k, l: Miller indexes. [int]
    - rel_angle: relative angle [float] 
    - debye_temp_factor: Debye Temperature Factor. [float]
    
    Returns:

    - w0: Angular shift of the Rocking Curve in rad. [float]
        
    References:
        
    Elements of modern X-ray physics / Jens Als-Nielsen, Des McMorrow – 2nd ed. Cap. 6.
        
    '''

    # Function:

    def calc_g(d, r0, Vc, F):
        return abs((2 * d * d * r0 / (Vc)) * F)

    # Calculating rocking curve shift:

    cryst = xraylib.Crystal_GetCrystal(crystal)

    bragg = xraylib.Bragg_angle(cryst, energy, h, k, l)

    r0 = physical_constants['classical electron radius'][0]

    F0 = xraylib.Crystal_F_H_StructureFactor(cryst, energy, 0, 0, 0,
                                             debye_temp_factor, rel_angle)

    d = 1e-10 * xraylib.Crystal_dSpacing(cryst, h, k, l)

    V = 1e-30 * cryst['volume']

    g0 = calc_g(d, r0, V, F0)

    w0 = (g0 / (np.pi)) * np.tan(bragg)

    return w0
Example #3
0
    def test_crystal_diffraction(self):
        crystals_list = xraylib.Crystal_GetCrystalsList()
        self.assertEqual(len(crystals_list), 38)
        for crystal_name in crystals_list:
            cs = xraylib.Crystal_GetCrystal(crystal_name)
            self.assertEqual(crystal_name, cs['name'])

        with self.assertRaises(ValueError):
            cs = xraylib.Crystal_GetCrystal(None)

        with self.assertRaises(ValueError):
            cs = xraylib.Crystal_GetCrystal("non-existent-crystal")

        cs = xraylib.Crystal_GetCrystal("Diamond") 

        cs_copy = xraylib.Crystal_MakeCopy(cs)

        with self.assertRaises(ValueError):
            xraylib.Crystal_AddCrystal(cs)

        with self.assertRaises(ValueError):
            xraylib.Crystal_AddCrystal(cs_copy)

        cs_copy['name'] = "Diamond-copy"
        xraylib.Crystal_AddCrystal(cs_copy)

        cs_copy['name'] = 20012016
        with self.assertRaises(TypeError):
            xraylib.Crystal_AddCrystal(cs_copy)

        cs_copy['name'] = "Diamond-copy"

        cs_copy['atom'] = list()
        with self.assertRaises(TypeError):
            xraylib.Crystal_AddCrystal(cs_copy)

        cs_copy['atom'] = (25, "jkewjfpwejffj", None, )
        with self.assertRaises(TypeError):
            xraylib.Crystal_AddCrystal(cs_copy)

        del cs_copy['atom']

        with self.assertRaises(KeyError):
            xraylib.Crystal_AddCrystal(cs_copy)

        crystals_list = xraylib.Crystal_GetCrystalsList()
        self.assertEqual(len(crystals_list), 39)

        for crystal_name in crystals_list:
            cs = xraylib.Crystal_GetCrystal(crystal_name)
            self.assertEqual(crystal_name, cs['name'])

        current_ncrystals = len(crystals_list)

        for i in range(xraylib.CRYSTALARRAY_MAX):
            cs_copy = xraylib.Crystal_MakeCopy(cs)
            cs_copy['name'] = "Diamond copy {}".format(i)
            if current_ncrystals < xraylib.CRYSTALARRAY_MAX:
                xraylib.Crystal_AddCrystal(cs_copy)
                current_ncrystals = current_ncrystals + 1
                self.assertEqual(len(xraylib.Crystal_GetCrystalsList()), current_ncrystals)
            else:
                with self.assertRaises(RuntimeError):
                    xraylib.Crystal_AddCrystal(cs_copy)
                self.assertEqual(len(xraylib.Crystal_GetCrystalsList()), xraylib.CRYSTALARRAY_MAX)

        cs = xraylib.Crystal_GetCrystal("Diamond") 

        # Bragg angle
        angle = xraylib.Bragg_angle(cs, 10.0, 1, 1, 1)
        self.assertAlmostEqual(angle, 0.3057795845795849)

        with self.assertRaises(TypeError):
            angle = xraylib.Bragg_angle(None, 10.0, 1, 1, 1)

        with self.assertRaises(ValueError):
            angle = xraylib.Bragg_angle(cs, -10.0, 1, 1, 1)

        with self.assertRaises(TypeError):
            angle = xraylib.Bragg_angle(cs, 1, 1, 1)

        
	# Q_scattering_amplitude
        tmp = xraylib.Q_scattering_amplitude(cs, 10.0, 1, 1, 1, math.pi/4.0)
        self.assertAlmostEqual(tmp, 0.19184445408324474)

        tmp = xraylib.Q_scattering_amplitude(cs, 10.0, 0, 0, 0, math.pi/4.0)
        self.assertEqual(tmp, 0.0)

        # Atomic factors
        (f0, f_prime, f_prime2) = xraylib.Atomic_Factors(26, 10.0, 1.0, 10.0)
        self.assertAlmostEqual(f0, 65.15)
        self.assertAlmostEqual(f_prime, -0.22193271025027966)
        self.assertAlmostEqual(f_prime2, 22.420270655080493)

        with self.assertRaises(ValueError):
            (f0, f_prime, f_prime2) = xraylib.Atomic_Factors(-10, 10.0, 1.0, 10.0)

        # unit cell volume
        tmp = xraylib.Crystal_UnitCellVolume(cs)
        self.assertAlmostEqual(tmp, 45.376673902751)

        # crystal dspacing
        tmp = xraylib.Crystal_dSpacing(cs, 1, 1, 1)
        self.assertAlmostEqual(tmp, 2.0592870875248344)

        del cs
Example #4
0
    def new_bragg(cls,
                  DESCRIPTOR="Graphite",
                  H_MILLER_INDEX=0,
                  K_MILLER_INDEX=0,
                  L_MILLER_INDEX=2,
                  TEMPERATURE_FACTOR=1.0,
                  E_MIN=5000.0,
                  E_MAX=15000.0,
                  E_STEP=100.0,
                  SHADOW_FILE="bragg.dat"):
        """
         SHADOW preprocessor for crystals - python+xraylib version

         -"""
        # retrieve physical constants needed
        codata = scipy.constants.codata.physical_constants
        codata_e2_mc2, tmp1, tmp2 = codata["classical electron radius"]
        # or, hard-code them
        # In [179]: print("codata_e2_mc2 = %20.11e \n" % codata_e2_mc2 )
        # codata_e2_mc2 =    2.81794032500e-15

        fileout = SHADOW_FILE
        descriptor = DESCRIPTOR

        hh = int(H_MILLER_INDEX)
        kk = int(K_MILLER_INDEX)
        ll = int(L_MILLER_INDEX)

        temper = float(TEMPERATURE_FACTOR)

        emin = float(E_MIN)
        emax = float(E_MAX)
        estep = float(E_STEP)

        #
        # end input section, start calculations
        #

        f = open(fileout, 'wt')

        cryst = xraylib.Crystal_GetCrystal(descriptor)
        volume = cryst['volume']

        #test crystal data - not needed
        itest = 1
        if itest:
            if (cryst == None):
                sys.exit(1)
            print("  Unit cell dimensions are %f %f %f" %
                  (cryst['a'], cryst['b'], cryst['c']))
            print("  Unit cell angles are %f %f %f" %
                  (cryst['alpha'], cryst['beta'], cryst['gamma']))
            print("  Unit cell volume is %f A^3" % volume)
            print("  Atoms at:")
            print("     Z  fraction    X        Y        Z")
            for i in range(cryst['n_atom']):
                atom = cryst['atom'][i]
                print("    %3i %f %f %f %f" %
                      (atom['Zatom'], atom['fraction'], atom['x'], atom['y'],
                       atom['z']))
            print("  ")

        volume = volume * 1e-8 * 1e-8 * 1e-8  # in cm^3
        #flag ZincBlende
        f.write("%i " % 0)
        #1/V*electronRadius
        f.write("%e " % ((1e0 / volume) * (codata_e2_mc2 * 1e2)))
        #dspacing
        dspacing = xraylib.Crystal_dSpacing(cryst, hh, kk, ll)
        f.write("%e " % (dspacing * 1e-8))
        f.write("\n")
        #Z's
        atom = cryst['atom']
        f.write("%i " % atom[0]["Zatom"])
        f.write("%i " % atom[-1]["Zatom"])
        f.write("%e " % temper)  # temperature parameter
        f.write("\n")

        ga = (1e0+0j) + cmath.exp(1j*cmath.pi*(hh+kk))  \
                                 + cmath.exp(1j*cmath.pi*(hh+ll))  \
                                 + cmath.exp(1j*cmath.pi*(kk+ll))
        gb = ga * cmath.exp(1j * cmath.pi * 0.5 * (hh + kk + ll))
        ga_bar = ga.conjugate()
        gb_bar = gb.conjugate()

        f.write("(%20.11e,%20.11e ) \n" % (ga.real, ga.imag))
        f.write("(%20.11e,%20.11e ) \n" % (ga_bar.real, ga_bar.imag))
        f.write("(%20.11e,%20.11e ) \n" % (gb.real, gb.imag))
        f.write("(%20.11e,%20.11e ) \n" % (gb_bar.real, gb_bar.imag))

        zetas = numpy.array([atom[0]["Zatom"], atom[-1]["Zatom"]])
        for zeta in zetas:
            xx01 = 1e0 / 2e0 / dspacing
            xx00 = xx01 - 0.1
            xx02 = xx01 + 0.1
            yy00 = xraylib.FF_Rayl(int(zeta), xx00)
            yy01 = xraylib.FF_Rayl(int(zeta), xx01)
            yy02 = xraylib.FF_Rayl(int(zeta), xx02)
            xx = numpy.array([xx00, xx01, xx02])
            yy = numpy.array([yy00, yy01, yy02])
            fit = numpy.polyfit(xx, yy, 2)
            #print "zeta: ",zeta
            #print "z,xx,YY: ",zeta,xx,yy
            #print "fit: ",fit[::-1] # reversed coeffs
            #print "fit-tuple: ",(tuple(fit[::-1].tolist())) # reversed coeffs
            #print("fit-tuple: %e %e %e  \n" % (tuple(fit[::-1].tolist())) ) # reversed coeffs
            f.write("%e %e %e  \n" %
                    (tuple(fit[::-1].tolist())))  # reversed coeffs

        npoint = int((emax - emin) / estep + 1)
        f.write(("%i \n") % npoint)
        for i in range(npoint):
            energy = (emin + estep * i)
            f1a = xraylib.Fi(int(zetas[0]), energy * 1e-3)
            f2a = xraylib.Fii(int(zetas[0]), energy * 1e-3)
            f1b = xraylib.Fi(int(zetas[1]), energy * 1e-3)
            f2b = xraylib.Fii(int(zetas[1]), energy * 1e-3)
            out = numpy.array([energy, f1a, abs(f2a), f1b, abs(f2b)])
            f.write(("%20.11e %20.11e %20.11e \n %20.11e %20.11e \n") %
                    (tuple(out.tolist())))

        f.close()
        print("File written to disk: %s" % fileout)
def bragg():
    """
     SHADOW preprocessor for crystals - python+xraylib version

     -""" 
    # retrieve physical constants needed
    codata = scipy.constants.codata.physical_constants
    codata_e2_mc2, tmp1, tmp2 = codata["classical electron radius"]
    # or, hard-code them
    # In [179]: print("codata_e2_mc2 = %20.11e \n" % codata_e2_mc2 )
    # codata_e2_mc2 =    2.81794032500e-15

    print("bragg: SHADOW preprocessor for crystals - python+xraylib version")
    fileout = raw_input("Name of output file : ")
    f = open(fileout, 'wb')

    print(" bragg (python) only works now for ZincBlende Cubic structures. ")
    print(" Valid descriptor are: ")
    print("     Si (alternatiovely Si_NIST, Si2) ")
    print("     Ge")
    print("     Diamond")
    print("     GaAs, GaSb, GaP")
    print("     InAs, InP, InSb")
    print("     SiC")

    descriptor = raw_input("Name of crystal descriptor : ")
    cryst = xraylib.Crystal_GetCrystal(descriptor)

    #test crystal data - not needed
    itest = 1
    if itest: 
        if (cryst == None):
            sys.exit(1)
        print "  Unit cell dimensions are %f %f %f" % (cryst['a'],cryst['b'],cryst['c'])
        print "  Unit cell angles are %f %f %f" % (cryst['alpha'],cryst['beta'],cryst['gamma'])
        volume = cryst['volume']
        print "  Unit cell volume is %f" % volume
        volume = volume*1e-8*1e-8*1e-8 # in cm^3
        print "  Atoms at:"
        print "     Z  fraction    X        Y        Z"
        for i in range(cryst['n_atom']):
            atom =  cryst['atom'][i]
            print "    %3i %f %f %f %f" % (atom['Zatom'], atom['fraction'], atom['x'], atom['y'], atom['z'])
        print "  "

    print("Miller indices of crystal plane of reflection.")
    miller = raw_input("H K L: ")
    miller = miller.split()
    hh = int(miller[0])
    kk = int(miller[1])
    ll = int(miller[2])

    #flag ZincBlende
    f.write( "%i " % 0) 
    #1/V*electronRadius
    f.write( "%e " % ((1e0/volume)*(codata_e2_mc2*1e2)) ) 
    #dspacing
    dspacing = xraylib.Crystal_dSpacing(cryst, hh, kk, ll)
    f.write( "%e " % (dspacing*1e-8) ) 
    f.write( "\n")
    #Z's
    atom =  cryst['atom']
    f.write( "%i " % atom[0]["Zatom"] )
    f.write( "%i " % atom[7]["Zatom"] )
    temper = raw_input("Temperature (Debye-Waller) factor (set 1 for default): ")
    temper = float(temper)
    f.write( "%e " % temper ) # temperature parameter
    f.write( "\n")

    ga = (1e0+0j) + cmath.exp(1j*cmath.pi*(hh+kk))  \
                             + cmath.exp(1j*cmath.pi*(hh+ll))  \
                             + cmath.exp(1j*cmath.pi*(kk+ll))
    gb = ga * cmath.exp(1j*cmath.pi*0.5*(hh+kk+ll))
    ga_bar = ga.conjugate()
    gb_bar = gb.conjugate()


    f.write( "(%20.11e,%20.11e ) \n" % (ga.real, ga.imag) ) 
    f.write( "(%20.11e,%20.11e ) \n" % (ga_bar.real, ga_bar.imag) ) 
    f.write( "(%20.11e,%20.11e ) \n" % (gb.real, gb.imag) ) 
    f.write( "(%20.11e,%20.11e ) \n" % (gb_bar.real, gb_bar.imag) ) 

    zetas = numpy.array([atom[0]["Zatom"],atom[7]["Zatom"]])
    for zeta in zetas:
        xx01 = 1e0/2e0/dspacing
        xx00 = xx01-0.1
        xx02 = xx01+0.1
        yy00= xraylib.FF_Rayl(int(zeta),xx00)
        yy01= xraylib.FF_Rayl(int(zeta),xx01)
        yy02= xraylib.FF_Rayl(int(zeta),xx02)
        xx = numpy.array([xx00,xx01,xx02])
        yy = numpy.array([yy00,yy01,yy02])
        fit = numpy.polyfit(xx,yy,2)
        #print "zeta: ",zeta
        #print "z,xx,YY: ",zeta,xx,yy
        #print "fit: ",fit[::-1] # reversed coeffs
        #print "fit-tuple: ",(tuple(fit[::-1].tolist())) # reversed coeffs
        #print("fit-tuple: %e %e %e  \n" % (tuple(fit[::-1].tolist())) ) # reversed coeffs
        f.write("%e %e %e  \n" % (tuple(fit[::-1].tolist())) ) # reversed coeffs


    emin = raw_input("minimum photon energy (eV): ")
    emin = float(emin)
    emax = raw_input("maximum photon energy (eV): ")
    emax = float(emax)
    estep = raw_input("energy step (eV): ")
    estep = float(estep)

    npoint  = int( (emax - emin)/estep + 1 )
    f.write( ("%i \n") % npoint)
    for i in range(npoint): 
        energy = (emin+estep*i)
        f1a = xraylib.Fi(int(zetas[0]),energy*1e-3)
        f2a = xraylib.Fii(int(zetas[0]),energy*1e-3)
        f1b = xraylib.Fi(int(zetas[1]),energy*1e-3)
        f2b = xraylib.Fii(int(zetas[1]),energy*1e-3)
        out = numpy.array([energy,f1a,abs(f2a),f1b,abs(f2b)])
        f.write( ("%20.11e %20.11e %20.11e \n %20.11e %20.11e \n") % ( tuple(out.tolist()) ) )

    f.close()
    print("File written to disk: %s" % fileout)
    return None
Example #6
0
# define miller indices, distances and photon energy in eV
#
hh = 1
kk = 1
ll = 1
d1 = 3000.0
d2 = 3000.0
alpha = 5.0 * numpy.pi / 180.0 # asymmetry angle in rad
crystal_name = "Si"
photon_energy_ev = 10000.0

#
# get crystal info from xraylib
#
cryst = xraylib.Crystal_GetCrystal(crystal_name)
dspacing = xraylib.Crystal_dSpacing(cryst,hh,kk,ll )

#sin_theta = (tocm/(photon_energy_ev*2.0*dspacing*1e-8));

#rt=2*p*q/(p+q)/sin_theta;
#rs=2*p*q*sin_theta/(p+q);

theta=numpy.arcsin(tocm/(photon_energy_ev*2.0*dspacing*1e-8))
t1 = theta + alpha
t2 = theta - alpha

#calculations

s1 = numpy.sin(t1)
s2 = numpy.sin(t2)
s1_2 = s1*s1
Example #7
0
def calc_Darwin_curve(delta_theta=np.linspace(-0.00015, 0.00015, 5000),
                      crystal='Si',
                      energy=8,
                      h=1,
                      k=1,
                      l=1,
                      rel_angle=1,
                      debye_temp_factor=1,
                      use_correction=True,
                      save_txt=True,
                      save_fig=True,
                      filename_to_save='Darwin_curve'):
    '''
    Calculates the Darwin curve. It does not considers absortion. Valid for s-polarization only.
    
    Parameters:
        
    - delta_theta: array containing values of (Theta - Theta_Bragg) in rad. [array]
    - crystal: crystal material. [str]
    - energy: energy in keV. [float]
    - h, k, l: Miller indexes. [int]
    - rel_angle: relative angle [float] 
    - debye_temp_factor: Debye Temperature Factor. [float]
    - use_correction: if True, considers the corrected Bragg angle due to refraction. [boolean] 
    - save_txt: if True, saves the Darwin Curve in a .txt file. [boolean]
    - save_fig: if True, saves a figure with the Darwin Curve in .png. [boolean]
    - filename_to_save: name to save the figure and the .txt file. [string]
    
    Returns:
    
    - delta_theta: Array containing values of (Theta - Theta_Bragg) in rad. [array] 
    - R: Intensity reflectivity array. [array]
    - zeta_total: Total Darwin width (delta_lambda/lambda). [float]
    - zeta_FWHM: Darwin width FWHM (delta_lambda/lambda). [float]
    - w_total: Total Angular Darwin width (delta_Theta) in rad. [float]
    - w_FWHM: Angular Darwin width FWHM (delta_Theta) in rad. [float]
    - w0: Angular shift of the Rocking Curve in rad. [float]
        
    References:
        
    Elements of modern X-ray physics / Jens Als-Nielsen, Des McMorrow – 2nd ed. Cap. 6.
        
    '''

    # Functions:

    def calc_g(d, r0, Vc, F):
        return abs((2 * d * d * r0 / (Vc)) * F)

    def calc_xc(zeta, g, g0):
        return np.pi * zeta / g - g0 / g

    def calc_zeta_total(d, r0, Vc, F):
        return (4 / np.pi) * (d) * (d) * (r0 * abs(F) / Vc)

    def Darwin_curve(xc):

        R = []

        for x in xc:

            if (x >= 1):

                r = (x - np.sqrt(x * x - 1)) * (x - np.sqrt(x * x - 1))

            if (x <= 1):

                r = 1

            if (x <= -1):

                r = (x + np.sqrt(x * x - 1)) * (x + np.sqrt(x * x - 1))

            R.append(r)

        return np.array(R)

    # Calculating Darwin curve:

    cryst = xraylib.Crystal_GetCrystal(crystal)

    bragg = xraylib.Bragg_angle(cryst, energy, h, k, l)

    zeta = delta_theta / np.tan(bragg)

    r0 = physical_constants['classical electron radius'][0]

    FH = xraylib.Crystal_F_H_StructureFactor(cryst, energy, h, k, l,
                                             debye_temp_factor, rel_angle)

    F0 = xraylib.Crystal_F_H_StructureFactor(cryst, energy, 0, 0, 0,
                                             debye_temp_factor, rel_angle)

    d = 1e-10 * xraylib.Crystal_dSpacing(cryst, h, k, l)

    V = 1e-30 * cryst['volume']

    g = calc_g(d, r0, V, FH)

    g0 = calc_g(d, r0, V, F0)

    xc = calc_xc(zeta, g, g0)

    R = Darwin_curve(xc)

    zeta_total = calc_zeta_total(d, r0, V, FH)

    zeta_FWHM = (3 / (2 * np.sqrt(2))) * zeta_total

    w_total = zeta_total * np.tan(bragg)

    w_FWHM = (3 / (2 * np.sqrt(2))) * w_total

    w0 = (g0 / (np.pi)) * np.tan(bragg)

    # Correcting curve offset (due to refraction):

    if (use_correction):

        delta_theta = delta_theta - w0

    # Saving .txt file:

    if (save_txt):

        filename = filename_to_save + '.txt'

        with open(filename, 'w') as f:

            f.write('#Delta_Theta[rad] Intensity_Reflectivity \n')

            for i in range(len(R)):

                f.write('%.6E\t%.6E \n' % (delta_theta[i], R[i]))

    # Plotting Graph:

    plt.figure()
    plt.plot(delta_theta, R, linewidth=1.8, color='black')
    plt.fill_between(delta_theta, R, alpha=0.9, color='C0')
    plt.ylabel('Intensity reflectivity', fontsize=13)
    plt.xlabel('$\Delta$' + '$\Theta$' + ' [rad]', fontsize=13)
    plt.xscale('linear')
    plt.yscale('linear')
    plt.minorticks_on()
    plt.ticklabel_format(style='sci', axis='x', scilimits=(0, 0))
    plt.tick_params(which='both',
                    axis='both',
                    direction='in',
                    right=True,
                    top=True,
                    labelsize=12)
    plt.grid(which='both', alpha=0.2)
    plt.tight_layout()
    textstr = '\n'.join((r'$\zeta_{total}=$%.4E' % (zeta_total, ),
                         r'$\zeta_{FWHM}=$%.4E' % (zeta_FWHM, ),
                         r'$\omega_{total}=$%.4E rad' % (w_total, ),
                         r'$\omega_{FWHM}=$%.4E rad' % (w_FWHM, )))
    props = dict(boxstyle='round', facecolor='wheat',
                 alpha=0.5)  # wheat # gray
    plt.text(0.05,
             0.95,
             textstr,
             transform=plt.gca().transAxes,
             fontsize=10,
             verticalalignment='top',
             bbox=props)
    plt.show()
    if (save_fig):
        plt.savefig(filename_to_save + '.png', dpi=600)

    return delta_theta, R, zeta_total, zeta_FWHM, w_total, w_FWHM, w0
Example #8
0
def calcTemperatureFactor(temperature,
                          crystal='Si',
                          debyeTemperature=644.92,
                          millerIndex=[1, 1, 1],
                          atomicMass=28.09,
                          dSpacing=3.1354162886330583):
    """
    Calculates the (Debye) temperature factor for single crystals.
    
    Parameters
    ----------
    temperature : float 
        Crystal temperature in Kelvin (positive number).
    crystal : str 
        Crystal single-element symbol (e.g. Si, Ge, ...).   
    debyeTemperature : float 
        Debye temperature of the crystal material in Kelvin.    
    millerIndex : array-like (1D), optional
        Miller indexes of the crystal orientation. For use with xraylib only.
    atomicMass : float, optional. 
        Atomic mass of the crystal element (amu unit). if atomicMass == 0, get from xraylib.
    dSpacing : float, optional. 
        dSpacing in Angstroms, given the crystal and millerIndex . if dSpacing == 0, get from xraylib.
        
    Returns:
    --------
    temperatureFactor : float
    
    Examples:
    ---------
        ### using xraylib:
            
        >>> calcTemperatureFactor(80, crystal='Si', millerIndex=[3,1,1], debyeTemperature=644.92, dSpacing=0, atomicMass=0)
        0.983851994268226
        
        
        ### forcing it to use given dSpacing and atomicMass:
        
        >>> calcTemperatureFactor(80, crystal='Si', millerIndex=[1,1,1], debyeTemperature=644.92, atomicMass=28.09, dSpacing=3.1354163)
        0.9955698950510736
    
    References:
    -----------
    
    [1]: A. Freund, Nucl. Instrum. and Meth. 213 (1983) 495-501
    
    [2]: M. Sanchez del Rio and R. J. Dejus, "Status of XOP: an x-ray optics software toolkit", 
         SPIE proc. vol. 5536 (2004) pp.171-174
        

    """
    def debyeFunc(x):
        return x / (np.exp(-x) - 1)

    def debyePhi(y):
        from scipy.integrate import quad
        integral = quad(lambda x: debyeFunc(x), 0, y)[0]
        return (1 / y) * integral

    planck = 6.62607015e-34  # codata.Planck
    Kb = 1.380649e-23  # codata.Boltzmann
    atomicMassTokg = 1.6605390666e-27  # codata.atomic_mass

    try:
        import xraylib
        h, k, l = millerIndex
        crystalDict = xraylib.Crystal_GetCrystal(crystal)
        if (dSpacing == 0):
            dSpacing = xraylib.Crystal_dSpacing(crystalDict, h, k, l)
        if (atomicMass == 0):
            atomicMass = xraylib.AtomicWeight(
                xraylib.SymbolToAtomicNumber(crystal))

    except:
        print(
            "xraylib not available. Please give dSpacing and atomicMass manually."
        )
        if ((dSpacing == 0) or (atomicMass == 0)):
            return np.nan

    atomicMass *= atomicMassTokg  # converting to [kg]
    dSpacing *= 1e-10  # converting to [m]

    x = debyeTemperature / (-1 * temperature)  # invert temperature sign (!!!)

    B0 = (3 * planck**2) / (2 * Kb * debyeTemperature * atomicMass)

    BT = 4 * B0 * debyePhi(x) / x

    ratio = 1 / (2 * dSpacing)

    M = (B0 + BT) * ratio**2

    temperatureFactor = np.exp(-M)

    return temperatureFactor