Source code for galore.cross_sections

import os.path
from pkg_resources import resource_filename
from json import load as json_load
from collections import Iterable

import sqlite3
from scipy import polyval
from numpy import fromstring as np_fromstr
from numpy import exp, log


[docs]def get_cross_sections(weighting, elements=None): """Get photoionization cross-section weighting data. For known sources, data is based on tabulation of Yeh/Lindau (1985).[1] Otherwise, energies in keV from 1-1500 are used with log-log polynomial parametrisation of data from Scofield.[2] References: 1. Yeh, J.J. and Lindau, I. (1985) Atomic Data and Nuclear Data Tables 32 pp 1-155 2. J. H. Scofield (1973) Lawrence Livermore National Laboratory Report No. UCRL-51326 Args: weighting (str or float): Data source for photoionization cross-sections. If the string is a known keyword then data will be drawn from files included with Galore. Otherwise, the string will be interpreted as a path to a JSON file containing data arranged in the same way as the output of this function. elements (iterable or None): Collection of element symbols to include in the data set. If None, a full set of available elements will be included. When using a JSON dataset (including the inbuilt Yeh/Lindau) this parameter will be ignored as the entire dataset has already been loaded into memory. Returns: dict: Photoionization cross-section weightings arranged by element and orbital as nested dictionaries of floats, i.e.:: {el1: {orb1: cs11, orb2: cs12, ...}, el2: {orb1: cs21, orb2: cs22, ...}, ... } In addition the keys "reference", "link", "energy" and "warning" may be used to store metadata. """ try: energy = float(weighting) return get_cross_sections_scofield(energy, elements=elements) except ValueError: if isinstance(weighting, str): if weighting.lower() in ('alka', 'he2', 'yeh_haxpes'): return get_cross_sections_yeh(weighting) elif weighting.lower() in ('xps', 'ups', 'haxpes'): raise ValueError("Key '{0}' is no longer accepted for " "weighting. Please use 'alka' for Al k-alpha," " 'he2' for He (II) or 'yeh_haxpes' for " "8047.8 eV HAXPES".format(weighting)) else: return get_cross_sections_json(weighting) # A string or a number would have hit a return statement by now raise ValueError("Weighting not understood as string or float. ", "Please use a keyword, path to JSON file or an " "energy value in eV")
[docs]def cross_sections_info(cross_sections, logging=None): """Log basic info from cross-sections dict. Args: cross_sections (dict): The keys 'energy', 'citation', 'link' and 'warning' are checked for relevant information logging (module): Active logging module from Python standard library. If None, logging will be set up. Returns: module: Active logging module from Python standard library """ if logging is None: import logging logging.basicConfig(filename='galore.log', level=logging.INFO) console = logging.StreamHandler() logging.getLogger().addHandler(console) if 'energy' in cross_sections: logging.info(" Photon energy: {0}".format(cross_sections['energy'])) if 'citation' in cross_sections: logging.info(" Citation: {0}".format(cross_sections['citation'])) if 'link' in cross_sections: logging.info(" Link: {0}".format(cross_sections['link'])) return logging
[docs]def get_cross_sections_json(path): """Get valence-band cross-sections from JSON file Read photoionization data from a JSON file. File is expected to contain data for multiple elements and orbitals in the form ``{El1: {orb1: c1, orb2: c2, ...}, ...}``. While it is expected that Galore will be used to examine valence-band orbitals labelled (s, p, d, f) it may be helpful in some cases to prepare a file with alternative orbital labels corresponding to the pDOS labels. The labels 'citation', 'energy' and 'link' are reserved for metadata which may be displayed in the program log. The label 'comment' may be used for additional material in the JSON file; it is recommended to use this repeatedly for line-breaks, e.g.:: {"comment": "First line of text", "comment": "which is continued.", ...} Args: path (str): Path to JSON file Returns: dict: Weighted photoionization cross-sections for each element and orbital in form:: {el1: {'s': c11, 'p': c12, ... }, el2: {'s': c21, 'p': c22, ... }, ... } in tabulated units. """ if os.path.exists(path): with open(path, 'r') as f: cross_sections = json_load(f) else: raise IOError("Cross-sections file {0} does not " "exist!".format(path)) return cross_sections
[docs]def get_cross_sections_yeh(source): """Get valence-band cross-sections from tabulated data Tabulated values of photoionization cross-sections were drawn from ref [1] for energy values corresponding to relevant radiation sources: - 1486.6 eV, corresponding to Al k-alpha (laboratory XPS) - 40.8 eV, corresponding to He II (laboratory UPS) - 8047.8 eV, corresponding to a possible HAXPES source References: 1. Yeh, J.J. and Lindau, I. (1985) Atomic Data and Nuclear Data Tables 32 pp 1-155 Args: source (str): Label corresponding to radiation source. Accepted values 'alka' (1486.6 eV), 'he2' (40.8 eV), 'yeh_haxpes' (8047.8). These keys are not case-sensitive and correspond to Al k-alpha, He(II) and hard x-ray sources. Returns: dict: Weighted photoionization cross-sections in megaBarns/electron for each orbital in form:: {el1: {'s': c11, 'p': c12, ... }, el2: {'s': c21, 'p': c22, ... }, ... } """ weighting_files = {'alka': resource_filename( __name__, "data/cross_sections.json"), 'he2': resource_filename( __name__, "data/cross_sections_ups.json"), 'yeh_haxpes': resource_filename( __name__, "data/cross_sections_haxpes.json")} if source.lower() in weighting_files: path = weighting_files[source.lower()] return get_cross_sections_json(path) else: raise Exception( "Energy source '{0}' not recognised. ".format(source), "Accepted values: {0}".format(", ".join((weighting_files.keys()))))
[docs]def get_cross_sections_scofield(energy, elements=None): """Get valence-band cross-sections from fitted data Energy-dependent cross-sections have been averaged and weighted for the uppermost s, p, d, f orbitals from data tabulated by Scofield. The energy/cross-section relationship was fitted to an order-8 polynomial on a log-log scale. Multiple energy values can be evaluated simultaneously by passing an array-like group of energies as ``energy``. In this case the cross-section values will be arrays with the same shape as the energy arrays. Args: energy (float or array-like): Incident energy in keV element (iterable or None): Iterable (e.g. list) of element symbols. If None, include all available elements (1 <= Z <= 100). Returns: dict: Weighted photoionization cross-sections in Barns/electron for each orbital in form:: {el1: {'s': c11, 'p': c12, ... }, el2: {'s': c21, 'p': c22, ... }, ... } Raises: ValueError: Energy values must lie within interpolation range 1--1500keV """ min_energy, max_energy = 1., 1500. def _low_value(energy): raise ValueError("Scofield data not available below 1 keV: refusing" " to extrapolate to {0} keV".format(energy)) def _high_value(energy): raise ValueError("Scofield data not available above 1500 keV: refusing" " to extrapolate to {0} keV".format(energy)) if isinstance(energy, Iterable): if min(energy) < min_energy: _low_value(energy) elif max(energy) > max_energy: _high_value(energy) else: if energy < min_energy: _low_value(energy) elif energy > max_energy: _high_value(energy) db_file = resource_filename(__name__, "data/scofield_data.db") if elements is None: elements = ['H', 'He', 'Li', 'Be', 'B', 'C', 'N', 'O', 'F', 'Ne', 'Na', 'Mg', 'Al', 'Si', 'P', 'S', 'Cl', 'Ar', 'K', 'Ca', 'Sc', 'Ti', 'V', 'Cr', 'Mn', 'Fe', 'Co', 'Ni', 'Cu', 'Zn', 'Ga', 'Ge', 'As', 'Se', 'Br', 'Kr', 'Rb', 'Sr', 'Y', 'Zr', 'Nb', 'Mo', 'Tc', 'Ru', 'Rh', 'Pd', 'Ag', 'Cd', 'In', 'Sn', 'Sb', 'Te', 'I', 'Xe', 'Cs', 'Ba', 'La', 'Ce', 'Pr', 'Nd', 'Pm', 'Sm', 'Eu', 'Gd', 'Tb', 'Dy', 'Ho', 'Er', 'Tm', 'Yb', 'Lu', 'Hf', 'Ta', 'W', 'Re', 'Os', 'Ir', 'Pt', 'Au', 'Hg', 'Tl', 'Pb', 'Bi', 'Po', 'At', 'Rn', 'Fr', 'Ra', 'Ac', 'Th', 'Pa', 'U', 'Np', 'Pu', 'Am', 'Cm', 'Bk', 'Cf', 'Es', 'Fm'] def _eval_fit(energy, coeffs): """Convert log-log polynomial fit to cross-section value""" log_val = polyval(coeffs, log(energy)) return exp(log_val) el_cross_sections = {'energy': '{0} keV'.format(energy), 'citation': "J. H. Scofield (1973) Lawrence Livermore" " National Laboratory " "Report No. UCRL-51326, \n" "Parametrised as log-log order 8 " "polynomial (A. J. Jackson 2018)", 'link': "https://doi.org/10.2172/4545040"} with sqlite3.connect(db_file) as con: for element in elements: cur = con.cursor() cur.execute('SELECT orbital, coeffs_np FROM fits WHERE Element=?;', [element]) orbitals_fits = cur.fetchall() el_cross_sections.update({element: { orb: _eval_fit(energy, np_fromstr(coeffs)) for orb, coeffs in orbitals_fits}}) return el_cross_sections