199 lines
6.7 KiB
Python
199 lines
6.7 KiB
Python
"""
|
|
@package pmsco.elements.spectrum
|
|
photoelectron spectrum simulator
|
|
|
|
this module calculates the basic structure of a photoelectron spectrum.
|
|
it calculates positions and approximate amplitude of elastic peaks
|
|
based on photon energy, binding energy, photoionization cross section, and stoichiometry.
|
|
escape depth, photon flux, analyser transmission are not accounted for.
|
|
|
|
|
|
usage
|
|
-----
|
|
|
|
this module requires python 3.6, numpy, matplotlib and
|
|
the periodictable package (https://pypi.python.org/pypi/periodictable).
|
|
|
|
~~~~~~{.py}
|
|
import numpy as np
|
|
import periodictable as pt
|
|
import pmsco.elements.spectrum as spec
|
|
|
|
# for working with the data
|
|
labels, energy, intensity = spec.build_spectrum(800., {"Ti": 1, "O": 2})
|
|
|
|
# for plotting
|
|
spec.plot_spectrum(800., {"Ti": 1, "O": 2})
|
|
~~~~~~
|
|
|
|
|
|
|
|
@author Matthias Muntwiler
|
|
|
|
@copyright (c) 2020 by Paul Scherrer Institut @n
|
|
Licensed under the Apache License, Version 2.0 (the "License"); @n
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
"""
|
|
|
|
from matplotlib import pyplot as plt
|
|
import numpy as np
|
|
import periodictable as pt
|
|
from . import bindingenergy
|
|
from . import photoionization
|
|
|
|
|
|
def get_element(number_or_symbol):
|
|
"""
|
|
return the given Element object of the periodic table.
|
|
|
|
@param number_or_symbol: atomic number (int) or chemical symbol (str).
|
|
@return: Element object.
|
|
"""
|
|
try:
|
|
el = pt.elements[number_or_symbol]
|
|
except KeyError:
|
|
el = pt.elements.symbol(number_or_symbol)
|
|
return el
|
|
|
|
|
|
def get_binding_energy(photon_energy, element, nlj):
|
|
"""
|
|
look up the binding energy of a core level and check whether it is smaller than the photon energy.
|
|
|
|
@param photon_energy: photon energy in eV.
|
|
@param element: Element object of the periodic table.
|
|
@param nlj: (str) spectroscopic term, e.g. '4f7/2'.
|
|
@return: (float) binding energy or numpy.nan.
|
|
"""
|
|
try:
|
|
eb = element.binding_energy[nlj]
|
|
except KeyError:
|
|
return np.nan
|
|
if eb < photon_energy:
|
|
return eb
|
|
else:
|
|
return np.nan
|
|
|
|
|
|
def get_cross_section(photon_energy, element, nlj):
|
|
"""
|
|
look up the photoionization cross section.
|
|
|
|
since the Yeh/Lindau tables do not resolve the spin-orbit splitting,
|
|
this function applies the normal relative weights of a full sub-shell.
|
|
|
|
the result is a linear interpolation between tabulated values.
|
|
|
|
@param photon_energy: photon energy in eV.
|
|
@param element: Element object of the periodic table.
|
|
@param nlj: (str) spectroscopic term, e.g. '4f7/2'.
|
|
@return: (float) cross section in Mb.
|
|
"""
|
|
nl = nlj[0:2]
|
|
try:
|
|
pet, cst = element.photoionization.cross_section[nl]
|
|
except KeyError:
|
|
return np.nan
|
|
|
|
# weights of spin-orbit peaks
|
|
d_wso = {"p1/2": 1./3.,
|
|
"p3/2": 2./3.,
|
|
"d3/2": 2./5.,
|
|
"d5/2": 3./5.,
|
|
"f5/2": 3./7.,
|
|
"f7/2": 4./7.}
|
|
wso = d_wso.get(nlj[1:], 1.)
|
|
cst = cst * wso
|
|
|
|
# todo: consider spline
|
|
return np.interp(photon_energy, pet, cst)
|
|
|
|
|
|
def build_spectrum(photon_energy, elements, binding_energy=False, work_function=4.5):
|
|
"""
|
|
calculate the positions and amplitudes of core-level photoemission lines.
|
|
|
|
the function looks up the binding energies and cross sections of all photoemission lines in the energy range
|
|
given by the photon energy and returns an array of expected spectral lines.
|
|
|
|
@param photon_energy: (numeric) photon energy in eV.
|
|
@param elements: list or dictionary of elements.
|
|
elements are identified by their atomic number (int) or chemical symbol (str).
|
|
if a dictionary is given, the (float) values are stoichiometric weights of the elements.
|
|
@param binding_energy: (bool) return binding energies (True) rather than kinetic energies (False, default).
|
|
@param work_function: (float) work function of the instrument in eV.
|
|
@return: tuple (labels, positions, intensities) of 1-dimensional numpy arrays representing the spectrum.
|
|
labels are in the format {Symbol}{n}{l}{j}.
|
|
"""
|
|
ekin = []
|
|
ebind = []
|
|
intens = []
|
|
labels = []
|
|
|
|
for element in elements:
|
|
el = get_element(element)
|
|
for n in range(1, 8):
|
|
for l in "spdf":
|
|
for j in ['', '1/2', '3/2', '5/2', '7/2']:
|
|
nlj = f"{n}{l}{j}"
|
|
eb = get_binding_energy(photon_energy, el, nlj)
|
|
cs = get_cross_section(photon_energy, el, nlj)
|
|
try:
|
|
cs = cs * elements[element]
|
|
except (KeyError, TypeError):
|
|
pass
|
|
if not np.isnan(eb) and not np.isnan(cs):
|
|
ekin.append(photon_energy - eb - work_function)
|
|
ebind.append(eb)
|
|
intens.append(cs)
|
|
labels.append(f"{el.symbol}{nlj}")
|
|
|
|
ebind = np.array(ebind)
|
|
ekin = np.array(ekin)
|
|
intens = np.array(intens)
|
|
labels = np.array(labels)
|
|
|
|
if binding_energy:
|
|
return labels, ebind, intens
|
|
else:
|
|
return labels, ekin, intens
|
|
|
|
|
|
def plot_spectrum(photon_energy, elements, binding_energy=False, work_function=4.5, show_labels=True):
|
|
"""
|
|
plot a simple spectrum representation of a material.
|
|
|
|
the function looks up the binding energies and cross sections of all photoemission lines in the energy range
|
|
given by the photon energy and returns an array of expected spectral lines.
|
|
|
|
the spectrum is plotted using matplotlib.pyplot.stem.
|
|
|
|
@param photon_energy: (numeric) photon energy in eV.
|
|
@param elements: list or dictionary of elements.
|
|
elements are identified by their atomic number (int) or chemical symbol (str).
|
|
if a dictionary is given, the (float) values are stoichiometric weights of the elements.
|
|
@param binding_energy: (bool) return binding energies (True) rather than kinetic energies (False, default).
|
|
@param work_function: (float) work function of the instrument in eV.
|
|
@param show_labels: (bool) show peak labels (True, default) or not (False).
|
|
@return: (figure, axes)
|
|
"""
|
|
labels, energy, intensity = build_spectrum(photon_energy, elements, binding_energy=binding_energy,
|
|
work_function=work_function)
|
|
|
|
fig, ax = plt.subplots()
|
|
ax.stem(energy, intensity, basefmt=' ', use_line_collection=True)
|
|
if show_labels:
|
|
for sxy in zip(labels, energy, intensity):
|
|
ax.annotate(sxy[0], xy=(sxy[1], sxy[2]), textcoords='data')
|
|
|
|
ax.grid()
|
|
if binding_energy:
|
|
ax.set_xlabel('binding energy')
|
|
else:
|
|
ax.set_xlabel('kinetic energy')
|
|
ax.set_ylabel('intensity')
|
|
ax.set_title(elements)
|
|
return fig, ax
|