Skip to main content
`Ctrl`+`K`

# Activity 6: Effect of the temperature

## Surface and bulk effects of the temperature

In this example, we will look at the effects of the temperature on the X-Ray photoelectron diffraction from a copper substrate. We will base our python script on a paper published in 1986 by R. Trehan and S. Fadley. In their work, they performed azimutal scans of a copper(001) surface at 2 different polar angles: one at grazing incidence and one at 45° for incresing temperatures from 298K to roughly 1000K.

For each azimutal scan, they looked at the anisotropy of the signal, that is:

(1)\[\frac{I_{max} - I_{min}}{I_{max}}=\frac{\Delta I}{I_{max}}\]

This value is representative of how clear are the modulations of the signal. They also proposed single scattering calculations that reproduced well these results.

We will reproduce this kind of calculations to introduce the parameters that control the vibrational damping.

See also

based on this paper from R. Trehan and C.S. Fadley Phys. Rev. B 34 p6784–98 (2012)

### The script

Since we want to distinguish between bulk (low polar angles, \(\theta = 45°\) in the paper) and surface effects (large polar angles, \(\theta = 83°\) in the paper), we need to compute scans for different emitters and different depths in the cluster.

The script contains 3 functions:

1. The `create_clusters` function will build clusters with increasing number of planes, with the emitter being in the deepest plane for each cluster.

2. The function `compute` will compute the azimuthal scan for a given cluster.

3. The `analysis` function will sum the intensity from each plane for a given temperature. This will be the substrate total signal (more on this in the Activity 7: Large clusters and path filtering section). The function will also compute the anisotropy (equation (1)).

MsSpec can introduce temperature effects in two ways: either by using the Debye Waller model or by doing an average over the phase shifts. We will explore the latter in this example.

Regardless of the option chosen to model temperature effects, we need to define mean square displacement (MSD) values of the atoms in our cluster. We will use the Debye model for this. The MSD is defined as follows:

(2)\[<U_j^2(T)> = \frac{3\hbar^2 T}{M K_B \Theta_D^2}\]

where \(\hbar\) is the reduce Planck’s constant, \(T\) is the temperature, \(M\) is the mass of the atom, \(k_B\) is the Boltzmann’s constant and \(\Theta_D\) is the Debye temperature of the sample.

To get an idea of the typical order of magnitude for MSD, the figure below plots the values of MSD for copper for temperatures ranging from 300 K to 1000 K.

Fig. 14 Variation of MSD for copper versus temperature using equation (2)

Quiz

With the help of the MsSpec documentation and the second paragraph p6791 of the article cited above, complete the hilighted lines in the following script to compute the anisotropy of Cu(2p) \(\phi\)-scans for polar angle \(\theta\)=45° and 83°.

