Source code for sloth.utils.xdata

#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""Utility to retrieve x-ray data from external libraries/databases
===================================================================

Available libraries/dbs and order of usage
------------------------------------------

1. `xraylib <https://github.com/tschoonj/xraylib>`_
2. ``Elements`` in `PyMca <https://github.com/vasole/pymca>`_
3. ``xraydb_plugin.py`` in `Larch <https://github.com/xraypy/xraylarch>`_

.. note:: `XrayDB <https://github.com/xraypy/XrayDB>`_ has its own package now

"""
import math
import numpy as np

try:
    from PyMca5.PyMcaPhysics.xrf.Elements import Element

    HAS_PYMCA = True
except ImportError:
    HAS_PYMCA = False
    ELEMENTS_DICT = None
    pass

try:
    import xraylib as xl

    #xl.SetErrorMessages(0)  #: disable showing error messages // DEPRECATED since xraylib 4.1.1
    HAS_XRAYLIB = True
except ImportError:
    HAS_XRAYLIB = False
    pass

try:
    import xraydb
    HAS_XRAYDB = True
except ImportError:
    HAS_XRAYDB = False
    pass

from sloth.utils.bragg import findhkl

from sloth.math.lineshapes import lorentzian, fwhm2sigma

#: MODULE LOGGER
from .logging import getLogger

_LOGGER = getLogger("sloth.utils.xdata", level="INFO")

#: ERROR MESSAGES


def _larch_error(ret=None):
    """Log a missing larch error message and return given 'ret'"""
    _LOGGER.error("Larch not found")
    return ret


def _xraylib_error(ret=None):
    """Log a missing xraylib error message and return given 'ret'"""
    _LOGGER.error("Xraylib not found")
    return ret


def _pymca_error(ret=None):
    """Log a missing PyMca5 error message and return given 'ret'"""
    _LOGGER.error("PyMca5 not found")
    return ret


def _xraydb_error(ret=None):
    """Log a missing XrayDB error message and return given 'ret'"""
    _LOGGER.error("XrayDB not found")
    return ret


#######################
#: ELEMENTS AND LINES #
#######################

#: Taken from PyMca5/PyMcaPhysics/xrf/Elements.py
#
#   Symbol  Atomic Number   x y ( positions on table )
#       name,  mass, density
#
ELEMENTS_INFO = (
    ("H", 1, 1, 1, "hydrogen", 1.00800, 0.08988),
    ("He", 2, 18, 1, "helium", 4.00300, 0.17860),
    ("Li", 3, 1, 2, "lithium", 6.94000, 534.000),
    ("Be", 4, 2, 2, "beryllium", 9.01200, 1848.00),
    ("B", 5, 13, 2, "boron", 10.8110, 2340.00),
    ("C", 6, 14, 2, "carbon", 12.0100, 1580.00),
    ("N", 7, 15, 2, "nitrogen", 14.0080, 1.25000),
    ("O", 8, 16, 2, "oxygen", 16.0000, 1.42900),
    ("F", 9, 17, 2, "fluorine", 19.0000, 1108.00),
    ("Ne", 10, 18, 2, "neon", 20.1830, 0.90020),
    ("Na", 11, 1, 3, "sodium", 22.9970, 970.000),
    ("Mg", 12, 2, 3, "magnesium", 24.3200, 1740.00),
    ("Al", 13, 13, 3, "aluminium", 26.9700, 2720.00),
    ("Si", 14, 14, 3, "silicon", 28.0860, 2330.00),
    ("P", 15, 15, 3, "phosphorus", 30.9750, 1820.00),
    ("S", 16, 16, 3, "sulphur", 32.0660, 2000.00),
    ("Cl", 17, 17, 3, "chlorine", 35.4570, 1560.00),
    ("Ar", 18, 18, 3, "argon", 39.9440, 1.78400),
    ("K", 19, 1, 4, "potassium", 39.1020, 862.000),
    ("Ca", 20, 2, 4, "calcium", 40.0800, 1550.00),
    ("Sc", 21, 3, 4, "scandium", 44.9600, 2992.00),
    ("Ti", 22, 4, 4, "titanium", 47.9000, 4540.00),
    ("V", 23, 5, 4, "vanadium", 50.9420, 6110.00),
    ("Cr", 24, 6, 4, "chromium", 51.9960, 7190.00),
    ("Mn", 25, 7, 4, "manganese", 54.9400, 7420.00),
    ("Fe", 26, 8, 4, "iron", 55.8500, 7860.00),
    ("Co", 27, 9, 4, "cobalt", 58.9330, 8900.00),
    ("Ni", 28, 10, 4, "nickel", 58.6900, 8900.00),
    ("Cu", 29, 11, 4, "copper", 63.5400, 8940.00),
    ("Zn", 30, 12, 4, "zinc", 65.3800, 7140.00),
    ("Ga", 31, 13, 4, "gallium", 69.7200, 5903.00),
    ("Ge", 32, 14, 4, "germanium", 72.5900, 5323.00),
    ("As", 33, 15, 4, "arsenic", 74.9200, 5730.00),
    ("Se", 34, 16, 4, "selenium", 78.9600, 4790.00),
    ("Br", 35, 17, 4, "bromine", 79.9200, 3120.00),
    ("Kr", 36, 18, 4, "krypton", 83.8000, 3.74000),
    ("Rb", 37, 1, 5, "rubidium", 85.4800, 1532.00),
    ("Sr", 38, 2, 5, "strontium", 87.6200, 2540.00),
    ("Y", 39, 3, 5, "yttrium", 88.9050, 4405.00),
    ("Zr", 40, 4, 5, "zirconium", 91.2200, 6530.00),
    ("Nb", 41, 5, 5, "niobium", 92.9060, 8570.00),
    ("Mo", 42, 6, 5, "molybdenum", 95.9500, 10220.0),
    ("Tc", 43, 7, 5, "technetium", 99.0000, 11500.0),
    ("Ru", 44, 8, 5, "ruthenium", 101.0700, 12410.0),
    ("Rh", 45, 9, 5, "rhodium", 102.9100, 12440.0),
    ("Pd", 46, 10, 5, "palladium", 106.400, 12160.0),
    ("Ag", 47, 11, 5, "silver", 107.880, 10500.0),
    ("Cd", 48, 12, 5, "cadmium", 112.410, 8650.00),
    ("In", 49, 13, 5, "indium", 114.820, 7280.00),
    ("Sn", 50, 14, 5, "tin", 118.690, 5310.00),
    ("Sb", 51, 15, 5, "antimony", 121.760, 6691.00),
    ("Te", 52, 16, 5, "tellurium", 127.600, 6240.00),
    ("I", 53, 17, 5, "iodine", 126.910, 4940.00),
    ("Xe", 54, 18, 5, "xenon", 131.300, 5.90000),
    ("Cs", 55, 1, 6, "caesium", 132.910, 1873.00),
    ("Ba", 56, 2, 6, "barium", 137.360, 3500.00),
    ("La", 57, 3, 6, "lanthanum", 138.920, 6150.00),
    ("Ce", 58, 4, 9, "cerium", 140.130, 6670.00),
    ("Pr", 59, 5, 9, "praseodymium", 140.920, 6769.00),
    ("Nd", 60, 6, 9, "neodymium", 144.270, 6960.00),
    ("Pm", 61, 7, 9, "promethium", 147.000, 6782.00),
    ("Sm", 62, 8, 9, "samarium", 150.350, 7536.00),
    ("Eu", 63, 9, 9, "europium", 152.000, 5259.00),
    ("Gd", 64, 10, 9, "gadolinium", 157.260, 7950.00),
    ("Tb", 65, 11, 9, "terbium", 158.930, 8272.00),
    ("Dy", 66, 12, 9, "dysprosium", 162.510, 8536.00),
    ("Ho", 67, 13, 9, "holmium", 164.940, 8803.00),
    ("Er", 68, 14, 9, "erbium", 167.270, 9051.00),
    ("Tm", 69, 15, 9, "thulium", 168.940, 9332.00),
    ("Yb", 70, 16, 9, "ytterbium", 173.040, 6977.00),
    ("Lu", 71, 17, 9, "lutetium", 174.990, 9842.00),
    ("Hf", 72, 4, 6, "hafnium", 178.500, 13300.0),
    ("Ta", 73, 5, 6, "tantalum", 180.950, 16600.0),
    ("W", 74, 6, 6, "tungsten", 183.920, 19300.0),
    ("Re", 75, 7, 6, "rhenium", 186.200, 21020.0),
    ("Os", 76, 8, 6, "osmium", 190.200, 22500.0),
    ("Ir", 77, 9, 6, "iridium", 192.200, 22420.0),
    ("Pt", 78, 10, 6, "platinum", 195.090, 21370.0),
    ("Au", 79, 11, 6, "gold", 197.200, 19370.0),
    ("Hg", 80, 12, 6, "mercury", 200.610, 13546.0),
    ("Tl", 81, 13, 6, "thallium", 204.390, 11860.0),
    ("Pb", 82, 14, 6, "lead", 207.210, 11340.0),
    ("Bi", 83, 15, 6, "bismuth", 209.000, 9800.00),
    ("Po", 84, 16, 6, "polonium", 209.000, 0),
    ("At", 85, 17, 6, "astatine", 210.000, 0),
    ("Rn", 86, 18, 6, "radon", 222.000, 9.73000),
    ("Fr", 87, 1, 7, "francium", 223.000, 0),
    ("Ra", 88, 2, 7, "radium", 226.000, 0),
    ("Ac", 89, 3, 7, "actinium", 227.000, 0),
    ("Th", 90, 4, 10, "thorium", 232.000, 11700.0),
    ("Pa", 91, 5, 10, "proactinium", 231.03588, 0),
    ("U", 92, 6, 10, "uranium", 238.070, 19050.0),
    ("Np", 93, 7, 10, "neptunium", 237.000, 0),
    ("Pu", 94, 8, 10, "plutonium", 239.100, 19700.0),
    ("Am", 95, 9, 10, "americium", 243, 0),
    ("Cm", 96, 10, 10, "curium", 247, 0),
    ("Bk", 97, 11, 10, "berkelium", 247, 0),
    ("Cf", 98, 12, 10, "californium", 251, 0),
    ("Es", 99, 13, 10, "einsteinium", 252, 0),
    ("Fm", 100, 14, 10, "fermium", 257, 0),
    ("Md", 101, 15, 10, "mendelevium", 258, 0),
    ("No", 102, 16, 10, "nobelium", 259, 0),
    ("Lr", 103, 17, 10, "lawrencium", 262, 0),
    ("Rf", 104, 4, 7, "rutherfordium", 261, 0),
    ("Db", 105, 5, 7, "dubnium", 262, 0),
    ("Sg", 106, 6, 7, "seaborgium", 266, 0),
    ("Bh", 107, 7, 7, "bohrium", 264, 0),
    ("Hs", 108, 8, 7, "hassium", 269, 0),
    ("Mt", 109, 9, 7, "meitnerium", 268, 0),
)
ELEMENTS = [elt[0] for elt in ELEMENTS_INFO]
ELEMENTS_N = [elt[1] for elt in ELEMENTS_INFO]

#: SHELLS / EDGES
#: K = 1 (s)
#: L = 2 (s, p)
#: M = 3 (s, p, d)
#: N = 4 (s, p, d, f)
#: O = 5 (s, p, d, f)
#: P = 6 (s, p)
SHELLS = (
    "K",  # 0
    "L1",
    "L2",
    "L3",  # 1, 2, 3
    "M1",
    "M2",
    "M3",
    "M4",
    "M5",  # 4, 5, 6, 7, 8
    "N1",
    "N2",
    "N3",
    "N4",
    "N5",
    "N6",
    "N7",  # 9, 10, 11, 12, 13, 14, 15
    "O1",
    "O2",
    "O3",
    "O4",
    "O5",  # 16, 17, 18, 19, 20
    "P1",
    "P2",
    "P3",
)  # 21, 22, 23

#: TRANSITIONS
#: 1 = s
#: 2, 3 = p (1/2, 3/2)
#: 4, 5 = d (3/2, 5/2)
#: 6, 7 = f (5/2, 7/2)
LEVELS_DICT = {
    "K": "1s",
    "L1": "2s",
    "L2": "2p1/2",
    "L3": "2p3/2",
    "M1": "3s",
    "M2": "3p1/2",
    "M3": "3p3/2",
    "M4": "3d3/2",
    "M5": "3d5/2",
    "N1": "4s",
    "N2": "4p1/2",
    "N3": "4p3/2",
    "N4": "4d3/2",
    "N5": "4d5/2",
    "N6": "4f5/2",
    "N7": "4f7/2",
    "O1": "5s",
    "O2": "5p1/2",
    "O3": "5p3/2",
    "P1": "6s",
    "P2": "6p1/2",
    "P3": "6p3/2",
}

#: dictionary of lines
#: Ln in Hepheastus is LE in Xraylib
#: Mz in Hepheastus not in Xraylib (the single transitions yes!)
LINES_DICT = {
    "K": (
        "KA1",
        "KA2",
        "KA3",  # LINES[0, 1, 2]
        "KB1",
        "KB2",
        "KB3",
        "KB4",
        "KB5",
    ),  # LINES[3, 4, 5, 6, 7]
    "L1": ("LB3", "LB4", "LG2", "LG3"),  # LINES[8, 9, 10, 11]
    "L2": ("LB1", "LG1", "LG6", "LE"),  # LINES[12, 13, 14, 15]
    #: LINES[16, 17, 18, 19, 20, 21]
    "L3": ("LA1", "LA2", "LB2", "LB5", "LB6", "LL"),
    "M3": ("MG",),  # LINES[22]
    "M4": ("MB",),  # LINES[23]
    "M5": ("MA1", "MA2"),
}  # LINES[24, 25]
LINES_K = LINES_DICT["K"]
LINES_L = LINES_DICT["L1"] + LINES_DICT["L2"] + LINES_DICT["L3"]
LINES_M = LINES_DICT["M3"] + LINES_DICT["M4"] + LINES_DICT["M5"]
LINES = LINES_K + LINES_L + LINES_M
#
TRANS_DICT = {
    "K": ("KL3", "KL2", "KL1", "KM3", "KN3", "KM2", "KN5", "KM5"),
    "L1": ("L1M3", "L1M2", "L1N2", "L1N3"),
    "L2": ("L2M4", "L2N4", "L2O4", "L2M1"),
    "L3": ("L3M5", "L3M4", "L3N5", "L304", "L3N1", "L3M1"),
    "M3": ("M3N5",),
    "M4": ("M4N6",),
    "M5": ("M5N7", "M5N6"),
}
TRANS_K = TRANS_DICT["K"]
TRANS_L = TRANS_DICT["L1"] + TRANS_DICT["L2"] + TRANS_DICT["L3"]
TRANS_M = TRANS_DICT["M3"] + TRANS_DICT["M4"] + TRANS_DICT["M5"]
TRANSITIONS = TRANS_K + TRANS_L + TRANS_M
#: INDEX DICTIONARY: KEYS=LINES : VALUES=(LINES[IDX],\
#                                         SHELLS[IDX_XAS], SHELLS[IDX_XES])
LINES2TRANS = {
    "KA1": (0, 0, 3),
    "KA2": (1, 0, 2),
    "KA3": (2, 0, 1),
    "KB1": (3, 0, 6),
    "KB2": (4, 0, 11),
    "KB3": (5, 0, 5),
    "KB4": (6, 0, 13),
    "KB5": (7, 0, 8),
    "LB3": (8, 1, 6),
    "LB4": (9, 1, 5),
    "LG2": (10, 1, 10),
    "LG3": (11, 1, 11),
    "LB1": (12, 2, 7),
    "LG1": (13, 2, 12),
    "LG6": (14, 2, 19),
    "LE": (15, 2, 4),
    "LA1": (16, 3, 8),
    "LA2": (17, 3, 7),
    "LB2": (18, 3, 13),
    "LB5": (19, 3, 19),  # WARNING: here is only O4
    "LB6": (20, 3, 9),
    "LL": (21, 3, 4),
    "MG": (22, 6, 13),
    "MB": (23, 7, 14),
    "MA1": (24, 8, 15),
    "MA2": (25, 8, 14),
}


[docs] def mapLine2Trans(line): """returns a tuple of strings mapping the transitions for a given line""" try: idx = LINES2TRANS[line] return (LINES[idx[0]], TRANSITIONS[idx[0]], SHELLS[idx[1]], SHELLS[idx[2]]) except KeyError: _LOGGER.error("Line {0} not known; returning 0".format(line)) return 0
############################ #: XRAYLIB-BASED FUNCTIONS # ############################
[docs] def get_element(elem): """get a tuple of information for a given element""" _errstr = f"Element {elem} not known!" if (isinstance(elem, str) and (elem in ELEMENTS)): return [elt for elt in ELEMENTS_INFO if elt[0] == elem][0] if (isinstance(elem, int) and (elem in ELEMENTS_N)): return [elt for elt in ELEMENTS_INFO if elt[1] == elem] _LOGGER.error(_errstr) raise NameError(_errstr)
[docs] def get_line(line): """Check the line name is a valid name and return it""" if line not in LINES: _errstr = f"Line {line} is not a valid name in Siegbahn notation" _LOGGER.error(_errstr) raise NameError(_errstr) return line
[docs] def find_edge(emin, emax, shells=None): """Get the edge energy in a given energy range [emin, emax] (eV) Parameters ---------- emin, emax : floats energy range to search for an absorption edege (eV) shells : list of str (optional) list of shells to search for [None -> use SHELLS (=all)] """ if HAS_XRAYLIB is False: _xraylib_error(0) if shells is None: shells = SHELLS for el in ELEMENTS: eln = get_element(el) for sh in shells: edge = (xl.EdgeEnergy(eln[1], getattr(xl, sh + "_SHELL")) * 1000) if (edge >= emin) and (edge <= emax): _LOGGER.info("{0} \t {1} \t {2:>.2f} eV".format(el, sh, edge))
[docs] def find_line(emin, emax, elements=None, lines=None, outDict=False, backend="xraylib", skip_zero_width=True, thetamin=65): """Get the emission line energy in a given energy range [emin,emax] (eV) Parameters ---------- emin, emax : float [minimum, maximum] energy range (eV) elements : list of str (optional) list of elements, [None -> ELEMENTS (all)] lines : list of str (optional) list of lines, [None -> LINES (all)] outDict : boolean, False returns a dictionary instead of printing to screen skip_zero_width : boolean, True True: if fluo_width == 0, not include in the results Returns ------- None or outDict if outDict: _out['el']: element symbol, list of strs _out['eln]: element number, list of ints _out['ln']: line, list of strs _out['en']: energy eV, list of floats _out['w'] : width eV, list of floats else: prints to screen the results """ if backend == "pymca" and (not HAS_PYMCA): _pymca_error() backend = "xraylib" _LOGGER.warning("Changed backend to %s", backend) if backend == "xraylib" and (not HAS_XRAYLIB): _xraylib_error() _LOGGER.error("No backend available!") return None if lines is None: lines = LINES if elements is None: elements = ELEMENTS _out = {} _out["el"] = [] _out["eln"] = [] _out["ln"] = [] _out["en"] = [] _out["w"] = [] _out["crys_lab"] = [] _out["crys0_lab"] = [] _out["crys_deg"] = [] for el in elements: eln = get_element(el) for ln in lines: try: if backend == "pymca": line = Element[eln[0]][mapLine2Trans(ln)[1]]["energy"] * 1000 else: line = xl.LineEnergy(eln[1], getattr(xl, ln + "_LINE")) * 1000 except Exception: _LOGGER.debug("{0}.{1} none".format(el, ln)) continue if (line >= emin) and (line <= emax): w = fluo_width(elem=el, line=ln, showInfos=False) if w == 0: _LOGGER.warning(f"{el}.{ln} zero width") if skip_zero_width: _LOGGER.info(f"{el}.{ln} skipped") continue _out["el"].append(eln[0]) _out["eln"].append(eln[1]) _out["ln"].append(ln) _out["en"].append(line) _out["w"].append(w) try: hkl_out = findhkl(line, thetamin=thetamin, verbose=False, retBest=True) except Exception: _LOGGER.warning(f"No Si/Ge crystal analyzer found for {eln[0]} {ln}") hkl_out = [None, None, None, None, None, None, None] _out["crys_lab"].append(hkl_out[5]) _out["crys0_lab"].append(hkl_out[6]) _out["crys_deg"].append(hkl_out[4]) #: returns if outDict: return _out else: for eln, el, ln, line, w in zip( _out["eln"], _out["el"], _out["ln"], _out["en"], _out["w"] ): _LOGGER.info(f"{eln} {el} {ln} {line:>.3f} {w:>.2f}")
[docs] def ene_res(emin, emax, shells=["K"]): """ used in spectro.py """ if HAS_XRAYLIB is False: _xraylib_error(0) s = {} s["el"] = [] s["en"] = [] s["edge"] = [] s["ch"] = [] s["dee"] = [] for el in ELEMENTS: eln = get_element(el) for sh in shells: edge = (xl.EdgeEnergy(eln[1], getattr(xl, sh + "_SHELL")) * 1000) ch = (xl.AtomicLevelWidth(eln[1], getattr(xl, sh + "_SHELL")) * 1000) if (edge >= emin) and (edge <= emax): s["el"].append(el) s["en"].append(xl.SymbolToAtomicNumber(el)) s["edge"].append(edge) s["ch"].append(ch) s["dee"].append(ch / edge) return s
[docs] def fluo_width(elem=None, line=None, herfd=False, showInfos=True): """Get the fluorescence line width in eV Parameters ---------- elem : string or int absorbing element line : string Siegbahn notation for emission line Returns ------- herfd=False (default): lw_xas + lw_xes herfd=True: 1/(math.sqrt(lw_xas**2 + lw_xes**2)) """ if HAS_XRAYLIB is False: _xraylib_error(0) if (elem is None) or (line is None): _LOGGER.error("element or edge not given, returning 0") return 0 elm = get_element(elem) ln = mapLine2Trans(line) try: lw_xas = xl.AtomicLevelWidth(elm[1], getattr(xl, ln[2] + "_SHELL")) * 1000 lw_xes = xl.AtomicLevelWidth(elm[1], getattr(xl, ln[3] + "_SHELL")) * 1000 lw_herfd = 1.0 / (math.sqrt(lw_xas ** 2 + lw_xes ** 2)) if showInfos: ln_ev = xl.LineEnergy(elm[1], getattr(xl, line + "_LINE")) * 1000 _LOGGER.info(f"{elm[0]} {line} (={ln[1]}): {ln_ev:.2f} eV") _LOGGER.info( f"Atomic levels widths: XAS={lw_xas:.2f} eV, XES={lw_xes:.2f} eV" ) _LOGGER.info(f"... -> STD={lw_xas+lw_xes:.2f} eV, HERFD={lw_herfd:.2f} eV]") if herfd is True: return lw_herfd else: return lw_xas + lw_xes except Exception: return 0
[docs] def fluo_amplitude(elem, line, excitation=None, barn_unit=False): """Get the fluorescence cross section for a given element/line Parameters ---------- elem : string or number element line : string emission line Siegban (e.g. 'LA1') or IUPAC (e.g. 'L3M5') excitation : float (optional) excitation energy in eV [None -> 10 keV] barn_unit : boolean use units of barn/atom [None -> cm2/g] Returns ------- fluo_amp (in 'cm2/g' or 'barn/atom' if barn_unit is True) """ if excitation is None: _LOGGER.warning("Excitation energy not given, using 10 keV") excitation = 10.0 #: guess if eV or keV elif excitation >= 200.0: excitation /= 1000 _LOGGER.info(f"Excitation energy is {excitation} keV") el_n = get_element(elem)[1] if barn_unit: CSfluo = xl.CSb_FluorLine_Kissel_Cascade else: CSfluo = xl.CS_FluorLine_Kissel_Cascade try: fluo_amp = CSfluo(el_n, getattr(xl, line + "_LINE"), excitation) except Exception: _LOGGER.warning("Line not known") fluo_amp = 0 return fluo_amp
[docs] def xray_line(element, line=None, initial_level=None): """Get the emission energy in eV for a given element/line or level Parameters ---------- element : string or int absorbing element line: string (optional) Siegbahn notation, e.g. 'KA1' [None -> LINES (all)] initial_level: string initial core level, e.g. 'K' [None] Returns ------- dictionary {'line': [], 'ene': []} or a number """ if HAS_XRAYLIB is False: _xraylib_error(0) el_n = get_element(element)[1] outdict = {"line": [], "ene": []} _retNum = False if (line is None) and (initial_level is not None): try: lines = [line for line in LINES if initial_level in line] except Exception: _LOGGER.error("initial_level is wrong") else: lines = [line] _retNum = True for _line in lines: try: line_ene = xl.LineEnergy(el_n, getattr(xl, _line + "_LINE")) * 1000 outdict["line"].append(_line) outdict["ene"].append(line_ene) except Exception: _LOGGER.error("line is wrong") continue if _retNum: return outdict["ene"][0] else: return outdict
[docs] def xray_edge(element, initial_level=None): """Get the energy edge in eV for a given element :param element: string or number :param initial_level: string, initial core level, e.g. 'K' or list [None] :returns: dictionary {'edge' : [], 'ene' : []} or a number """ if HAS_XRAYLIB is False: _xraylib_error(0) el_n = get_element(element)[1] outdict = {"edge": [], "ene": []} _retNum = False if initial_level is None: initial_level = SHELLS if type(initial_level) == str: initial_level = [initial_level] _retNum = True else: _LOGGER.error("initial_level is wrong") for _level in initial_level: try: edge_ene = xl.EdgeEnergy(el_n, getattr(xl, _level + "_SHELL")) * 1000 outdict["edge"].append(_level) outdict["ene"].append(edge_ene) except Exception: _LOGGER.warning( "{0} {1} edge unknown".format(get_element(element)[0], _level) ) continue if _retNum: return outdict["ene"][0] else: return outdict
[docs] def fluo_spectrum(elem, line, xwidth=3, xstep=0.05, plot=False, showInfos=True, **kws): """Generate a fluorescence spectrum for a given element/line .. note:: it generates a Lorentzian function with the following parameters: - center: emission energy (eV) - sigma: from FWHM of sum of atomic levels widths (XAS+XES) - amplitude: CS_FuorLine_Kissel_Cascade - xmin, xmax: center -+ xwidth*fwhm Parameters ---------- elem : string or int absorbing element line : string emission line Siegban (e.g. 'LA1') or IUPAC (e.g. 'L3M5') xwidth : int or float (optional) FWHM multiplication factor to establish xmin, xmax range (= center -+ xwidth*fwhm) [3] xstep : float (optional) energy step in eV [0.05] showInfos : boolean (optional) print the `info` dict [True] plot : boolean (optional) plot the line before returning it [False] **kws : keyword arguments for :func:`fluo_width`, :func:`fluo_amplitude` Returns ------- xfluo, yfluo, info : XY arrays of floats, dictionary """ el = get_element(elem) exc = kws.get("excitation", 10000.0) bu = kws.get("barn_unit", False) if bu is True: yunit = "barn/atom" else: yunit = "cm2/g" fwhm = fluo_width(elem, line, showInfos=showInfos) amp = fluo_amplitude(el[1], line, excitation=exc, barn_unit=bu) cen = xl.LineEnergy(el[1], getattr(xl, line + "_LINE")) * 1000 if (fwhm == 0) or (amp == 0) or (cen == 0): raise NameError("no line found") sig = fwhm2sigma(fwhm) xmin = cen - xwidth * fwhm xmax = cen + xwidth * fwhm xfluo = np.arange(xmin, xmax, xstep) yfluo = lorentzian(xfluo, amplitude=amp, center=cen, sigma=sig) info = { "el": el[0], "eln": el[1], "ln": line, "exc": exc, "cen": cen, "fwhm": fwhm, "amp": amp, "yunit": yunit, } legend = "{eln} {ln}".format(**info) if showInfos: _LOGGER.info( "Lorentzian => cen: {cen:.3f} eV, amp: {amp:.3f} {yunit}, fwhm: {fwhm:.3f} eV".format( **info ) ) if plot: from sloth.gui.plot.plot1D import Plot1D p1 = Plot1D() p1.addCurve( xfluo, yfluo, legend=legend, replace=True, xlabel="energy (eV)", ylabel="intensity ({0})".format(yunit), ) p1.show() input("PRESS ENTER to close the plot window and return") return xfluo, yfluo, info
[docs] def fluo_lines(elem, lines, retAll=False, **fluokws): """Generate a cumulative emission spectrum of a given element and list of lines Parameters ---------- elem : string or int lines : list of str emission lines as Siegban (e.g. 'LA1') or IUPAC (e.g. 'L3M5') **fluokws : keyword arguments for :func:`fluo_spectrum` Returns ------- xcom, ycom : arrays of floats energy/intensity of the whole spectrum if retAll: xcom, ycom, [xi, yi, ii] """ plot = fluokws.get("plot", False) xstep = fluokws.get("xstep", 0.05) fluokws.update({"plot": False}) xi, yi, ii = [], [], [] for ln in lines: try: x, y, i = fluo_spectrum(elem, ln, **fluokws) xi.append(x) yi.append(y) ii.append(i) except Exception: _LOGGER.info("no line found for {0}-{1}".format(elem, ln)) xmin = min([x.min() for x in xi]) xmax = max([x.max() for x in xi]) xcom = np.arange(xmin, xmax, xstep) ycom = np.zeros_like(xcom) for x, y in zip(xi, yi): yinterp = np.interp(xcom, x, y) ycom += yinterp if plot: from sloth.gui.plot.plot1D import Plot1D p = Plot1D() p.addCurve( xcom, ycom, legend="sum", color="black", replace=True, xlabel="energy (eV)", ylabel="intensity", ) for x, y, i in zip(xi, yi, ii): p.addCurve(x, y, legend=i["ln"], replace=False) p.show() if retAll: return xcom, ycom, [xi, yi, ii] else: return xcom, ycom
########################## #: LARCH-BASED FUNCTIONS # ########################## #: xdb.function() # ----------------------------------------------------------- #: function : description # --------------------:-------------------------------------- #: atomic_number() : atomic number from symbol #: atomic_symbol() : atomic symbol from number #: atomic_mass() : atomic mass #: atomic_density() : atomic density (for pure element) #: xray_edge() : xray edge data for a particular element and edge #: xray_line() : xray emission line data for an element and line #: xray_edges() : dictionary of all X-ray edges data for an element #: xray_lines() : dictionary of all X-ray emission line data for an element #: fluo_yield() : fluorescence yield and weighted line energy #: core_width() : core level width for an element and edge #: (Keski-Rahkonen and Krause) #: mu_elam() : absorption cross-section #: coherent_xsec() : coherent cross-section #: incoherent_xsec() : incoherent cross-section #: f0() : elastic scattering factor (Waasmaier and Kirfel) #: f0_ions() : list of valid “ions” for f0() (Waasmaier and Kirfel) #: chantler_energies() : energies of tabulation for Chantler data (Chantler) #: f1_chantler() : f’ anomalous factor (Chantler) #: f2_chantler() : f” anomalous factor (Chantler) #: mu_chantler() : absorption cross-section (Chantler) #: xray_delta_beta() : anomalous components of the index of refraction for a material #: f1f2_cl() : f’ and f” anomalous factors (Cromer and Liberman) #: Table of X-ray Edge / Core electronic levels # +-----+-----------------+-----+-----------------+-----+-----------------+ # |Name |electronic level |Name |electronic level |Name |electronic level | # +=====+=================+=====+=================+=====+=================+ # | K | 1s | N7 | 4f7/2 | O3 | 5p3/2 | # +-----+-----------------+-----+-----------------+-----+-----------------+ # | L3 | 2p3/2 | N6 | 4f5/2 | O2 | 5p1/2 | # +-----+-----------------+-----+-----------------+-----+-----------------+ # | L2 | 2p1/2 | N5 | 4d5/2 | O1 | 5s | # +-----+-----------------+-----+-----------------+-----+-----------------+ # | L1 | 2s | N4 | 4d3/2 | P3 | 6p3/2 | # +-----+-----------------+-----+-----------------+-----+-----------------+ # | M5 | 3d5/2 | N3 | 4p3/2 | P2 | 6p1/2 | # +-----+-----------------+-----+-----------------+-----+-----------------+ # | M4 | 3d3/2 | N2 | 4p1/2 | P1 | 6s | # +-----+-----------------+-----+-----------------+-----+-----------------+ # | M3 | 3p3/2 | N1 | 4s | | | # +-----+-----------------+-----+-----------------+-----+-----------------+ # | M2 | 3p1/2 | | | | | # +-----+-----------------+-----+-----------------+-----+-----------------+ # | M1 | 3s | | | | | # +-----+-----------------+-----+-----------------+-----+-----------------+ #: Table of X-ray emission line names and the corresponding Siegbahn and IUPAC notations # +--------+---------------------+--------+-----+----------+----------+ # | Name | Siegbahn | IUPAC | Name| Siegbahn | IUPAC | # +========+=====================+========+=====+==========+==========+ # | Ka1 | K\alpha_1 | K-L3 | Lb4 | L\beta_4 | L1-M2 | # +--------+---------------------+--------+-----+----------+----------+ # | Ka2 | K\alpha_2 | K-L2 | Lb5 | L\beta_5 | L3-O4,5 | # +--------+---------------------+--------+-----+----------+----------+ # | Ka3 | K\alpha_3 | K-L1 | Lb6 | L\beta_6 | L3-N1 | # +--------+---------------------+--------+-----+----------+----------+ # | Kb1 | K\beta_1 | K-M3 | Lg1 | L\gamma_1| L2-N4 | # +--------+---------------------+--------+-----+----------+----------+ # | Kb2 | K\beta_2 | K-N2,3 | Lg2 | L\gamma_2| L1-N2 | # +--------+---------------------+--------+-----+----------+----------+ # | Kb3 | K\beta_3 | K-M2 | Lg3 | L\gamma_3| L1-N3 | # +--------+---------------------+--------+-----+----------+----------+ # | Kb4 | K\beta_2 | K-N4,5 | Lg6 | L\gamma_6| L2-O4 | # +--------+---------------------+--------+-----+----------+----------+ # | Kb5 | K\beta_3 | K-M4,5 | Ll | Ll | L3-M1 | # +--------+---------------------+--------+-----+----------+----------+ # | La1 | L\alpha_1 | L3-M5 | Ln | L\nu | L2-M1 | # +--------+---------------------+--------+-----+----------+----------+ # | La2 | L\alpha_1 | L3-M4 | Ma | M\alpha | M5-N6,7 | # +--------+---------------------+--------+-----+----------+----------+ # | Lb1 | L\beta_1 | L2-M4 | Mb | M\beta | M4-N6 | # +--------+---------------------+--------+-----+----------+----------+ # | Lb2,15 | L\beta_2,L\beta_{15}| L3-N4,5| Mg | M\gamma | M3-N5 | # +--------+---------------------+--------+-----+----------+----------+ # | Lb3 | L\beta_3 | L1-M3 | Mz | M\zeta | M4,5-N6,7| # +--------+---------------------+--------+-----+----------+----------+ if __name__ == "__main__": # see tests/examples in xdata_tests.py pass