def guess_edge(energy, edges=['K', 'L3', 'L2', 'L1', 'M5'], _larch=None): """guess an element and edge based on energy (in eV) Arguments --------- energy (float) : approximate edge energy (in eV) edges (list of strings) : edges to consider ['K', 'L3', 'L2', 'L1', 'M5'] Returns ------- (element symbol, edge) """ xdb = get_xraydb(_larch) ret = [] min_diff = 1e9 for edge in edges: ename = edge.lower() # if not already in _edge_energies, look it up and save it now if ename not in _edge_energies: energies = [-1000]*150 maxz = 0 for row in xdb.tables['xray_levels'].select().execute().fetchall(): ir, elem, edgename, en, eyield, xjump = row iz = xdb.atomic_number(elem) maxz = max(iz, maxz) if ename == edgename.lower(): energies[iz] = en _edge_energies[ename] = np.array(energies[:maxz]) if _larch is not None: symname = '%s._edges_%s' % (MODNAME, ename) _larch.symtable.set_symbol(symname, _edge_energies[ename]) energies = _edge_energies[ename] iz = int(index_nearest(energies, energy)) diff = energy - energies[iz] if diff < 0: # prefer positive errors diff = -2.0*diff if iz < 10 or iz > 92: # penalize extreme elements diff = 2.0*diff if edge == 'K': # prefer K edge diff = 0.25*diff elif edge in ('L1', 'M5'): # penalize L1 and M5 edges diff = 2.0*diff if diff < min_diff: min_diff = diff ret.append((edge, iz, diff)) for edge, iz, diff in ret: if abs(diff - min_diff) < 2: return (atomic_symbol(iz), edge) return (None, None)
def preedge(energy, mu, e0=None, step=None, nnorm=None, nvict=0, pre1=None, pre2=-50, norm1=100, norm2=None): """pre edge subtraction, normalization for XAFS (straight python) This performs a number of steps: 1. determine E0 (if not supplied) from max of deriv(mu) 2. fit a line of polymonial to the region below the edge 3. fit a polymonial to the region above the edge 4. extrapolae the two curves to E0 to determine the edge jump Arguments ---------- energy: array of x-ray energies, in eV mu: array of mu(E) e0: edge energy, in eV. If None, it will be determined here. step: edge jump. If None, it will be determined here. pre1: low E range (relative to E0) for pre-edge fit pre2: high E range (relative to E0) for pre-edge fit nvict: energy exponent to use for pre-edg fit. See Note 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. Default=None -- see note. Returns ------- dictionary with elements (among others) e0 energy origin in eV edge_step edge step norm normalized mu(E) pre_edge determined pre-edge curve post_edge determined post-edge, normalization curve Notes ----- 1 nvict gives an exponent to the energy term for the fits to the pre-edge and the post-edge region. For the pre-edge, a line (m * energy + b) is fit to mu(energy)*energy**nvict over the pre-edge region, energy=[e0+pre1, e0+pre2]. For the post-edge, a polynomial of order nnorm will be fit to mu(energy)*energy**nvict of the post-edge region energy=[e0+norm1, e0+norm2]. 2 nnorm will default to 2 in norm2-norm1>300, to 1 if 100>norm2-norm1>300, and to 0 in norm2-norm1<100. """ energy = remove_dups(energy) if e0 is None or e0 < energy[1] or e0 > energy[-2]: e0 = _finde0(energy, mu) ie0 = index_nearest(energy, e0) e0 = energy[ie0] pre1_input = pre1 norm2_input = norm2 if pre1 is None: pre1 = min(energy) - e0 if norm2 is None: norm2 = max(energy) - e0 if norm2 < 0: norm2 = max(energy) - e0 - norm2 pre1 = max(pre1, (min(energy) - e0)) norm2 = min(norm2, (max(energy) - e0)) if pre1 > pre2: pre1, pre2 = pre2, pre1 if norm1 > norm2: norm1, norm2 = norm2, norm1 p1 = index_of(energy, pre1+e0) p2 = index_nearest(energy, pre2+e0) if p2-p1 < 2: p2 = min(len(energy), p1 + 2) omu = mu*energy**nvict ex, mx = remove_nans2(energy[p1:p2], omu[p1:p2]) precoefs = polyfit(ex, mx, 1) pre_edge = (precoefs[0] * energy + precoefs[1]) * energy**(-nvict) # normalization p1 = index_of(energy, norm1+e0) p2 = index_nearest(energy, norm2+e0) if p2-p1 < 2: p2 = min(len(energy), p1 + 2) if nnorm is None: nnorm = 0 if norm2-norm1 > 100: nnorm = 1 if norm2-norm1 > 400: nnorm = 2 nnorm = max(min(nnorm, MAX_NNORM), 0) presub = (mu-pre_edge)[p1:p2] coefs = polyfit(energy[p1:p2], presub, nnorm) post_edge = 1.0*pre_edge norm_coefs = [] for n, c in enumerate(reversed(list(coefs))): post_edge += c * energy**(n) norm_coefs.append(c) edge_step = step if edge_step is None: edge_step = post_edge[ie0] - pre_edge[ie0] norm = (mu - pre_edge)/edge_step return {'e0': e0, 'edge_step': edge_step, 'norm': norm, 'pre_edge': pre_edge, 'post_edge': post_edge, 'norm_coefs': norm_coefs, 'nvict': nvict, 'nnorm': nnorm, 'norm1': norm1, 'norm2': norm2, 'pre1': pre1, 'pre2': pre2, 'precoefs': precoefs, 'norm2_input': norm2_input, 'pre1_input': pre1_input}
def pre_edge(energy, mu=None, group=None, e0=None, step=None, nnorm=None, nvict=0, pre1=None, pre2=-50, norm1=100, norm2=None, make_flat=True, emin_area=None, _larch=None): """pre edge subtraction, normalization for XAFS This performs a number of steps: 1. determine E0 (if not supplied) from max of deriv(mu) 2. fit a line of polymonial to the region below the edge 3. fit a polymonial to the region above the edge 4. extrapolae the two curves to E0 to determine the edge jump 5. estimate area from emin_area to norm2, to get norm_area Arguments ---------- energy: array of x-ray energies, in eV, or group (see note) mu: array of mu(E) group: output group e0: edge energy, in eV. If None, it will be determined here. step: edge jump. If None, it will be determined here. pre1: low E range (relative to E0) for pre-edge fit pre2: high E range (relative to E0) for pre-edge fit nvict: energy exponent to use for pre-edg fit. See Note 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. Default=None (see note) make_flat: boolean (Default True) to calculate flattened output. emin_area: energy threshold for area normalization (see note) Returns ------- None The following attributes will be written to the output group: e0 energy origin edge_step edge step norm normalized mu(E), using polynomial norm_area normalized mu(E), using integrated area flat flattened, normalized mu(E) pre_edge determined pre-edge curve post_edge determined post-edge, normalization curve dmude derivative of mu(E) (if the output group is None, _sys.xafsGroup will be written to) Notes ----- 1 If the first argument is a Group, it must contain 'energy' and 'mu'. If it exists, group.e0 will be used as e0. See First Argrument Group in Documentation 2 nvict gives an exponent to the energy term for the fits to the pre-edge and the post-edge region. For the pre-edge, a line (m * energy + b) is fit to mu(energy)*energy**nvict over the pre-edge region, energy=[e0+pre1, e0+pre2]. For the post-edge, a polynomial of order nnorm will be fit to mu(energy)*energy**nvict of the post-edge region energy=[e0+norm1, e0+norm2]. 3 nnorm will default to 2 in norm2-norm1>400, to 1 if 100>norm2-norm1>300, and to 0 in norm2-norm1<100. 4 norm_area will be estimated so that the area between emin_area and norm2 is equal to (norm2-emin_area). By default emin_area will be set to the *nominal* edge energy for the element and edge - 3*core_level_width """ energy, mu, group = parse_group_args(energy, members=('energy', 'mu'), defaults=(mu,), group=group, fcn_name='pre_edge') if len(energy.shape) > 1: energy = energy.squeeze() if len(mu.shape) > 1: mu = mu.squeeze() pre_dat = preedge(energy, mu, e0=e0, step=step, nnorm=nnorm, nvict=nvict, pre1=pre1, pre2=pre2, norm1=norm1, norm2=norm2) group = set_xafsGroup(group, _larch=_larch) e0 = pre_dat['e0'] norm = pre_dat['norm'] norm1 = pre_dat['norm1'] norm2 = pre_dat['norm2'] # generate flattened spectra, by fitting a quadratic to .norm # and removing that. flat = norm ie0 = index_nearest(energy, e0) p1 = index_of(energy, norm1+e0) p2 = index_nearest(energy, norm2+e0) if p2-p1 < 2: p2 = min(len(energy), p1 + 2) if make_flat and p2-p1 > 4: enx, mux = remove_nans2(energy[p1:p2], norm[p1:p2]) # enx, mux = (energy[p1:p2], norm[p1:p2]) fpars = Parameters() ncoefs = len(pre_dat['norm_coefs']) fpars.add('c0', value=0, vary=True) fpars.add('c1', value=0, vary=(ncoefs>1)) fpars.add('c2', value=0, vary=(ncoefs>2)) fit = Minimizer(flat_resid, fpars, fcn_args=(enx, mux)) result = fit.leastsq(xtol=1.e-6, ftol=1.e-6) fc0 = result.params['c0'].value fc1 = result.params['c1'].value fc2 = result.params['c2'].value flat_diff = fc0 + energy * (fc1 + energy * fc2) flat = norm - (flat_diff - flat_diff[ie0]) flat[:ie0] = norm[:ie0] group.e0 = e0 group.norm = norm group.norm_poly = 1.0*norm group.flat = flat group.dmude = np.gradient(mu)/np.gradient(energy) group.edge_step = pre_dat['edge_step'] group.edge_step_poly = pre_dat['edge_step'] group.pre_edge = pre_dat['pre_edge'] group.post_edge = pre_dat['post_edge'] group.pre_edge_details = Group() group.pre_edge_details.pre1 = pre_dat['pre1'] group.pre_edge_details.pre2 = pre_dat['pre2'] group.pre_edge_details.nnorm = pre_dat['nnorm'] group.pre_edge_details.norm1 = pre_dat['norm1'] group.pre_edge_details.norm2 = pre_dat['norm2'] group.pre_edge_details.nvict = pre_dat['nvict'] group.pre_edge_details.pre1_input = pre_dat['pre1_input'] group.pre_edge_details.norm2_input = pre_dat['norm2_input'] group.pre_edge_details.pre_slope = pre_dat['precoefs'][0] group.pre_edge_details.pre_offset = pre_dat['precoefs'][1] for i in range(MAX_NNORM): if hasattr(group, 'norm_c%i' % i): delattr(group, 'norm_c%i' % i) for i, c in enumerate(pre_dat['norm_coefs']): setattr(group.pre_edge_details, 'norm_c%i' % i, c) # guess element and edge group.atsym = getattr(group, 'atsym', None) group.edge = getattr(group, 'edge', None) if group.atsym is None or group.edge is None: _atsym, _edge = guess_edge(group.e0, _larch=_larch) if group.atsym is None: group.atsym = _atsym if group.edge is None: group.edge = _edge # calcuate area-normalization if emin_area is None: emin_area = (xray_edge(group.atsym, group.edge).edge - 2*core_width(group.atsym, group.edge)) i1 = index_of(energy, emin_area) i2 = index_of(energy, e0+norm2) en = energy[i1:i2] area_step = max(1.e-15, simps(norm[i1:i2], en) / en.ptp()) group.edge_step_area = group.edge_step_poly * area_step group.norm_area = norm/area_step group.pre_edge_details.emin_area = emin_area return
def preedge(energy, mu, e0=None, step=None, nnorm=None, nvict=0, pre1=None, pre2=None, norm1=None, norm2=None): """pre edge subtraction, normalization for XAFS (straight python) This performs a number of steps: 1. determine E0 (if not supplied) from max of deriv(mu) 2. fit a line to the region below the edge 3. fit a polymonial to the region above the edge 4. extrapolate the two curves to E0 and take their difference to determine the edge jump Arguments ---------- energy: array of x-ray energies, in eV mu: array of mu(E) e0: edge energy, in eV. If None, it will be determined here. step: edge jump. If None, it will be determined here. pre1: low E range (relative to E0) for pre-edge fit pre2: high E range (relative to E0) for pre-edge fit nvict: energy exponent to use for pre-edg fit. See Note 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. Default=None -- see note. Returns ------- dictionary with elements (among others) e0 energy origin in eV edge_step edge step norm normalized mu(E) pre_edge determined pre-edge curve post_edge determined post-edge, normalization curve Notes ----- 1 pre_edge: a line is fit to mu(energy)*energy**nvict over the region, energy=[e0+pre1, e0+pre2]. pre1 and pre2 default to None, which will set pre1 = e0 - 2nd energy point, rounded to 5 eV pre2 = roughly pre1/3.0, rounded to 5 eV 2 post-edge: a polynomial of order nnorm is fit to mu(energy)*energy**nvict between energy=[e0+norm1, e0+norm2]. nnorm, norm1, norm2 default to None, which will set: nnorm = 2 in norm2-norm1>350, 1 if norm2-norm1>50, or 0 if less. norm2 = max energy - e0, rounded to 5 eV norm1 = roughly min(150, norm2/3.0), rounded to 5 eV """ energy = remove_dups(energy) if e0 is None or e0 < energy[1] or e0 > energy[-2]: e0 = _finde0(energy, mu) ie0 = index_nearest(energy, e0) e0 = energy[ie0] if pre1 is None: # skip first energy point, often bad if ie0 > 20: pre1 = 5.0 * round((energy[1] - e0) / 5.0) else: pre1 = 2.0 * round((energy[1] - e0) / 2.0) pre1 = max(pre1, (min(energy) - e0)) if pre2 is None: pre2 = 5.0 * round(pre1 / 15.0) if pre1 > pre2: pre1, pre2 = pre2, pre1 if norm2 is None: norm2 = 5.0 * round((max(energy) - e0) / 5.0) if norm2 < 0: norm2 = max(energy) - e0 - norm2 norm2 = min(norm2, (max(energy) - e0)) if norm1 is None: norm1 = min(150, 5.0 * round(norm2 / 15.0)) if norm1 > norm2: norm1, norm2 = norm2, norm1 if nnorm is None: nnorm = 2 if norm2 - norm1 < 350: nnorm = 1 if norm2 - norm1 < 50: nnorm = 0 nnorm = max(min(nnorm, MAX_NNORM), 0) # preedge p1 = index_of(energy, pre1 + e0) p2 = index_nearest(energy, pre2 + e0) if p2 - p1 < 2: p2 = min(len(energy), p1 + 2) omu = mu * energy**nvict ex, mx = remove_nans2(energy[p1:p2], omu[p1:p2]) precoefs = polyfit(ex, mx, 1) pre_edge = (precoefs[0] * energy + precoefs[1]) * energy**(-nvict) # normalization p1 = index_of(energy, norm1 + e0) p2 = index_nearest(energy, norm2 + e0) if p2 - p1 < 2: p2 = min(len(energy), p1 + 2) presub = (mu - pre_edge)[p1:p2] coefs = polyfit(energy[p1:p2], presub, nnorm) post_edge = 1.0 * pre_edge norm_coefs = [] for n, c in enumerate(reversed(list(coefs))): post_edge += c * energy**(n) norm_coefs.append(c) edge_step = step if edge_step is None: edge_step = post_edge[ie0] - pre_edge[ie0] edge_step = abs(edge_step) norm = (mu - pre_edge) / edge_step return { 'e0': e0, 'edge_step': edge_step, 'norm': norm, 'pre_edge': pre_edge, 'post_edge': post_edge, 'norm_coefs': norm_coefs, 'nvict': nvict, 'nnorm': nnorm, 'norm1': norm1, 'norm2': norm2, 'pre1': pre1, 'pre2': pre2, 'precoefs': precoefs }
def pre_edge(energy, mu=None, group=None, e0=None, step=None, nnorm=None, nvict=0, pre1=None, pre2=None, norm1=None, norm2=None, make_flat=True, _larch=None): """pre edge subtraction, normalization for XAFS This performs a number of steps: 1. determine E0 (if not supplied) from max of deriv(mu) 2. fit a line of polymonial to the region below the edge 3. fit a polymonial to the region above the edge 4. extrapolate the two curves to E0 and take their difference to determine the edge jump Arguments ---------- energy: array of x-ray energies, in eV, or group (see note 1) mu: array of mu(E) group: output group e0: edge energy, in eV. If None, it will be determined here. step: edge jump. If None, it will be determined here. pre1: low E range (relative to E0) for pre-edge fit pre2: high E range (relative to E0) for pre-edge fit nvict: energy exponent to use for pre-edg fit. See Notes. 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. See Notes. make_flat: boolean (Default True) to calculate flattened output. Returns ------- None: The following attributes will be written to the output group: e0 energy origin edge_step edge step norm normalized mu(E), using polynomial norm_area normalized mu(E), using integrated area flat flattened, normalized mu(E) pre_edge determined pre-edge curve post_edge determined post-edge, normalization curve dmude derivative of mu(E) (if the output group is None, _sys.xafsGroup will be written to) Notes ----- 1. Supports `First Argument Group` convention, requiring group members `energy` and `mu`. 2. Support `Set XAFS Group` convention within Larch or if `_larch` is set. 3. pre_edge: a line is fit to mu(energy)*energy**nvict over the region, energy=[e0+pre1, e0+pre2]. pre1 and pre2 default to None, which will set pre1 = e0 - 2nd energy point, rounded to 5 eV pre2 = roughly pre1/3.0, rounded to 5 eV 4. post-edge: a polynomial of order nnorm is fit to mu(energy)*energy**nvict between energy=[e0+norm1, e0+norm2]. nnorm, norm1, norm2 default to None, which will set: norm2 = max energy - e0, rounded to 5 eV norm1 = roughly min(150, norm2/3.0), rounded to 5 eV nnorm = 2 in norm2-norm1>350, 1 if norm2-norm1>50, or 0 if less. 5. flattening fits a quadratic curve (no matter nnorm) to the post-edge normalized mu(E) and subtracts that curve from it. """ energy, mu, group = parse_group_args(energy, members=('energy', 'mu'), defaults=(mu, ), group=group, fcn_name='pre_edge') if len(energy.shape) > 1: energy = energy.squeeze() if len(mu.shape) > 1: mu = mu.squeeze() pre_dat = preedge(energy, mu, e0=e0, step=step, nnorm=nnorm, nvict=nvict, pre1=pre1, pre2=pre2, norm1=norm1, norm2=norm2) group = set_xafsGroup(group, _larch=_larch) e0 = pre_dat['e0'] norm = pre_dat['norm'] norm1 = pre_dat['norm1'] norm2 = pre_dat['norm2'] # generate flattened spectra, by fitting a quadratic to .norm # and removing that. flat = norm ie0 = index_nearest(energy, e0) p1 = index_of(energy, norm1 + e0) p2 = index_nearest(energy, norm2 + e0) if p2 - p1 < 2: p2 = min(len(energy), p1 + 2) if make_flat and p2 - p1 > 4: enx, mux = remove_nans2(energy[p1:p2], norm[p1:p2]) # enx, mux = (energy[p1:p2], norm[p1:p2]) fpars = Parameters() ncoefs = len(pre_dat['norm_coefs']) fpars.add('c0', value=0, vary=True) fpars.add('c1', value=0, vary=(ncoefs > 1)) fpars.add('c2', value=0, vary=(ncoefs > 2)) fit = Minimizer(flat_resid, fpars, fcn_args=(enx, mux)) result = fit.leastsq(xtol=1.e-6, ftol=1.e-6) fc0 = result.params['c0'].value fc1 = result.params['c1'].value fc2 = result.params['c2'].value flat_diff = fc0 + energy * (fc1 + energy * fc2) flat = norm - (flat_diff - flat_diff[ie0]) flat[:ie0] = norm[:ie0] group.e0 = e0 group.norm = norm group.norm_poly = 1.0 * norm group.flat = flat group.dmude = np.gradient(mu) / np.gradient(energy) group.edge_step = pre_dat['edge_step'] group.edge_step_poly = pre_dat['edge_step'] group.pre_edge = pre_dat['pre_edge'] group.post_edge = pre_dat['post_edge'] group.pre_edge_details = Group() for attr in ('pre1', 'pre2', 'norm1', 'norm2', 'nnorm', 'nvict'): setattr(group.pre_edge_details, attr, pre_dat.get(attr, None)) group.pre_edge_details.pre_slope = pre_dat['precoefs'][0] group.pre_edge_details.pre_offset = pre_dat['precoefs'][1] for i in range(MAX_NNORM): if hasattr(group, 'norm_c%i' % i): delattr(group, 'norm_c%i' % i) for i, c in enumerate(pre_dat['norm_coefs']): setattr(group.pre_edge_details, 'norm_c%i' % i, c) # guess element and edge group.atsym = getattr(group, 'atsym', None) group.edge = getattr(group, 'edge', None) if group.atsym is None or group.edge is None: _atsym, _edge = guess_edge(group.e0) if group.atsym is None: group.atsym = _atsym if group.edge is None: group.edge = _edge return
def mback(energy, mu=None, group=None, z=None, edge='K', e0=None, pre1=None, pre2=-50, norm1=100, norm2=None, order=3, leexiang=False, tables='chantler', fit_erfc=False, return_f1=False, _larch=None): """ Match mu(E) data for tabulated f''(E) using the MBACK algorithm and, optionally, the Lee & Xiang extension Arguments ---------- energy: array of x-ray energies, in eV. mu: array of mu(E). group: output group. z: atomic number of the absorber. edge: x-ray absorption edge (default 'K') e0: edge energy, in eV. If None, it will be determined here. pre1: low E range (relative to e0) for pre-edge region. pre2: high E range (relative to e0) for pre-edge region. norm1: low E range (relative to e0) for post-edge region. norm2: high E range (relative to e0) for post-edge region. order: order of the legendre polynomial for normalization. (default=3, min=0, max=5). leexiang: boolean (default False) to use the Lee & Xiang extension. tables: tabulated scattering factors: 'chantler' (default) or 'cl' (cromer-liberman) fit_erfc: boolean (default False) to fit parameters of error function. return_f1: boolean (default False) to include the f1 array in the group. Returns ------- None The following attributes will be written to the output group: group.f2: tabulated f2(E). group.f1: tabulated f1(E) (if 'return_f1' is True). group.fpp: mback atched spectrum. group.edge_step: edge step of spectrum. group.norm: normalized spectrum. group.mback_params: group of parameters for the minimization. References: * MBACK (Weng, Waldo, Penner-Hahn): http://dx.doi.org/10.1086/303711 * Lee and Xiang: http://dx.doi.org/10.1088/0004-637X/702/2/970 * Cromer-Liberman: http://dx.doi.org/10.1063/1.1674266 * Chantler: http://dx.doi.org/10.1063/1.555974 """ order = max(min(order, MAXORDER), 0) ### 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) energy = remove_dups(energy) if e0 is None or e0 < energy[1] or e0 > energy[-2]: e0 = find_e0(energy, mu, group=group) print(e0) ie0 = index_nearest(energy, e0) e0 = energy[ie0] pre1_input = pre1 norm2_input = norm2 if pre1 is None: pre1 = min(energy) - e0 if norm2 is None: norm2 = max(energy) - e0 if norm2 < 0: norm2 = max(energy) - e0 - norm2 pre1 = max(pre1, (min(energy) - e0)) norm2 = min(norm2, (max(energy) - e0)) if pre1 > pre2: pre1, pre2 = pre2, pre1 if norm1 > norm2: norm1, norm2 = norm2, norm1 p1 = index_of(energy, pre1+e0) p2 = index_nearest(energy, pre2+e0) n1 = index_nearest(energy, norm1+e0) n2 = index_of(energy, norm2+e0) if p2 - p1 < 2: p2 = min(len(energy), p1 + 2) if n2 - n1 < 2: p2 = min(len(energy), p1 + 2) ## theta is a boolean array indicating the ## energy values considered for the fit. ## theta=1 for included values, theta=0 for excluded values. theta = np.zeros_like(energy, dtype='int') theta[p1:(p2+1)] = 1 theta[n1:(n2+1)] = 1 ## weights for the pre- and post-edge regions, as defined in the MBACK paper (?) weight = np.ones_like(energy, dtype=float) weight[p1:(p2+1)] = np.sqrt(np.sum(weight[p1:(p2+1)])) weight[n1:(n2+1)] = np.sqrt(np.sum(weight[n1:(n2+1)])) ## get the f'' function from CL or Chantler if tables.lower() == 'chantler': f1 = f1_chantler(z, energy) f2 = f2_chantler(z, energy) else: (f1, f2) = f1f2_cl(z, energy, edge=edge) group.f2 = f2 if return_f1: group.f1 = f1 em = find_xray_line(z, edge)[0] # erfc centroid params = Parameters() params.add(name='s', value=1.0, vary=True) # scale of data params.add(name='xi', value=50.0, vary=False, min=0) # width of erfc params.add(name='a', value=0.0, vary=False) # amplitude of erfc if fit_erfc: params['a'].vary = True params['a'].value = 0.5 params['xi'].vary = True for i in range(order+1): # polynomial coefficients params.add(name='c%d' % i, value=0, vary=True) out = minimize(match_f2, 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, f2=f2, e0=e0, em=em, order=order, weight=weight, theta=theta, leexiang=leexiang)) opars = out.params.valuesdict() eoff = energy - e0 norm_function = opars['a']*erfc((energy-em)/opars['xi']) + opars['c0'] for i in range(order): attr = 'c%d' % (i + 1) if attr in opars: norm_function += opars[attr]* eoff**(i + 1) group.e0 = e0 group.fpp = opars['s']*mu - norm_function # calculate edge step and normalization from f2 + norm_function pre_f2 = preedge(energy, group.f2+norm_function, e0=e0, pre1=pre1, pre2=pre2, norm1=norm1, norm2=norm2, nnorm=2, nvict=0) group.edge_step = pre_f2['edge_step'] / opars['s'] group.norm = (opars['s']*mu - pre_f2['pre_edge']) / pre_f2['edge_step'] group.mback_details = Group(params=opars, pre_f2=pre_f2, f2_scaled=opars['s']*f2, norm_function=norm_function)
def autobk(energy, mu=None, group=None, rbkg=1, nknots=None, e0=None, edge_step=None, kmin=0, kmax=None, kweight=1, dk=0.1, win='hanning', k_std=None, chi_std=None, nfft=2048, kstep=0.05, pre_edge_kws=None, nclamp=4, clamp_lo=1, clamp_hi=1, calc_uncertainties=True, err_sigma=1, _larch=None, **kws): """Use Autobk algorithm to remove XAFS background Parameters: ----------- energy: 1-d array of x-ray energies, in eV, or group mu: 1-d array of mu(E) group: output group (and input group for e0 and edge_step). rbkg: distance (in Ang) for chi(R) above which the signal is ignored. Default = 1. e0: edge energy, in eV. If None, it will be determined. edge_step: edge step. If None, it will be determined. pre_edge_kws: keyword arguments to pass to pre_edge() nknots: number of knots in spline. If None, it will be determined. kmin: minimum k value [0] kmax: maximum k value [full data range]. kweight: k weight for FFT. [1] dk: FFT window window parameter. [0.1] win: FFT window function name. ['hanning'] nfft: array size to use for FFT [2048] kstep: k step size to use for FFT [0.05] k_std: optional k array for standard chi(k). chi_std: optional chi array for standard chi(k). nclamp: number of energy end-points for clamp [2] clamp_lo: weight of low-energy clamp [1] clamp_hi: weight of high-energy clamp [1] calc_uncertaintites: Flag to calculate uncertainties in mu_0(E) and chi(k) [True] err_sigma: sigma level for uncertainties in mu_0(E) and chi(k) [1] Output arrays are written to the provided group. Follows the 'First Argument Group' convention. """ msg = sys.stdout if _larch is not None: msg = _larch.writer.write if 'kw' in kws: kweight = kws.pop('kw') if len(kws) > 0: msg('Unrecognized a:rguments for autobk():\n') msg(' %s\n' % (', '.join(kws.keys()))) return energy, mu, group = parse_group_args(energy, members=('energy', 'mu'), defaults=(mu,), group=group, fcn_name='autobk') if len(energy.shape) > 1: energy = energy.squeeze() if len(mu.shape) > 1: mu = mu.squeeze() energy = remove_dups(energy) # if e0 or edge_step are not specified, get them, either from the # passed-in group or from running pre_edge() group = set_xafsGroup(group, _larch=_larch) if edge_step is None and isgroup(group, 'edge_step'): edge_step = group.edge_step if e0 is None and isgroup(group, 'e0'): e0 = group.e0 if e0 is None or edge_step is None: # need to run pre_edge: pre_kws = dict(nnorm=3, nvict=0, pre1=None, pre2=-50., norm1=100., norm2=None) if pre_edge_kws is not None: pre_kws.update(pre_edge_kws) pre_edge(energy, mu, group=group, _larch=_larch, **pre_kws) if e0 is None: e0 = group.e0 if edge_step is None: edge_step = group.edge_step if e0 is None or edge_step is None: msg('autobk() could not determine e0 or edge_step!: trying running pre_edge first\n') return # get array indices for rkbg and e0: irbkg, ie0 ie0 = index_of(energy, e0) rgrid = np.pi/(kstep*nfft) if rbkg < 2*rgrid: rbkg = 2*rgrid irbkg = int(1.01 + rbkg/rgrid) # save ungridded k (kraw) and grided k (kout) # and ftwin (*k-weighting) for FT in residual enpe = energy[ie0:] - e0 kraw = np.sign(enpe)*np.sqrt(ETOK*abs(enpe)) if kmax is None: kmax = max(kraw) else: kmax = max(0, min(max(kraw), kmax)) kout = kstep * np.arange(int(1.01+kmax/kstep), dtype='float64') iemax = min(len(energy), 2+index_of(energy, e0+kmax*kmax/ETOK)) - 1 # interpolate provided chi(k) onto the kout grid if chi_std is not None and k_std is not None: chi_std = np.interp(kout, k_std, chi_std) # pre-load FT window ftwin = kout**kweight * ftwindow(kout, xmin=kmin, xmax=kmax, window=win, dx=dk, dx2=dk) # calc k-value and initial guess for y-values of spline params nspl = max(5, min(64, int(2*rbkg*(kmax-kmin)/np.pi) + 2)) spl_y, spl_k, spl_e = np.zeros(nspl), np.zeros(nspl), np.zeros(nspl) for i in range(nspl): q = kmin + i*(kmax-kmin)/(nspl - 1) ik = index_nearest(kraw, q) i1 = min(len(kraw)-1, ik + 5) i2 = max(0, ik - 5) spl_k[i] = kraw[ik] spl_e[i] = energy[ik+ie0] spl_y[i] = (2*mu[ik+ie0] + mu[i1+ie0] + mu[i2+ie0] ) / 4.0 # get spline represention: knots, coefs, order=3 # coefs will be varied in fit. knots, coefs, order = splrep(spl_k, spl_y) # set fit parameters from initial coefficients params = Parameters() for i in range(len(coefs)): params.add(name = FMT_COEF % i, value=coefs[i], vary=i<len(spl_y)) initbkg, initchi = spline_eval(kraw[:iemax-ie0+1], mu[ie0:iemax+1], knots, coefs, order, kout) # do fit result = minimize(__resid, params, method='leastsq', gtol=1.e-5, ftol=1.e-5, xtol=1.e-5, epsfcn=1.e-5, kws = dict(ncoefs=len(coefs), chi_std=chi_std, knots=knots, order=order, kraw=kraw[:iemax-ie0+1], mu=mu[ie0:iemax+1], irbkg=irbkg, kout=kout, ftwin=ftwin, kweight=kweight, nfft=nfft, nclamp=nclamp, clamp_lo=clamp_lo, clamp_hi=clamp_hi)) # write final results coefs = [result.params[FMT_COEF % i].value for i in range(len(coefs))] bkg, chi = spline_eval(kraw[:iemax-ie0+1], mu[ie0:iemax+1], knots, coefs, order, kout) obkg = np.copy(mu) obkg[ie0:ie0+len(bkg)] = bkg # outputs to group group = set_xafsGroup(group, _larch=_larch) group.bkg = obkg group.chie = (mu-obkg)/edge_step group.k = kout group.chi = chi/edge_step group.e0 = e0 # now fill in 'autobk_details' group details = Group(params=result.params) details.init_bkg = np.copy(mu) details.init_bkg[ie0:ie0+len(bkg)] = initbkg details.init_chi = initchi/edge_step details.knots_e = spl_e details.knots_y = np.array([coefs[i] for i in range(nspl)]) details.init_knots_y = spl_y details.nfev = result.nfev details.kmin = kmin details.kmax = kmax group.autobk_details = details # uncertainties in mu0 and chi: can be fairly slow. if calc_uncertainties: nchi = len(chi) nmue = iemax-ie0 + 1 redchi = result.redchi covar = result.covar / redchi jac_chi = np.zeros(nchi*nspl).reshape((nspl, nchi)) jac_bkg = np.zeros(nmue*nspl).reshape((nspl, nmue)) cvals, cerrs = [], [] for i in range(len(coefs)): par = result.params[FMT_COEF % i] cvals.append(getattr(par, 'value', 0.0)) cdel = getattr(par, 'stderr', 0.0) if cdel is None: cdel = 0.0 cerrs.append(cdel/2.0) cvals = np.array(cvals) cerrs = np.array(cerrs) # find derivatives by hand! _k = kraw[:nmue] _m = mu[ie0:iemax+1] for i in range(nspl): cval0 = cvals[i] cvals[i] = cval0 + cerrs[i] bkg1, chi1 = spline_eval(_k, _m, knots, cvals, order, kout) cvals[i] = cval0 - cerrs[i] bkg2, chi2 = spline_eval(_k, _m, knots, cvals, order, kout) cvals[i] = cval0 jac_chi[i] = (chi1 - chi2) / (2*cerrs[i]) jac_bkg[i] = (bkg1 - bkg2) / (2*cerrs[i]) dfchi = np.zeros(nchi) dfbkg = np.zeros(nmue) for i in range(nspl): for j in range(nspl): dfchi += jac_chi[i]*jac_chi[j]*covar[i,j] dfbkg += jac_bkg[i]*jac_bkg[j]*covar[i,j] prob = 0.5*(1.0 + erf(err_sigma/np.sqrt(2.0))) dchi = t.ppf(prob, nchi-nspl) * np.sqrt(dfchi*redchi) dbkg = t.ppf(prob, nmue-nspl) * np.sqrt(dfbkg*redchi) group.delta_chi = dchi group.delta_bkg = 0.0*mu group.delta_bkg[ie0:ie0+len(dbkg)] = dbkg
def mback(energy, mu=None, group=None, z=None, edge='K', e0=None, pre1=None, pre2=-50, norm1=100, norm2=None, order=3, leexiang=False, tables='chantler', fit_erfc=False, return_f1=False, _larch=None): """ Match mu(E) data for tabulated f''(E) using the MBACK algorithm and, optionally, the Lee & Xiang extension Arguments ---------- energy: array of x-ray energies, in eV. mu: array of mu(E). group: output group. z: atomic number of the absorber. edge: x-ray absorption edge (default 'K') e0: edge energy, in eV. If None, it will be determined here. pre1: low E range (relative to e0) for pre-edge region. pre2: high E range (relative to e0) for pre-edge region. norm1: low E range (relative to e0) for post-edge region. norm2: high E range (relative to e0) for post-edge region. order: order of the legendre polynomial for normalization. (default=3, min=0, max=5). leexiang: boolean (default False) to use the Lee & Xiang extension. tables: tabulated scattering factors: 'chantler' [deprecated] fit_erfc: boolean (default False) to fit parameters of error function. return_f1: boolean (default False) to include the f1 array in the group. Returns ------- None The following attributes will be written to the output group: group.f2: tabulated f2(E). group.f1: tabulated f1(E) (if 'return_f1' is True). group.fpp: mback atched spectrum. group.edge_step: edge step of spectrum. group.norm: normalized spectrum. group.mback_params: group of parameters for the minimization. Notes: Chantler tables is now used, with Cromer-Liberman no longer supported. References: * MBACK (Weng, Waldo, Penner-Hahn): http://dx.doi.org/10.1086/303711 * Lee and Xiang: http://dx.doi.org/10.1088/0004-637X/702/2/970 * Cromer-Liberman: http://dx.doi.org/10.1063/1.1674266 * Chantler: http://dx.doi.org/10.1063/1.555974 """ order = max(min(order, MAXORDER), 0) ### 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) energy = remove_dups(energy) if e0 is None or e0 < energy[1] or e0 > energy[-2]: e0 = find_e0(energy, mu, group=group) print(e0) ie0 = index_nearest(energy, e0) e0 = energy[ie0] pre1_input = pre1 norm2_input = norm2 if pre1 is None: pre1 = min(energy) - e0 if norm2 is None: norm2 = max(energy) - e0 if norm2 < 0: norm2 = max(energy) - e0 - norm2 pre1 = max(pre1, (min(energy) - e0)) norm2 = min(norm2, (max(energy) - e0)) if pre1 > pre2: pre1, pre2 = pre2, pre1 if norm1 > norm2: norm1, norm2 = norm2, norm1 p1 = index_of(energy, pre1+e0) p2 = index_nearest(energy, pre2+e0) n1 = index_nearest(energy, norm1+e0) n2 = index_of(energy, norm2+e0) if p2 - p1 < 2: p2 = min(len(energy), p1 + 2) if n2 - n1 < 2: p2 = min(len(energy), p1 + 2) ## theta is a boolean array indicating the ## energy values considered for the fit. ## theta=1 for included values, theta=0 for excluded values. theta = np.zeros_like(energy, dtype='int') theta[p1:(p2+1)] = 1 theta[n1:(n2+1)] = 1 ## weights for the pre- and post-edge regions, as defined in the MBACK paper (?) weight = np.ones_like(energy, dtype=float) weight[p1:(p2+1)] = np.sqrt(np.sum(weight[p1:(p2+1)])) weight[n1:(n2+1)] = np.sqrt(np.sum(weight[n1:(n2+1)])) ## get the f'' function from CL or Chantler f1 = f1_chantler(z, energy) f2 = f2_chantler(z, energy) group.f2 = f2 if return_f1: group.f1 = f1 em = find_xray_line(z, edge).energy # erfc centroid params = Parameters() params.add(name='s', value=1.0, vary=True) # scale of data params.add(name='xi', value=50.0, vary=False, min=0) # width of erfc params.add(name='a', value=0.0, vary=False) # amplitude of erfc if fit_erfc: params['a'].vary = True params['a'].value = 0.5 params['xi'].vary = True for i in range(order+1): # polynomial coefficients params.add(name='c%d' % i, value=0, vary=True) out = minimize(match_f2, 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, f2=f2, e0=e0, em=em, order=order, weight=weight, theta=theta, leexiang=leexiang)) opars = out.params.valuesdict() eoff = energy - e0 norm_function = opars['a']*erfc((energy-em)/opars['xi']) + opars['c0'] for i in range(order): attr = 'c%d' % (i + 1) if attr in opars: norm_function += opars[attr]* eoff**(i + 1) group.e0 = e0 group.fpp = opars['s']*mu - norm_function # calculate edge step and normalization from f2 + norm_function pre_f2 = preedge(energy, group.f2+norm_function, e0=e0, pre1=pre1, pre2=pre2, norm1=norm1, norm2=norm2, nnorm=2, nvict=0) group.edge_step = pre_f2['edge_step'] / opars['s'] group.norm = (opars['s']*mu - pre_f2['pre_edge']) / pre_f2['edge_step'] group.mback_details = Group(params=opars, pre_f2=pre_f2, f2_scaled=opars['s']*f2, norm_function=norm_function)