Source code for mass.calibration.nist_xray_database

"""
nist_xray_database

Download the NIST x-ray line database from the website, and parse the
downloaded data into useable form.

For loading a file (locally, from disk) and plotting some information:
* NISTXrayDBFile
* plot_line_uncertainties

For updating the data files:
* NISTXrayDBRetrieve
* GetAllLines

Basic usage (assuming you put the x-ray files in
${MASS_HOME}/mass/calibration/nist_xray_data.dat):


J. Fowler, NIST
February 2014
"""
import os
import urllib
import pylab as plt

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', 'Md', 'No', 'Lr')
ATOMIC_NUMBERS = dict((ELEMENTS[i], i) for i in range(len(ELEMENTS)))


[docs] class NISTXrayDBFile: DEFAULT_FILENAMES = "nist_xray_data.dat", "low_z_xray_data.dat" def __init__(self, *filenames): """Initialize the database from 1 or more <filenames>, which point to files downloaded using NISTXrayDBRetrieve. If the list is empty (the default), then the file named by self.DEFAULT_FILENAME will be used.""" self.lines = {} self.alllines = set() if not filenames: path = os.path.split(__file__)[0] filenames = [os.path.join(path, df) for df in self.DEFAULT_FILENAMES] self.loaded_filenames = [] for filename in filenames: try: fp = open(filename, "r", encoding="utf-8") except OSError: print("'%s' is not a readable file with X-ray database info! Continuing..." % filename) continue while True: line = fp.readline() if "Theory" in line and "Blend" in line and "Ref." in line: break for textline in fp.readlines(): try: xrayline = NISTXrayLine(textline) self.lines[xrayline.name] = xrayline self.alllines.add(xrayline) except Exception: continue self.loaded_filenames.append(filename) fp.close() LINE_NICKNAMES = { 'KA1': 'KL3', 'KA2': 'KL2', 'KB1': 'KM3', 'KB3': 'KM2', 'KB5': 'KM5', 'LA1': 'L3M5', 'LA2': 'L3M4', 'Ll': 'L3M1', 'LB3': 'L1M3', 'LB1': 'L2M4', 'LB2': 'L3N5', 'LG1': 'L2N4', }
[docs] def get_lines_by_type(self, linetype): """Return a tuple containing all lines of a certain type, e.g., "KL3". See self.LINE_NICKNAMES for some known line "nicknames".""" linetype = linetype.upper() if "ALPHA" in linetype: linetype = linetype.replace("ALPHA", "A") elif "BETA" in linetype: linetype = linetype.replace("BETA", "B") elif "GAMMA" in linetype: linetype = linetype.replace("GAMMA", "G") linetype = self.LINE_NICKNAMES.get(linetype, linetype) lines = [] for element in ELEMENTS: linename = f'{element} {linetype}' if linename in self.lines: lines.append(self.lines[linename]) return tuple(lines)
def __getitem__(self, key): element, line = key.split()[:2] element = element.capitalize() line = line.upper() key = f'{element} {line}' if key in self.lines: return self.lines[key] lcline = line.lower() lcline = lcline.replace('alpha', 'a') lcline = lcline.replace('beta', 'b') lcline = lcline.replace('gamma', 'g') if lcline in self.LINE_NICKNAMES: key = f"{element} {self.LINE_NICKNAMES[lcline]}" return self.lines[key] raise KeyError("%s is not a known line or line nickname" % key)
[docs] class NISTXrayLine: DEFAULT_COLUMN_DEFS = {'element': (1, 4), 'transition': (10, 16), 'peak': (45, 59), 'peak_unc': (61, 72), 'blend': (74, 79), 'ref': (81, 91)} def __init__(self, textline, column_defs=None): if column_defs is None: column_defs = self.DEFAULT_COLUMN_DEFS for name, colrange in column_defs.items(): a = colrange[0] - 1 b = colrange[1] self.__dict__[name] = textline[a:b].rstrip() self.peak = float(self.peak) self.peak_unc = float(self.peak_unc) self.name = f'{self.element} {self.transition}' self.raw = textline.rstrip() def __str__(self): return f'{self.element} {self.transition} line: {self.peak:.3f} +- {self.peak_unc:.3f} eV' def __repr__(self): return self.raw
[docs] def plot_line_uncertainties(): db = NISTXrayDBFile() transitions = ('KL3', 'KL2', 'KM3', 'KM5', 'L3M5', 'L3M4', 'L2M4', 'L3N5', 'L2N4', 'L1M3', 'L3N7', 'L3M1') titles = { 'KL3': 'K$\\alpha_1$: Intense', 'KL2': 'K$\\alpha_2$: Intense, but not easily resolved', 'KM3': 'K$\\beta_1$: Intense', 'KM2': 'K$\\beta_3$: Intense, usually unresolvable', 'KM5': 'K$\\beta_5$: Weak line on high-E tail of K$\\beta_1$', 'L3M5': 'L$\\alpha_1$: Prominent', 'L3M4': 'L$\\alpha_2$: Small satellite', 'L2M4': 'L$\\beta_1$: Prominent', 'L3N5': 'L$\\beta_2$: Prominent', 'L2N4': 'K$\\gamma_1$: Weaker', 'L1M3': 'L$\\beta_3$: Weaker', 'L3N7': 'Lu: barely visible', 'L3M1': 'L$\\ell$: very weak', } axes = {} NX, NY = 3, 4 plt.clf() for i, tr in enumerate(transitions): axes[i] = plt.subplot(NY, NX, i + 1) plt.loglog() plt.grid(True) plt.title(titles[tr]) if i >= NX * (NY - 1): plt.xlabel("Line energy (eV)") if i % NX == 0: plt.ylabel("Line uncertainty (eV)") plt.ylim([1e-3, 10]) plt.xlim([100, 3e4]) for line in db.lines.values(): if line.transition not in transitions: continue i = transitions.index(line.transition) plt.sca(axes[i]) plt.plot(line.peak, line.peak_unc, 'or') plt.text(line.peak, line.peak_unc, line.name)
[docs] def plot_line_energies(): db = NISTXrayDBFile() plt.clf() cm = plt.cm.nipy_spectral transitions = ('KL2', 'KL3', 'KM5', 'KM3', 'KM2', 'L3M5', 'L3M4', 'L3M1', 'L2M4', 'L2N4', 'L3N5', 'L1M3', 'L3N7', 'M5N7', 'M5N6', 'M4N6', 'M3N5', 'M3N4') for i, linetype in enumerate(transitions): lines = db.get_lines_by_type(linetype) z = [ATOMIC_NUMBERS[line.element] for line in lines] e = [line.peak for line in lines] plt.loglog(z, e, 'o-', color=cm(float(i) / len(transitions)), label=linetype) plt.legend(loc='upper left') plt.xlim([6, 100]) plt.grid() r = list(range(6, 22)) + list(range(22, 43, 2)) + list(range(45, 75, 3)) + list(range(75, 100, 5)) plt.xticks(r, ['\n'.join([ELEMENTS[i], str(i)]) for i in r])
############################################################### # Below here are functions to recreate the nist_xray_data.dat # file, which I don't think anyone will ever need again. # J Fowler, Feb 28, 2014. ############################################################### def _NISTXrayDBRetrieve(line_names, savefile, min_E=150, max_E=25000): """Use this for updating the database file. You should not ever (?) need to do this, but who knows?""" form = "http://physics.nist.gov/cgi-bin/XrayTrans/search.pl?" args = {'download': 'column', 'element': 'All', 'units': 'eV', 'lower': str(min_E), 'upper': str(max_E)} joined_args = '&'.join([f'{k}={v}' for (k, v) in args.items()]) joined_lines = '&'.join(['trans=%s' % name for name in line_names]) get = f'{form}{joined_args}&{joined_lines}' print('Grabbing %s' % get) page = urllib.urlopen(get) fp = open(savefile, "w", encoding="utf-8") fp.writelines(page) fp.close() def _RetrieveAllLines(savefile, max_E=30000): """Use this for updating the database file. You should not ever (?) need to do this, but who knows?""" lines = ('KL2', 'KL3', 'KM5', 'KM3', 'KM2', 'L3M5', 'L3M4', 'L3M1', 'L2M4', 'L2N4', 'L3N5', 'L1M3', 'L3N7') _NISTXrayDBRetrieve(lines, savefile, max_E=max_E)