def xas_convolve(energy, norm=None, group=None, form='lorentzian', esigma=1.0, eshift=0.0, _larch=None): """ convolve a normalized mu(E) spectra with a Lorentzian or Gaussian peak shape, degrading separation of XANES features. This is provided as a complement to xas_deconvolve, and to deliberately broaden spectra to compare with spectra measured at lower resolution. Arguments ---------- energy: array of x-ray energies (in eV) or XAFS data group norm: array of normalized mu(E) group: output group form: form of deconvolution function. One of 'lorentzian' or 'gaussian' ['lorentzian'] esigma energy sigma (in eV) to pass to gaussian() or lorentzian() [1.0] eshift energy shift (in eV) to apply to result [0] Returns ------- None The array 'conv' will be written to the output group. Notes ----- Follows the First Argument Group convention, using group members named 'energy' and 'norm' """ energy, mu, group = parse_group_args(energy, members=('energy', 'norm'), defaults=(norm,), group=group, fcn_name='xas_convolve') eshift = eshift + 0.5 * esigma en = remove_dups(energy) en = en - en[0] estep = max(0.001, 0.001*int(min(en[1:]-en[:-1])*1000.0)) npad = 1 + int(max(estep*2.01, 50*esigma)/estep) npts = npad + int(max(en) / estep) x = np.arange(npts)*estep y = interp(en, mu, x, kind='cubic') kernel = lorentzian if form.lower().startswith('g'): kernel = gaussian k = kernel(x, center=0, sigma=esigma) ret = np.convolve(y, k, mode='full') out = interp(x-eshift, ret[:len(x)], en, kind='cubic') group = set_xafsGroup(group, _larch=_larch) group.conv = out / k.sum()
def merge_groups(grouplist, master=None, xarray='energy', yarray='mu', kind='cubic', trim=True, calc_yerr=True, _larch=None): """merge arrays from a list of groups. Arguments --------- grouplist list of groups to merge master group to use for common x arrary [None -> 1st group] xarray name of x-array for merge ['energy'] yarray name of y-array for merge ['mu'] kind interpolation kind ['cubic'] trim whether to trim to the shortest energy range [True] calc_yerr whether to use the variance in the input as yerr [True] Returns -------- group with x-array and y-array containing merged data. """ if master is None: master = grouplist[0] xout = getattr(master, xarray) xmins = [min(xout)] xmaxs = [max(xout)] yvals = [] for g in grouplist: x = getattr(g, xarray) y = getattr(g, yarray) yvals.append(interp(x, y, xout, kind=kind)) xmins.append(min(x)) xmaxs.append(max(x)) yvals = np.array(yvals) yave = yvals.mean(axis=0) ystd = yvals.std(axis=0) if trim: xmin = min(xmins) xmax = min(xmaxs) ixmin = index_of(xout, xmin) ixmax = index_of(xout, xmax) xout = xout[ixmin:ixmax] yave = yave[ixmin:ixmax] ystd = ystd[ixmin:ixmax] grp = Group() setattr(grp, xarray, xout) setattr(grp, yarray, yave) setattr(grp, yarray + '_std', ystd) return grp
def groups2csv(grouplist, filename, x='energy', y='norm', _larch=None): """save data from a list of groups to a CSV file Arguments --------- grouplist list of groups to save arrays from filname name of output file x name of group member to use for `x` y name of group member to use for `y` """ def get_label(grp): 'get label for group' for attr in ('filename', 'label', 'name', 'file', '__name__'): o = getattr(grp, attr, None) if o is not None: return o return repr(o) ngroups = len(grouplist) x0 = getattr(grouplist[0], x) npts = len(x0) columns = [x0, getattr(grouplist[0], y)] labels = [x, get_label(grouplist[0]) ] for g in grouplist[1:]: labels.append(get_label(g)) _x = getattr(g, x) _y = getattr(g, y) if ((len(_x) != npts) or (abs(_x -x0)).sum() > 1.0): columns.append(interp(_x, _y, x0)) else: columns.append(_y) buff = ["# %s" % ', '.join(labels)] for i in range(npts): buff.append(', '.join(["%.6f" % s[i] for s in columns])) buff.append('') with open(filename, 'w') as fh: fh.write("\n".join(buff)) print("Wrote %i groups to %s" % (len(columns)-1, filename))
def kk(self, energy=None, mu=None, z=None, edge='K', how='scalar', mback_kws=None): """ Convert mu(E) data into f'(E) and f"(E). f"(E) is made by matching mu(E) to the tabulated values of the imaginary part of the scattering factor (Cromer-Liberman), f'(E) is then obtained by performing a differential Kramers-Kronig transform on the matched f"(E). Attributes energy: energy array mu: array with mu(E) data z: Z number of absorber edge: absorption edge, usually 'K' or 'L3' mback_kws: arguments for the mback algorithm Returns self.f1, self.f2: CL values over on the input energy grid self.fp, self.fpp: matched and KK transformed data on the input energy grid References: * Cromer-Liberman: http://dx.doi.org/10.1063/1.1674266 * KK computation: Ohta and Ishida, Applied Spectroscopy 42:6 (1988) 952-957 * diffKK implementation: http://dx.doi.org/10.1103/PhysRevB.58.11215 * 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 """ if type(energy).__name__ == 'ndarray': self.energy = energy if type(mu).__name__ == 'ndarray': self.mu = mu if z != None: self.z = z if edge != None: self.edge = edge if mback_kws != None: self.mback_kws = mback_kws if self.z == None: Exception("Z for absorber not provided for diffKK") if self.edge == None: Exception("absorption edge not provided for diffKK") mb_kws = dict(order=3, z=self.z, edge=self.edge, e0=None, leexiang=False, tables='chantler', fit_erfc=False, return_f1=True) if self.mback_kws is not None: mb_kws.update(self.mback_kws) start = time.clock() mback(self.energy, self.mu, group=self, **mb_kws) ## interpolate matched data onto an even grid with an even number of elements (about 1 eV) npts = int(self.energy[-1] - self.energy[0]) + ( int(self.energy[-1] - self.energy[0]) % 2) self.grid = np.linspace(self.energy[0], self.energy[-1], npts) fpp = interp(self.energy, self.f2 - self.fpp, self.grid, fill_value=0.0) ## do difference KK if repr(how).startswith('sca'): fp = kkmclr_sca(self.grid, fpp) else: fp = kkmclr(self.grid, fpp) ## interpolate back to original grid and add diffKK result to f1 to make fp array self.fp = self.f1 + interp(self.grid, fp, self.energy, fill_value=0.0) ## clean up group #for att in ('normalization_function', 'weight', 'grid'): # if hasattr(self, att): delattr(self, att) finish = time.clock() self.time_elapsed = float(finish - start)
def xas_deconvolve(energy, norm=None, group=None, form='lorentzian', esigma=1.0, eshift=0.0, smooth=True, sgwindow=None, sgorder=3, _larch=None): """XAS spectral deconvolution de-convolve a normalized mu(E) spectra with a peak shape, enhancing the intensity and separation of peaks of a XANES spectrum. The results can be unstable, and noisy, and should be used with caution! Arguments ---------- energy: array of x-ray energies (in eV) or XAFS data group norm: array of normalized mu(E) group: output group form: functional form of deconvolution function. One of 'gaussian' or 'lorentzian' [default] esigma energy sigma to pass to gaussian() or lorentzian() [in eV, default=1.0] eshift energy shift to apply to result. [in eV, default=0] smooth whether to smooth result with savitzky_golay method [True] sgwindow window size for savitzky_golay [found from data step and esigma] sgorder order for savitzky_golay [3] Returns ------- None The array 'deconv' will be written to the output group. Notes ----- Support See First Argument Group convention, requiring group members 'energy' and 'norm' Smoothing with savitzky_golay() requires a window and order. By default, window = int(esigma / estep) where estep is step size for the gridded data, approximately the finest energy step in the data. """ energy, mu, group = parse_group_args(energy, members=('energy', 'norm'), defaults=(norm, ), group=group, fcn_name='xas_deconvolve') eshift = eshift + 0.5 * esigma en = remove_dups(energy) estep1 = int(0.1 * en[0]) * 2.e-5 en = en - en[0] estep = max(estep1, 0.01 * int(min(en[1:] - en[:-1]) * 100.0)) npts = 1 + int(max(en) / estep) if npts > 25000: npts = 25001 estep = max(en) / 25000.0 x = np.arange(npts) * estep y = interp(en, mu, x, kind='cubic') kernel = lorentzian if form.lower().startswith('g'): kernel = gaussian yext = np.concatenate((y, np.arange(len(y)) * y[-1])) ret, err = deconvolve(yext, kernel(x, center=0, sigma=esigma)) nret = min(len(x), len(ret)) ret = ret[:nret] * yext[nret - 1] / ret[nret - 1] if smooth: if sgwindow is None: sgwindow = int(1.0 * esigma / estep) sqwindow = int(sgwindow) if sgwindow < (sgorder + 1): sgwindow = sgorder + 2 if sgwindow % 2 == 0: sgwindow += 1 ret = savitzky_golay(ret, sgwindow, sgorder) out = interp(x + eshift, ret, en, kind='cubic') group = set_xafsGroup(group, _larch=_larch) group.deconv = out
def kk(self, energy=None, mu=None, z=None, edge='K', how='scalar', mback_kws=None): """ Convert mu(E) data into f'(E) and f"(E). f"(E) is made by matching mu(E) to the tabulated values of the imaginary part of the scattering factor (Cromer-Liberman), f'(E) is then obtained by performing a differential Kramers-Kronig transform on the matched f"(E). Attributes energy: energy array mu: array with mu(E) data z: Z number of absorber edge: absorption edge, usually 'K' or 'L3' mback_kws: arguments for the mback algorithm Returns self.f1, self.f2: CL values over on the input energy grid self.fp, self.fpp: matched and KK transformed data on the input energy grid References: * Cromer-Liberman: http://dx.doi.org/10.1063/1.1674266 * KK computation: Ohta and Ishida, Applied Spectroscopy 42:6 (1988) 952-957 * diffKK implementation: http://dx.doi.org/10.1103/PhysRevB.58.11215 * 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 """ if type(energy).__name__ == 'ndarray': self.energy = energy if type(mu).__name__ == 'ndarray': self.mu = mu if z != None: self.z = z if edge != None: self.edge = edge if mback_kws != None: self.mback_kws = mback_kws if self.z == None: Exception("Z for absorber not provided for diffKK") if self.edge == None: Exception("absorption edge not provided for diffKK") mb_kws = dict(order=3, z=self.z, edge=self.edge, e0=None, emin=None, emax=None, whiteline=False, leexiang=False, tables='chantler', fit_erfc=False, return_f1=True) if self.mback_kws is not None: mb_kws.update(self.mback_kws) start = time.clock() mback(self.energy, self.mu, group=self, _larch=self._larch, **mb_kws) ## interpolate matched data onto an even grid with an even number of elements (about 1 eV) npts = int(self.energy[-1] - self.energy[0]) + (int(self.energy[-1] - self.energy[0])%2) self.grid = np.linspace(self.energy[0], self.energy[-1], npts) fpp = interp(self.energy, self.f2-self.fpp, self.grid, fill_value=0.0) ## do difference KK if repr(how).startswith('sca'): fp = kkmclr_sca(self.grid, fpp) else: fp = kkmclr(self.grid, fpp) ## interpolate back to original grid and add diffKK result to f1 to make fp array self.fp = self.f1 + interp(self.grid, fp, self.energy, fill_value=0.0) ## clean up group #for att in ('normalization_function', 'weight', 'grid'): # if hasattr(self, att): delattr(self, att) finish = time.clock() self.time_elapsed = float(finish-start)