def __init__(self, p0=0.3, n=1e6): self.g = data_generator(tmax=10) self.p0 = p0 self.n = n g = self.g self.pexp = pulsed_exp(lifetime=g.lifetime, pulse_len=g.pulse_len) self.sexp = pulsed_strexp(lifetime=g.lifetime, pulse_len=g.pulse_len) self.T1 = np.logspace(np.log10(g.lifetime * 0.01), np.log10(g.lifetime * 100), 1000) self.kernel_fn = lambda x, w: self.pexp(x, w, 1)
def get_fn(self, fn_name, ncomp=1, pulse_len=-1, lifetime=-1): """ Get the fitting function used. fn_name: string of the function name users will select. ncomp: number of components, ex if 2, then return exp+exp pulse_len: duration of beam on in s lifetime: lifetime of probe in s Returns python function(x,*pars) """ # set fitting function if fn_name == 'Lorentzian': fn = fns.lorentzian self.mode = 1 elif fn_name == 'BiLorentzian': fn = fns.bilorentzian self.mode = 1 elif fn_name == 'Gaussian': fn = fns.gaussian self.mode = 1 elif fn_name == 'Exp': fn = fns.pulsed_exp(lifetime, pulse_len) self.mode = 2 elif fn_name == 'Bi Exp': fn = fns.pulsed_biexp(lifetime, pulse_len) self.mode = 2 elif fn_name == 'Str Exp': fn = fns.pulsed_strexp(lifetime, pulse_len) self.mode = 2 else: raise RuntimeError('Fitting function not found.') # Make final function based on number of components fnlist = [fn] * ncomp if self.mode == 1: fnlist.append(lambda x, b: b) fn = fns.get_fn_superpos(fnlist) return fn
def _setup(self): # get data dat = bd.bdata(self.run, self.year) self.x, self.y, self.yerr = dat.asym('c', rebin=self.rebin) # remove zero error values idx = self.yerr != 0 self.x = self.x[idx] self.y = self.y[idx] self.yerr = self.yerr[idx] # get function f = pulsed_exp(lifetime=bd.life[self.probe], pulse_len=dat.get_pulse_s()) self.fn = lambda x, w: f(x, w, 1) # build error matrix self.S = np.diag(1 / self.yerr) # set the kernel self.K = np.array([self.fn(self.x, i) for i in self.lamb]).T
def __init__(self, filename, nproc=4): """ filename: yaml or csv or without extension nproc: number of processors """ # get file names filename = os.path.splitext(filename)[0] yamlfile = filename + '.yaml' csvfile = filename + '.csv' # get run settings with open(yamlfile, 'r') as fid: run_settings = yaml.safe_load(fid) # get run data df = pd.read_csv(csvfile, comment='#') df = df[['t', self.asym_type, self.asym_err]] # drop bad rows and errors of zero df[self.asym_err].loc[df[self.asym_err] == 0] = np.nan df.dropna(inplace=True) # make kernel and fit functions lifetime = run_settings['lifetime (s)'] pulse_len = run_settings['beam pulse (s)'] self.pexp = pulsed_exp(pulse_len=pulse_len, lifetime=lifetime) self.sexp = pulsed_strexp(pulse_len=pulse_len, lifetime=lifetime) # generate the T1 range log10_T1_min = np.log10(lifetime * 1e-2) log10_T1_max = np.log10(lifetime * 1e2) self.T1 = np.logspace(log10_T1_min, log10_T1_max, self.nT1, base=10.0) # make ilt object super().__init__(df['t'].values, df[self.asym_type].values, df[self.asym_err].values, lambda x, w: self.pexp(x, w, 1), self.T1, nproc)
def run(self): """ Run the function placer comp = component number to run """ try: del self.fplace except AttributeError: pass else: self.fig.axes[0].cla() self.fig.axes[0].errorbar(*self.xy,fmt='.') # ensure matplotlib signals work. Not sure why this is needed. self.fig.tight_layout() # get paramters, translating the names p0 = [] if self.n_components > 1: for i in range(self.n_components): p0.append({self.parmap[k.split('_')[0]]:self.p0[k] \ for k in self.p0.keys() if 'base' in k or str(i) in k}) else: p0.append({self.parmap[k]:self.p0[k] for k in self.p0.keys()}) # get fitting function if self.fname == 'Lorentzian': fn = fns.lorentzian # freq,peak,width,amp # ~ elif self.fname == 'BiLorentzian': # ~ fn = fns.bilorentzian # freq, peak,widthA,ampA,widthB,ampB elif self.fname == 'Gaussian': fn = lambda freq,peak,width,amp : fns.gaussian(freq,peak,width,amp) elif self.fname in ('Exp', 'Str Exp'): # get function pulse = self.data.get_pulse_s() lifetime = bd.life[self.bfit.probe_species.get()] if self.fname == 'Exp': f1 = fns.pulsed_exp(lifetime=lifetime,pulse_len=pulse) if self.bfit.probe_species.get() == 'Mg31': fn = lambda x,lam,amp : fa_31Mg(x,pulse)*f1(x,lam,amp) else: fn = lambda x,lam,amp : f1(x,lam,amp) elif self.fname == 'Str Exp': f1 = fns.pulsed_strexp(lifetime=lifetime,pulse_len=pulse) if self.bfit.probe_species.get() == 'Mg31': fn = lambda x,lam,amp,beta : fa_31Mg(x,pulse)*f1(x,lam,beta,amp) else: fn = lambda x,lam,amp,beta : f1(x,lam,beta,amp) else: self.cancel() errormsg = 'Function "%s" not implemented in P0 Finder' % self.fname self.logger.warning(errormsg) messagebox.showerror("Error",errormsg) raise RuntimeError(errormsg) self.fig.canvas.mpl_connect('close_event',self.cancel) self.fplace = FunctionPlacer(fig=self.fig, data=self.data, fn_single=fn, ncomp=self.n_components, p0=p0, fnname=self.fname, asym_mode=self.bfit.get_asym_mode(self.bfit.fit_files), endfn=self.endfn)
def get_fn(self, fn_name, ncomp=1, pulse_len=-1, lifetime=-1, constr=None): """ Get the fitting function used. fn_name: string of the function name users will select. ncomp: number of components, ex if 2, then return exp+exp pulse_len: duration of beam on in s lifetime: lifetime of probe in s Returns python function(x, *pars) """ # set fitting function if fn_name == 'Lorentzian': fn = fns.lorentzian self.mode = 1 elif fn_name == 'BiLorentzian': fn = fns.bilorentzian self.mode = 1 elif fn_name == 'QuadLorentz': fn = lambda freq, nu_0, nu_q, eta, theta, phi, \ amp0, amp1, amp2, amp3, fwhm: \ fns.quadlorentzian(freq, nu_0, nu_q, eta, theta, phi, \ amp0, amp1, amp2, amp3, \ fwhm, fwhm, fwhm, fwhm, \ I=self.spin[self.probe_species]) self.mode = 1 elif fn_name == 'Gaussian': fn = fns.gaussian self.mode = 1 elif fn_name == 'Exp': fn = fns.pulsed_exp(lifetime, pulse_len) self.mode = 2 elif fn_name == 'Bi Exp': fn = fns.pulsed_biexp(lifetime, pulse_len) self.mode = 2 elif fn_name == 'Str Exp': fn = fns.pulsed_strexp(lifetime, pulse_len) self.mode = 2 else: raise RuntimeError('Fitting function not found.') # add corrections for probe daughters if self.mode == 2 and self.probe_species == 'Mg31': fn = fns.decay_corrected_fn(fa_31Mg, fn, beam_pulse=pulse_len) # Make superimposed function based on number of components fnlist = [fn] * ncomp if self.mode == 1: fnlist.append(lambda x, b: b) fn = fns.get_fn_superpos(fnlist) # set parameter constraints if constr and constr is not None: par_names_orig = self.gen_param_names(fn_name, ncomp) par_names_constr = self.gen_param_names(fn_name, ncomp, constr) fn = fns.get_constrained_fn(fn, par_names_orig, par_names_constr, constr) return fn
def run(self): """ Run the function placer comp = component number to run """ try: del self.fplace except AttributeError: pass else: self.fig.axes[0].cla() self.fig.axes[0].errorbar(*self.xy, fmt='.') # ensure matplotlib signals work. Not sure why this is needed. self.fig.tight_layout() # get paramters, translating the names p0 = self.split_components(self.p0) blo = self.split_components(self.blo) bhi = self.split_components(self.bhi) # get fitting function if self.fname == 'Lorentzian': fn = fns.lorentzian # freq, peak, fwhm, amp elif self.fname == 'BiLorentzian': fn = lambda freq, peak, fwhm, amp: fns.bilorentzian( freq, peak, fwhm, amp / 3, fwhm / 3, amp * 2 / 3) elif self.fname == 'Gaussian': fn = lambda freq, peak, fwhm, amp: fns.gaussian( freq, peak, fwhm, amp) elif self.fname == 'QuadLorentz': fn = lambda freq, nu_0, nu_q, eta, theta, phi, \ amp0, amp1, amp2, amp3, fwhm: \ fns.quadlorentzian(freq, nu_0, nu_q, eta, theta, phi, \ amp0, amp1, amp2, amp3, \ fwhm, fwhm, fwhm, fwhm, I = self.fitter.spin[self.fitter.probe_species]) elif self.fname in ('Exp', 'Str Exp'): # get function pulse = self.data.pulse_s lifetime = bd.life[self.bfit.probe_species.get()] if self.fname == 'Exp': f1 = fns.pulsed_exp(lifetime=lifetime, pulse_len=pulse) if self.bfit.probe_species.get() == 'Mg31': fn = lambda x, lam, amp: fa_31Mg(x, pulse) * f1( x, lam, amp) else: fn = lambda x, lam, amp: f1(x, lam, amp) elif self.fname == 'Str Exp': f1 = fns.pulsed_strexp(lifetime=lifetime, pulse_len=pulse) if self.bfit.probe_species.get() == 'Mg31': fn = lambda x, lam, amp, beta: fa_31Mg(x, pulse) * f1( x, lam, beta, amp) else: fn = lambda x, lam, amp, beta: f1(x, lam, beta, amp) else: self.cancel() errormsg = 'Function "%s" not implemented in P0 Finder' % self.fname self.logger.warning(errormsg) messagebox.showerror("Error", errormsg) raise RuntimeError(errormsg) self.fig.canvas.mpl_connect('close_event', self.cancel) self.fplace = FunctionPlacer( fig=self.fig, data=self.data, fn_single=fn, ncomp=self.n_components, p0=p0, fnname=self.fname, asym_mode=self.bfit.get_asym_mode(self.bfit.fit_files), endfn=self.endfn, spin=self.fitter.spin[self.fitter.probe_species])
def test_numeric_integration(tolerance=1e-5, n_samples=50, print_summary=False): # constants appropriate for most β-NMR data taken at TRIUMF nuclear_lifetime = bd.life["Li8"] pulse_duration = 3.0 * nuclear_lifetime # create the SLR functions fcn_exp = pulsed_exp(nuclear_lifetime, pulse_duration) fcn_strexp = pulsed_strexp(nuclear_lifetime, pulse_duration) # generate random values for the SLR fit function parameters that are uniformly # distributed between typical bounds for real data relaxation_rates = np.random.uniform(1e-2 * nuclear_lifetime, 1e2 * nuclear_lifetime, n_samples) amplitudes = np.random.uniform(0.00, 0.15, n_samples) # stretching exponent beta = 1.0 # machine precision for floating point values epsilon = np.finfo(float).eps # create an empty DataFrame to hold all of the results df = pd.DataFrame(columns=[ "Time (s)", "Amplitude", "Rate (1/s)", "Difference", "Absolute Difference", "Tolerance", "Tolerance Exceeded", "Epsilon", "Epsilon Exceeded", ]) # loop over the random function parameters for rate, amplitude in zip(relaxation_rates, amplitudes): # generate series of random times to evaluate the SLR fit functions that are # uniformly distributed between typical bounds for real data time = np.random.uniform(0.0, pulse_duration + 10 * nuclear_lifetime, n_samples) # evaluate the difference difference = fcn_exp(time, rate, amplitude) - fcn_strexp( time, rate, beta, amplitude) # fill the DataFrame row-by-row # https://stackoverflow.com/a/42837693 for t, d in zip(time, difference): df = df.append( { "Time (s)": t, "Amplitude": amplitude, "Rate (1/s)": rate, "Difference": d, "Absolute Difference": np.abs(d), "Tolerance": tolerance, "Tolerance Exceeded": np.abs(d) > tolerance, "Epsilon": epsilon, "Epsilon Exceeded": np.abs(d) < epsilon, }, ignore_index=True, ) # optionally print a summary of the results if print_summary: print("") print("-------") print("Summary of `test_numeric_integration()`") print("-------") print("") print("Tolerance = %g" % tolerance) print("Machine Epsilon = %g" % epsilon) print("") print("Absolute Difference:") print(" - Max = %g" % df["Absolute Difference"].max()) print(" - Min = %g" % df["Absolute Difference"].min()) print(" - Mean = %g" % df["Absolute Difference"].mean()) print("") print("Column Data:") print("") print(df["Tolerance Exceeded"].value_counts()) print("") print(df["Epsilon Exceeded"].value_counts()) print("") # finally, check all values and raise an error if the tolerance is exceeded for i in df.index: if df["Tolerance Exceeded"][i]: raise AssertionError( "Absolute difference greater than tolerance!\n\n" + "\t|%.4e| > %.4e\n\n" % (df["Difference"][i], tolerance) + "\ttime = %.4e s\n" % df["Time (s)"][i] + "\tamplitude = %.4e\n" % df["Amplitude"][i] + "\trate = %.4e 1/s" % df["Rate (1/s)"][i])
# basic dev testing the ilt # Derek Fujimoto # Feb 2020 from bILT.src.ilt import * from bfit.fitting.functions import pulsed_exp import bdata as bd import numpy as np x,y,dy = bd.bdata(40214, year=2009).asym('c') T1 = np.logspace(np.log(0.01 * 1.2096), np.log(100.0 * 1.2096), 100) alpha = np.logspace(2, 5, 50) f = pulsed_exp(1.21,4) fn = lambda x,w: f(x,w,1) I = ilt(x,y,dy,fn,T1,nproc=4) I.fit(alpha) # ~ plt.figure() # ~ I.draw_fit(10) # ~ plt.figure() # ~ I.draw_weights(10) # ~ plt.figure() # ~ I.draw_logdist(10) # ~ plt.figure() # ~ I.draw_Lcurve() # ~ plt.figure()
ax1.legend() ax1.set_title("") ax2.set_title("") return (fig, ax1, ax2) # EXPONENTIAL ================================================================ if 0: print("Running EXPONENTIAL") # setup T1_tst = 2 # T1 in this test f = lambda t: np.exp(-t / T1_tst) fit = pulsed_exp(lifetime=bd.life.Li8, pulse_len=4) # run and draw test1 = test() test1.run(f, fit, 5) fig, ax1, ax2 = test1.draw() ax2.axvline(1 / T1_tst, color='k', ls='--', lw=2, label='True 1/$T_1$') ax2.axvline(test1.par[0], color='r', ls=':', lw=2, label='Fitted 1/$T_1$') fig.suptitle("Exponential Relaxation") ax2.legend() # BIEXPONENTIAL - well separated ============================================== if 0: print("Running BIEXPONENTIAL - welll separated")