How is varying the anisotropy versus the temperature. How can you qualitatively explain this variation ?

      1from ase.build import bulk
      2import numpy as np
      3
      4from msspec.calculator import MSSPEC, XRaySource
      5from msspec.utils import hemispherical_cluster, get_atom_index
      6
      7def create_clusters(nplanes=3):
      8    copper  = bulk('Cu', a=3.6, cubic=True)
      9    clusters = []
     10    for emitter_plane in range(nplanes):
     11        cluster = hemispherical_cluster(copper,
     12                                        emitter_plane=emitter_plane,
     13                                        planes=emitter_plane+1,
     14                                        diameter=27,
     15                                        shape='cylindrical')
     16        cluster.absorber = get_atom_index(cluster, 0, 0, 0)
     17        # This is how to store extra information with your cluster
     18        cluster.info.update({
     19            'emitter_plane': emitter_plane,
     20        })
     21        clusters.append(cluster)
     22    return clusters
     23
     24
     25def compute(clusters, all_theta=[45., 83.],
     26            all_T=np.arange(300., 1000., 400.)):
     27    data = None
     28    for cluster in clusters:
     29        # Retrieve emitter's plane from cluster object
     30        plane   = cluster.info['emitter_plane']
     31
     32        calc = MSSPEC(spectroscopy='PED', algorithm='expansion')
     33        calc.source_parameters.energy = XRaySource.AL_KALPHA
     34        calc.muffintin_parameters.interstitial_potential = 14.1
     35
     36        # In simple scattering, it is common practice to use a real potential and
     37        # manually define a mean free path arbitrarily lower than the actual physical
     38        # value in an attempt to reproduce multiple scattering effects.
     39        calc.tmatrix_parameters.exchange_correlation = 'x_alpha_real'
     40        calc.calculation_parameters.mean_free_path = ... # -> half of the mean free
     41                                                         # path (see p6785)
     42        # Parameters for temperature effects
     43        calc.calculation_parameters.vibrational_damping = 'averaged_tl'
     44        calc.calculation_parameters.use_debye_model = ..... # Use the MsSpec help
     45        calc.calculation_parameters.debye_temperature = ... # and p6791 of the paper
     46        calc.calculation_parameters.vibration_scaling = ... # -> How much more do
     47                                                            # surface atoms vibrate
     48                                                            # than bulk atoms?
     49        calc.detector_parameters.average_sampling = 'low'
     50        calc.detector_parameters.angular_acceptance = 5.7
     51
     52        calc.calculation_parameters.scattering_order = 1
     53
     54
     55        for T in all_T:
     56            # Define the sample temperature
     57            calc.calculation_parameters.temperature = T
     58            # Set the atoms and compute an azimuthal scan
     59            calc.set_atoms(cluster)
     60            data = calc.get_phi_scan(level='2p', theta=all_theta,
     61                                        phi=np.linspace(0, 100, 51),
     62                                        kinetic_energy=560, data=data)
     63            # Small changes to add some details in both the title of the dataset
     64            # and the figure
     65            view = data[-1].views[-1]
     66            t = view._plotopts['title'] + f" (plane #{plane:d}, T={T:.0f} K)"
     67            data[-1].title = t
     68            view.set_plot_options(autoscale=True, title=t)
     69        calc.shutdown()
     70    return data
     71
     72
     73def analysis(data, all_theta, all_T, nplanes):
     74    # Sum cross_section for all emitter's plane at a given T
     75    # Compute the anisotropy
     76    results = dict.fromkeys(all_T, [])
     77    anisotropy = []
     78    for dset in data:
     79        # Retrieve temperature
     80        T = float(dset.get_parameter('CalculationParameters', 'temperature')['value'])
     81        # Update the sum in results
     82        if len(results[T]) == 0:
     83            results[T] = dset.cross_section
     84        else:
     85            results[T] += dset.cross_section
     86
     87    anisotropy_dset = data.add_dset("Anisotropies")
     88    anisotropy_dset.add_columns(temperature=all_T)
     89    for theta in all_theta:
     90        col_name = f"theta{theta:.0f}"
     91        col_values = []
     92        i = np.where(dset.theta == theta)[0]
     93        for T in all_T:
     94            cs = results[T][i]
     95            Imax = np.max(cs)
     96            Imin = np.min(cs)
     97            A = (Imax - Imin)/Imax
     98            col_values.append(A)
     99        anisotropy_dset.add_columns(**{col_name:col_values/np.max(col_values)})
    100
    101
    102    anisotropy_view = anisotropy_dset.add_view('Anisotropies',
    103                         title='Relative anisotropies for Cu(2p)',
    104                         marker='o',
    105                         xlabel='T (K)',
    106                         ylabel=r'$\frac{\Delta I / I_{max}(T)}{\Delta I_{300}'
    107                                r'/ I_{max}(300)} (\%)$',
    108                         autoscale=True)
    109    for theta in all_theta:
    110        col_name = f"theta{theta:.0f}"
    111        anisotropy_view.select('temperature', col_name,
    112                                legend=r'$\theta = {:.0f} \degree$'.format(theta))
    113
    114    return data
    115
    116
    117
    118if __name__ == "__main__":
    119    nplanes = 4
    120    all_theta = np.array([45, 83])
    121    all_theta = np.array([300., 1000.])
    122
    123    clusters = create_clusters(nplanes=nplanes)
    124    data = compute(clusters, all_T=all_T, all_theta=all_theta)
    125    data = analysis(data, all_T=all_T, all_theta=all_theta, nplanes=nplanes)
    126    data.view()

      1from ase.build import bulk
      2import numpy as np
      3
      4from msspec.calculator import MSSPEC, XRaySource
      5from msspec.utils import hemispherical_cluster, get_atom_index
      6
      7def create_clusters(nplanes=3):
      8    copper  = bulk('Cu', a=3.6, cubic=True)
      9    clusters = []
     10    for emitter_plane in range(nplanes):
     11        cluster = hemispherical_cluster(copper,
     12                                        emitter_plane=emitter_plane,
     13                                        planes=emitter_plane+1,
     14                                        diameter=27,
     15                                        shape='cylindrical')
     16        cluster.absorber = get_atom_index(cluster, 0, 0, 0)
     17        # This is how to store extra information with your cluster
     18        cluster.info.update({
     19            'emitter_plane': emitter_plane,
     20        })
     21        clusters.append(cluster)
     22    return clusters
     23
     24
     25def compute(clusters, all_theta=[45., 83.],
     26            all_T=np.arange(300., 1000., 400.)):
     27    data = None
     28    for cluster in clusters:
     29        # Retrieve emitter's plane from cluster object
     30        plane   = cluster.info['emitter_plane']
     31
     32        calc = MSSPEC(spectroscopy='PED', algorithm='expansion')
     33        calc.source_parameters.energy = XRaySource.AL_KALPHA
     34        calc.muffintin_parameters.interstitial_potential = 14.1
     35
     36        # In simple scattering, it is common practice to use a real potential and
     37        # manually define a mean free path arbitrarily lower than the actual physical
     38        # value in an attempt to reproduce multiple scattering effects.
     39        calc.tmatrix_parameters.exchange_correlation = 'x_alpha_real'
     40        calc.calculation_parameters.mean_free_path = .5 * 9.1
     41
     42        # Parameters for temperature effects
     43        calc.calculation_parameters.vibrational_damping = 'averaged_tl'
     44        calc.calculation_parameters.use_debye_model = True
     45        calc.calculation_parameters.debye_temperature = 343
     46        calc.calculation_parameters.vibration_scaling = 2.3
     47
     48
     49        calc.detector_parameters.average_sampling = 'low'
     50        calc.detector_parameters.angular_acceptance = 5.7
     51
     52        calc.calculation_parameters.scattering_order = 1
     53
     54
     55        for T in all_T:
     56            # Define the sample temperature
     57            calc.calculation_parameters.temperature = T
     58            # Set the atoms and compute an azimuthal scan
     59            calc.set_atoms(cluster)
     60            data = calc.get_phi_scan(level='2p', theta=all_theta,
     61                                        phi=np.linspace(0, 100, 51),
     62                                        kinetic_energy=560, data=data)
     63            # Small changes to add some details in both the title of the dataset
     64            # and the figure
     65            view = data[-1].views[-1]
     66            t = view._plotopts['title'] + f" (plane #{plane:d}, T={T:.0f} K)"
     67            data[-1].title = t
     68            view.set_plot_options(autoscale=True, title=t)
     69        calc.shutdown()
     70    return data
     71
     72
     73def analysis(data, all_theta, all_T, nplanes):
     74    # Sum cross_section for all emitter's plane at a given T
     75    # Compute the anisotropy
     76    results = dict.fromkeys(all_T, [])
     77    anisotropy = []
     78    for dset in data:
     79        # Retrieve temperature
     80        T = float(dset.get_parameter('CalculationParameters', 'temperature')['value'])
     81        # Update the sum in results
     82        if len(results[T]) == 0:
     83            results[T] = dset.cross_section
     84        else:
     85            results[T] += dset.cross_section
     86
     87    anisotropy_dset = data.add_dset("Anisotropies")
     88    anisotropy_dset.add_columns(temperature=all_T)
     89    for theta in all_theta:
     90        col_name = f"theta{theta:.0f}"
     91        col_values = []
     92        i = np.where(dset.theta == theta)[0]
     93        for T in all_T:
     94            cs = results[T][i]
     95            Imax = np.max(cs)
     96            Imin = np.min(cs)
     97            A = (Imax - Imin)/Imax
     98            col_values.append(A)
     99        anisotropy_dset.add_columns(**{col_name:col_values/np.max(col_values)})
    100
    101
    102    anisotropy_view = anisotropy_dset.add_view('Anisotropies',
    103                         title='Relative anisotropies for Cu(2p)',
    104                         marker='o',
    105                         xlabel='T (K)',
    106                         ylabel=r'$\frac{\Delta I / I_{max}(T)}{\Delta I_{300}'
    107                                r'/ I_{max}(300)} (\%)$',
    108                         autoscale=True)
    109    for theta in all_theta:
    110        col_name = f"theta{theta:.0f}"
    111        anisotropy_view.select('temperature', col_name,
    112                                legend=r'$\theta = {:.0f} \degree$'.format(theta))
    113
    114    return data
    115
    116
    117
    118if __name__ == "__main__":
    119    nplanes = 4
    120    all_theta = np.array([45, 83])
    121    all_theta = np.array([300., 1000.])
    122
    123    clusters = create_clusters(nplanes=nplanes)
    124    data = compute(clusters, all_T=all_T, all_theta=all_theta)
    125    data = analysis(data, all_T=all_T, all_theta=all_theta, nplanes=nplanes)
    126    data.view()

Fig. 15 Variation of anisotropy as a function of temperature and polar angle for Cu(2p).

The anisotropy decreases when the temperature is increased due to the increased disorder in the structure coming from thermal agitation. This variation in anisotropy is more pronounced for grazing incidence angles since surface atoms are expected to vibrate more than bulk ones and the expected mean depth of no-loss emission is \(\sim 1\) atomic layer at \(\theta = 83°\) and 3-4 layers at \(\theta = 45°\) as estimated by \(\Lambda_e\sin\theta\) (where \(\Lambda_e\) is the photoelectron mean free path at 560 eV).