def test_edge_energies(): from xraydb.xray import _edge_energies assert xray_edge(30, 'K') == xray_edge('Zn', 'K') for edge in ('k', 'l3', 'l2'): for iz in range(1, 98): _edge = xray_edge(iz, edge) if _edge is not None: en = _edge.energy assert _edge_energies[edge][iz] < en + 0.5 assert _edge_energies[edge][iz] > en - 0.5 assert_allclose(xray_edge('Zn', 'K', energy_only=True), 9660, rtol=0.01) sr_edges = xray_edges('Sr') assert_allclose(sr_edges['K'].energy, 16105.0, rtol=0.001) assert_allclose(sr_edges['L1'].energy, 2216.0, rtol=0.001) assert_allclose(sr_edges['L2'].energy, 2007.0, rtol=0.001) assert_allclose(sr_edges['L3'].energy, 1940.0, rtol=0.001) assert_allclose(sr_edges['M1'].energy, 358.7, rtol=0.001) assert_allclose(sr_edges['M2'].energy, 280.3, rtol=0.001) assert_allclose(sr_edges['M3'].energy, 270.0, rtol=0.001) assert_allclose(sr_edges['M4'].energy, 136.0, rtol=0.001) assert_allclose(sr_edges['M5'].energy, 134.2, rtol=0.001) assert_allclose(sr_edges['N1'].energy, 38.9, rtol=0.001) assert_allclose(sr_edges['N2'].energy, 21.6, rtol=0.001) assert_allclose(sr_edges['N3'].energy, 20.1, rtol=0.001) assert_allclose(sr_edges['K'].fyield, 0.664652, rtol=0.001) assert_allclose(sr_edges['L1'].fyield, 0.0051, rtol=0.001) assert_allclose(sr_edges['L2'].fyield, 0.024, rtol=0.001) assert_allclose(sr_edges['L3'].fyield, 0.026, rtol=0.001) assert_allclose(sr_edges['M1'].fyield, 5.95e-05, rtol=0.001) assert_allclose(sr_edges['M2'].fyield, 3.7e-05, rtol=0.001) assert_allclose(sr_edges['M3'].fyield, 0.000105, rtol=0.001) assert_allclose(sr_edges['M4'].fyield, 0.0027, rtol=0.001) assert_allclose(sr_edges['M5'].fyield, 0.0, rtol=0.001) assert_allclose(sr_edges['N1'].fyield, 1.2e-05, rtol=0.001) assert_allclose(sr_edges['N2'].fyield, 0.013, rtol=0.001) assert_allclose(sr_edges['N3'].fyield, 0.013, rtol=0.001) assert_allclose(sr_edges['K'].jump_ratio, 6.888, rtol=0.01) assert_allclose(sr_edges['L1'].jump_ratio, 1.1415, rtol=0.01) assert_allclose(sr_edges['L2'].jump_ratio, 1.4, rtol=0.01) assert_allclose(sr_edges['L3'].jump_ratio, 3.982, rtol=0.01) assert_allclose(sr_edges['M1'].jump_ratio, 1.04, rtol=0.01) assert_allclose(sr_edges['M2'].jump_ratio, 1.058, rtol=0.01) assert_allclose(sr_edges['M3'].jump_ratio, 1.12491, rtol=0.01) assert_allclose(sr_edges['M4'].jump_ratio, 1.139, rtol=0.01) assert_allclose(sr_edges['M5'].jump_ratio, 1.808, rtol=0.01) assert_allclose(sr_edges['N1'].jump_ratio, 1.0, rtol=0.01) assert_allclose(sr_edges['N2'].jump_ratio, 1.0, rtol=0.01) assert_allclose(sr_edges['N3'].jump_ratio, 1.0, rtol=0.01)
def predict_escape(self, det='Si', scale=1.0): """ predict detector escape for a spectrum, save to 'escape' attribute X-rays penetrate a depth 1/mu(material, energy) and the detector fluorescence escapes from that depth as exp(-mu(material, KaEnergy)*thickness) with a fluorecence yield of the material """ fluor_en = xray_line(det, 'Ka').energy / 1000.0 edge = xray_edge(det, 'K') # here we use the 1/e depth for the emission # and the 1/e depth of the incident beam: # the detector fluorescence can escape on either side mu_emit = material_mu(det, fluor_en * 1000) mu_input = material_mu(det, self.energy * 1000) escape = 0.5 * scale * edge.fyield * np.exp(-mu_emit / (2 * mu_input)) escape[np.where(self.energy * 1000 < edge.energy)] = 0.0 escape *= interp(self.energy - fluor_en, self.counts * 1.0, self.energy, kind='cubic') self.escape = escape
def read_energy_label(self): label_text = self.lineEdit_energy.text() try: energy = float(label_text) except ValueError: element, edge = label_text.split('-') energy = xraydb.xray_edge(element, edge).energy self.lineEdit_energy.setText(str(int(energy))) return energy
def fluo_corr(norm, formula, elem, edge, line, anginp, angout): """ Correct over-absorption (self-absorption) for fluorescene XAFS using the based on the `larch` implementation of the FLUO alogrithm of D. Haskel. See FLUO manual_ for details. .. _manual: https://www3.aps.anl.gov/haskel/fluo.html Parameters ---------- norm : iter Normalized fluorescence formula : str Chemical formula of the compound. For example: 'EuO'. elem : str Element of interest. For example: 'Eu'. edge : str Absorption edge of interest. For example: 'L3'. line : str Fluorescence line measured. For example: 'La'. anginp : float Input angle with respect to the sample surface. See FLUO manual. anginp : float Output angle with respect to the sample surface. See FLUO manual. Returns -------- norm_corr : numpy.array Corrected normalized fluorescence. """ # Angular correction. Avoid divide by zero. ang_corr = (np.sin(np.deg2rad(anginp)) / np.sin(max(1.e-7, np.deg2rad(angout)))) # Find edge energies and fluorescence line energy e_edge = xray_edge(elem, edge).energy e_fluor = xray_line(elem, line).energy # Calculate mu(E) for fluorescence energy, above, below edge # alpha ends up independent of density! muvals = material_mu(formula, np.array([e_fluor, e_edge - 10.0, e_edge + 10.0]), density=1) # Do the correction alpha = (muvals[0] * ang_corr + muvals[1]) / (muvals[2] - muvals[1]) norm_corr = np.array(norm) * alpha / (alpha + 1 - np.array(norm)) return norm_corr
def calc_escape_scale(self, energy, thickness=None): """ calculate energy dependence of escape effect X-rays penetrate a depth 1/mu(material, energy) and the detector fluorescence escapes from that depth as exp(-mu(material, KaEnergy)*thickness) with a fluorecence yield of the material """ det = self.detector # note material_mu, xray_edge, xray_line work in eV! escape_energy_ev = xray_line(det.material, 'Ka').energy mu_emit = material_mu(det.material, escape_energy_ev) self.escape_energy = 0.001 * escape_energy_ev mu_input = material_mu(det.material, 1000 * energy) edge = xray_edge(det.material, 'K') self.escape_scale = edge.fyield * np.exp(-mu_emit / (2 * mu_input)) self.escape_scale[np.where(energy < 0.001 * edge.energy)] = 0.0
def fluo_corr(energy, mu, formula, elem, group=None, edge='K', anginp=45, angout=45, _larch=None, **pre_kws): """correct over-absorption (self-absorption) for fluorescene XAFS using the FLUO alogrithm of D. Haskel. Arguments --------- energy array of energies mu uncorrected fluorescence mu formula string for sample stoichiometry elem atomic symbol or Z of absorbing element group output group [default None] edge name of edge ('K', 'L3', ...) [default 'K'] anginp input angle in degrees [default 45] angout output angle in degrees [default 45] Additional keywords will be passed to pre_edge(), which will be used to ensure consistent normalization. Returns -------- None, writes `mu_corr` and `norm_corr` (normalized `mu_corr`) to output group. Notes ----- Support First Argument Group convention, requiring group members 'energy' and 'mu' """ energy, mu, group = parse_group_args(energy, members=('energy', 'mu'), defaults=(mu, ), group=group, fcn_name='fluo_corr') # gather pre-edge options pre_opts = { 'e0': None, 'nnorm': 1, 'nvict': 0, 'pre1': None, 'pre2': -30, 'norm1': 100, 'norm2': None } if hasattr(group, 'pre_edge_details'): uopts = getattr(group.pre_edge_details, 'call_args', {}) for attr in pre_opts: if attr in uopts: pre_opts[attr] = uopts[attr] pre_opts.update(pre_kws) pre_opts['step'] = None pre_opts['nvict'] = 0 # generate normalized mu for correction preinp = preedge(energy, mu, **pre_opts) ang_corr = (np.sin(max(1.e-7, np.deg2rad(anginp))) / np.sin(max(1.e-7, np.deg2rad(angout)))) # find edge energies and fluorescence line energy e_edge = xray_edge(elem, edge).energy e_fluor = xray_line(elem, edge).energy # calculate mu(E) for fluorescence energy, above, below edge muvals = material_mu(formula, np.array([e_fluor, e_edge - 10.0, e_edge + 10.0]), density=1) alpha = (muvals[0] * ang_corr + muvals[1]) / (muvals[2] - muvals[1]) mu_corr = mu * alpha / (alpha + 1 - preinp['norm']) preout = preedge(energy, mu_corr, **pre_opts) if group is not None: if _larch is not None: group = set_xafsGroup(group, _larch=_larch) group.mu_corr = mu_corr group.norm_corr = preout['norm']
def mback_norm(energy, mu=None, group=None, z=None, edge='K', e0=None, pre1=None, pre2=None, norm1=None, norm2=None, nnorm=None, nvict=1, _larch=None): """ simplified version of MBACK to Match mu(E) data for tabulated f''(E) for normalization Arguments: energy, mu: arrays of energy and mu(E) group: output group (and input group for e0) z: Z number of absorber e0: edge energy pre1: low E range (relative to E0) for pre-edge fit pre2: high E range (relative to E0) for pre-edge fit norm1: low E range (relative to E0) for post-edge fit norm2: high E range (relative to E0) for post-edge fit nnorm: degree of polynomial (ie, nnorm+1 coefficients will be found) for post-edge normalization curve fit to the scaled f2. Default=1 (linear) Returns: group.norm_poly: normalized mu(E) from pre_edge() group.norm: normalized mu(E) from this method group.mback_mu: tabulated f2 scaled and pre_edge added to match mu(E) group.mback_params: Group of parameters for the minimization References: * MBACK (Weng, Waldo, Penner-Hahn): http://dx.doi.org/10.1086/303711 * Chantler: http://dx.doi.org/10.1063/1.555974 """ ### implement the First Argument Group convention energy, mu, group = parse_group_args(energy, members=('energy', 'mu'), defaults=(mu,), group=group, fcn_name='mback') if len(energy.shape) > 1: energy = energy.squeeze() if len(mu.shape) > 1: mu = mu.squeeze() if _larch is not None: group = set_xafsGroup(group, _larch=_larch) group.norm_poly = group.norm*1.0 if z is not None: # need to run find_e0: e0_nominal = xray_edge(z, edge).energy if e0 is None: e0 = getattr(group, 'e0', None) if e0 is None: find_e0(energy, mu, group=group) e0 = group.e0 atsym = None if z is None or z < 2: atsym, edge = guess_edge(group.e0) z = atomic_number(atsym) if atsym is None and z is not None: atsym = atomic_symbol(z) if getattr(group, 'pre_edge_details', None) is None: # pre_edge never run preedge(energy, mu, pre1=pre1, pre2=pre2, nvict=nvict, norm1=norm1, norm2=norm2, e0=e0, nnorm=nnorm) mu_pre = mu - group.pre_edge f2 = f2_chantler(z, energy) weights = np.ones(len(energy))*1.0 if norm2 is None: norm2 = max(energy) - e0 if norm2 < 0: norm2 = max(energy) - e0 - norm2 # avoid l2 and higher edges if edge.lower().startswith('l'): if edge.lower() == 'l3': e_l2 = xray_edge(z, 'L2').energy norm2 = min(norm2, e_l2-e0) elif edge.lower() == 'l2': e_l2 = xray_edge(z, 'L1').energy norm2 = min(norm2, e_l1-e0) ipre2 = index_of(energy, e0+pre2) inor1 = index_of(energy, e0+norm1) inor2 = index_of(energy, e0+norm2) + 1 weights[ipre2:] = 0.0 weights[inor1:inor2] = np.linspace(0.1, 1.0, inor2-inor1) params = Parameters() params.add(name='slope', value=0.0, vary=True) params.add(name='offset', value=-f2[0], vary=True) params.add(name='scale', value=f2[-1], vary=True) out = minimize(f2norm, params, method='leastsq', gtol=1.e-5, ftol=1.e-5, xtol=1.e-5, epsfcn=1.e-5, kws = dict(en=energy, mu=mu_pre, f2=f2, weights=weights)) p = out.params.valuesdict() model = (p['offset'] + p['slope']*energy + f2) * p['scale'] group.mback_mu = model + group.pre_edge pre_f2 = preedge(energy, model, nnorm=nnorm, nvict=nvict, e0=e0, pre1=pre1, pre2=pre2, norm1=norm1, norm2=norm2) step_new = pre_f2['edge_step'] group.edge_step_poly = group.edge_step group.edge_step_mback = step_new group.norm_mback = mu_pre / step_new group.mback_params = Group(e0=e0, pre1=pre1, pre2=pre2, norm1=norm1, norm2=norm2, nnorm=nnorm, fit_params=p, fit_weights=weights, model=model, f2=f2, pre_f2=pre_f2, atsym=atsym, edge=edge) if (abs(step_new - group.edge_step)/(1.e-13+group.edge_step)) > 0.75: print("Warning: mback edge step failed....") else: group.edge_step = step_new group.norm = group.norm_mback