def s12_time_profile(krdst, Tnbins, Trange, timeStamps, s2lim=(8e+3, 1e+4), s1lim=(10, 11), figsize=(8, 8)): xfmt = md.DateFormatter('%d-%m %H:%M') fig = plt.figure(figsize=figsize) x, y, yu = fitf.profileX(krdst.T, krdst.E, Tnbins, Trange) ax = fig.add_subplot(1, 2, 1) #plt.figure() #ax=plt.gca() #fig.add_subplot(1, 2, 1) ax.xaxis.set_major_formatter(xfmt) plt.errorbar(timeStamps, y, yu, fmt="kp", ms=7, lw=3) plt.xlabel('date') plt.ylabel('S2 (pes)') plt.ylim(s2lim) plt.xticks(rotation=25) x, y, yu = fitf.profileX(krdst.T, krdst.S1, Tnbins, Trange) ax = fig.add_subplot(1, 2, 2) #ax=plt.gca() #xfmt = md.DateFormatter('%d-%m %H:%M') ax.xaxis.set_major_formatter(xfmt) plt.errorbar(timeStamps, y, yu, fmt="kp", ms=7, lw=3) plt.xlabel('date') plt.ylabel('S1 (pes)') plt.ylim(s1lim) plt.xticks(rotation=25) plt.tight_layout()
def lt(z, v, znbins, zrange, vnbins, vrange, plot=True): """ compute the lifetime of v-variable (S2e, Se1, E) vs Z """ zbins = np.linspace(*zrange, znbins + 1) vbins = np.linspace(*vrange, vnbins + 1) x, y, yu = fitf.profileX(z, v, znbins, zrange) seed = expo_seed(x, y) f = fitf.fit(fitf.expo, x, y, seed, sigma=yu) if (not plot): return f #print('energy_0', f.values[0], ' +- ', f.errors[0] ) #print('lifetime', f.values[1], ' +- ', f.errors[1] ) frame_data = plt.gcf().add_axes((.1, .3, .8, .6)) plt.hist2d(z, v, (zbins, vbins)) x, y, yu = fitf.profileX(z, v, znbins, zrange) plt.errorbar(x, y, yu, np.diff(x)[0] / 2, fmt="kp", ms=7, lw=3) plt.plot(x, f.fn(x), "r-", lw=4) frame_data.set_xticklabels([]) labels("", "Energy (pes)", "Lifetime fit") lims = plt.xlim() frame_res = plt.gcf().add_axes((.1, .1, .8, .2)) plt.errorbar(x, (f.fn(x) - y) / yu, 1, np.diff(x)[0] / 2, fmt="p", c="k") plt.plot(lims, (0, 0), "g--") plt.xlim(*lims) plt.ylim(-5, +5) labels("Drift time (µs)", "Standarized residual") return f
def test_lt_profile_yields_same_result_expo_fit(): Nevt = int(1e5) e0 = 1e+4 # pes std = 0.05 * e0 lt = 2000 # lifetime in mus nbins_z = 12 range_z = (1, 500) z, es = energy_lt_experiment(Nevt, e0, lt, std) x, y, yu = fitf.profileX(z, es, nbins_z, range_z) valid_points = ~np.isnan(yu) x = x [valid_points] y = y [valid_points] yu = yu[valid_points] seed = expo_seed(x, y) f = fitf.fit(fitf.expo, x, y, seed, sigma=yu) par = np.array(f.values) err = np.array(f.errors) e0 = par[0] lt = - par[1] e0_u = err[0] lt_u = err[1] _, _, fr = fit_lifetime_profile(z, es, nbins_z, range_z) assert e0 == approx(fr.par[0], rel=0.05) assert lt == approx(fr.par[1], rel=0.05) assert e0_u == approx(fr.err[0], rel=0.05) assert lt_u == approx(fr.err[1], rel=0.05)
def lifetimes_in_TRange(kre: KrEvent, krnb: KrNBins, krb: KrBins, krr: KrRanges, TL) -> List[KrFit]: """ Plots lifetime fitted to a range of T values""" # Specify the range and number of bins in Z Znbins = krnb.Z Zrange = krr.Z kfs = [] for tlim in TL: # select data kre_t = select_in_TRange(kre, *tlim) z, e = kre_t.Z, kre_t.E x, y, yu = fitf.profileX(z, e, Znbins, Zrange) # Fit profile to an exponential seed = expo_seed(x, y) f = fitf.fit(fitf.expo, x, y, seed, sigma=yu) kf = KrFit(par=np.array(f.values), err=np.array(f.errors), chi2=chi2(f, x, y, yu)) #krf.print_fit(kf) kfs.append(kf) return kfs
def plt_v_vs_u(V, U, Vnbins, Unbins, Vrange, Urange , Vname='', Uname=''): c = hst.Canvas(1, 2) hst.hist2d(U, V, (Unbins, Vnbins), (Urange, Vrange), canvas=c(1), xylabels=(Uname, Vname)); xs, ys, eys = fitf.profileX(U, V, Unbins, xrange=Urange) hst.errorbar(xs, ys, yerr=eys, fmt='*', canvas=c(1), c='black') ymean = np.mean(ys) hst.hist(100.*ys/ymean, 20, canvas=c(2), xylabels=(' deviation ratio (%)', '')); return c
def profile1d(z: np.array, e: np.array, nbins_z: int, range_z: np.array) -> Tuple[float, float, float]: """Adds an extra layer to profileX, returning only valid points""" x, y, yu = fitf.profileX(z, e, nbins_z, range_z) valid_points = ~np.isnan(yu) x = x[valid_points] y = y[valid_points] yu = yu[valid_points] return x, y, yu
def lifetime_calculation(z, zrange, energy, erange, elim, slope, axes): """ Measure the lifetime for the given events and plots the raw energy distribution and the energy vs drift time in a separate axes instance. Parameters: z = Array of events' drift time zrange = Range in drift time for the fit. energy = Array of events' energy. erange = Range in energy for plotting. elim = Limits for the energy at z=0. slope = slope of the exponential, used in the selection of the events, should be the inverse of the expected lifetime (1/[expected lifetime]) axes = Axes object from a subplot, should have length >= 2. """ axes[0].hist(energy, 50, erange, rasterized=True) histFun.labels("S2 energy (pes)", "Entries", "Fiducialized energy spectrum") low_cut = elim[0] * np.exp(-slope * z) high_cut = elim[1] * np.exp(-slope * z) sel = coref.in_range(energy, low_cut, high_cut) # remove low and high E background axes[1].hist2d(z, energy, (100, 50), range=(zrange, erange), rasterized=True) x, y, u_y = fitf.profileX(z[sel], energy[sel], 100, xrange=zrange, yrange=erange) axes[1].plot(x, y, "--k", rasterized=True) axes[1].plot(z[z < 500], low_cut[z < 500], "k.", rasterized=True) axes[1].plot(z[z < 500], high_cut[z < 500], "k.", rasterized=True) Zrange_LT = zrange seed = np.max(y), (x[15] - x[5]) / np.log(y[15] / y[5]) f = fitf.fit(fitf.expo, x, y, seed, fit_range=Zrange_LT, sigma=u_y) axes[1].plot(x, f.fn(x), "r", lw=3, rasterized=True) print("Energy at z=0 = {:.1f} +- {:.1f}".format(f.values[0], f.errors[0])) print("Lifetime = {:.1f} +- {:.1f}".format(-f.values[1], f.errors[1])) print("Chi2 = {:.2f} \n".format(f.chi2)) # axes[1].text(zrange[0] + 0.05*(zrange[1]-zrange[0]), erange[0] + 1000, \ # "Lifetime = {:.1f} $\pm$ {:.1f}".format(-f.values[1], f.errors[1]), color = "white")#, fontsize = 14) histFun.labels("Drift time ($\mu$s)", "S2 energy (pes)", "") return np.array([-f.values[1], f.errors[1] ]), corrf.LifetimeCorrection(-f.values[1], f.errors[1])
def lifetimes_in_XYRange(kre: KrEvent, krnb: KrNBins, krb: KrBins, krr: KrRanges, xyr: XYRanges, XL=[(-125, -75), (-125, -75), (75, 125), (75, 125)], YL=[(-125, -75), (75, 125), (75, 125), (-125, -75)], nx=2, ny=2, figsize=(8, 8)) -> KrFit: """ Plots lifetime fitted to a range of XY values""" # Specify the range and number of bins in Z Znbins = krnb.Z Zrange = krr.Z fig = plt.figure(figsize=figsize) # XL = [(-125, -75), (-125, -75), (75, 125),(75, 125)] # YL = [(-125, -75), (75, 125), (75, 125),(-125, -75)] KF = [] for i, pair in enumerate(zip(XL, YL)): xlim = pair[0] ylim = pair[1] print(f'xlim = {xlim}, ylim ={ylim}') # select data in region defined by xyr xyr = XYRanges(X=xlim, Y=ylim) kre_xy = select_in_XYRange(kre, xyr) z, e = kre_xy.Z, kre_xy.E ax = fig.add_subplot(nx, ny, i + 1) x, y, yu = fitf.profileX(z, e, Znbins, Zrange) plt.errorbar(x, y, yu, np.diff(x)[0] / 2, fmt="kp", ms=7, lw=3) # Fit profile to an exponential seed = expo_seed(x, y) f = fitf.fit(fitf.expo, x, y, seed, sigma=yu) # plot fitted value plt.plot(x, f.fn(x), "r-", lw=4) labels("", "Energy (pes)", "Lifetime fit") kf = KrFit(par=np.array(f.values), err=np.array(f.errors), chi2=chi2(f, x, y, yu)) KF.append(kf) return KF
def fit_profile_1d_expo(xdata, ydata, nbins, *args, **kwargs): """ Make a profile of the input data and fit it to an exponential function with the parameters automatically estimated. """ x, y, yu = fitf.profileX(xdata, ydata, nbins, *args, **kwargs) valid_points = yu > 0 x = x [valid_points] y = y [valid_points] yu = yu[valid_points] seed = expo_seed(x, y) f = fitf.fit(fitf.expo, x, y, seed, sigma=yu) assert np.all(f.values != seed) return f
def _hprofile(u, v, bins, std=False, **kwargs): #hst.hist2d(u, v, bins, **kwargs) n = len(bins[0]) - 1 urange = u.min(), u.max() if (not isinstance(bins[0], int)): n = len(bins[0]) - 1 urange = bins[0].min(), bins[0].max() if (not isinstance(bins[1], int)): vrange = bins[1].min(), bins[1].max() if 'range' in kwargs: urange = kwargs['range'][0] vrange = kwargs['range'][1] # print('hprofile ', n, urange, vrange) xs, ys, eys = fitf.profileX(u, v, n, urange, vrange, std=std) cc = hst.errorbar(xs, ys, yerr=eys, **kwargs) return cc
def energy_X_profile(X: np.array, E: np.array, xnbins: int, xrange: Tuple[float, float], xlabel: str = 'R', erange: Tuple[float, float] = (9e+3, 14e+3), figsize: Tuple[float, float] = (10, 8)): fig = plt.figure(figsize=figsize) x, y, yu = fitf.profileX(X, E, xnbins, xrange) ax = fig.add_subplot(1, 1, 1) plt.errorbar(x, y, yu, fmt="kp", ms=7, lw=3) plt.xlabel(xlabel) plt.ylabel('E (pes)') plt.ylim(erange)
def profile_and_fit(X, Y, xrange, yrange, nbins, fitpar, label): fitOpt = "r" xe = (xrange[1] - xrange[0]) / nbins x, y, sy = fitf.profileX(X, Y, nbins=nbins, xrange=xrange, yrange=yrange, drop_nan=True) sel = in_range(x, xrange[0], xrange[1]) x, y, sy = x[sel], y[sel], sy[sel] f = fitf.fit(fitf.expo, x, y, fitpar, sigma=sy) plt.errorbar(x=x, xerr=xe, y=y, yerr=sy, linestyle='none', marker='.') plt.plot(x, f.fn(x), fitOpt) #set_plot_labels(xlabel=label[0], ylabel=label[1], grid=True) return f, x, y, sy
def build_correction_map_rad(kr_df : pd.DataFrame, num_bins : int ) -> pd.DataFrame: rad, pes, pes_error = profileX(kr_df.rad_rec, kr_df.s2_pes, num_bins) corr = pes / pes.min() # Plotting corrections if running interactively if is_interactive(): fig, (ax1, ax2) = plt.subplots(1,2, figsize=(16,7)) ax1.errorbar(rad, pes, pes_error, fmt="kp", ms=7, lw=3) ax1.set_title ("S2 pes (with errors)" , size=16); ax1.set_xlabel("Radius (mm)", size=16); ax1.set_ylabel("pes" , size=16); ax2.plot(rad, corr) ax2.set_title ("Correction factor" , size=16); ax2.set_xlabel("Radius (mm)", size=16); return pd.DataFrame({'rad': rad, 'correction': corr})
def lifetime_in_XYRange(kre: KrEvent, krnb: KrNBins, krb: KrBins, krr: KrRanges, xyr: XYRanges) -> KrFit: """ Fits lifetime to a range of XY values""" # select data in region defined by xyr kre_xy = select_in_XYRange(kre, xyr) z, e = kre_xy.Z, kre_xy.E # Specify the range and number of bins in Z Znbins = krnb.Z Zrange = krr.Z # create a figure and plot 2D histogram and profile frame_data = plt.gcf().add_axes((.1, .3, .8, .6)) plt.hist2d(z, e, (krb.Z, krb.E)) x, y, yu = fitf.profileX(z, e, Znbins, Zrange) plt.errorbar(x, y, yu, np.diff(x)[0] / 2, fmt="kp", ms=7, lw=3) # Fit profile to an exponential seed = expo_seed(x, y) f = fitf.fit(fitf.expo, x, y, seed, sigma=yu) # plot fitted value plt.plot(x, f.fn(x), "r-", lw=4) # labels and ticks frame_data.set_xticklabels([]) labels("", "Energy (pes)", "Lifetime fit") # add a second frame lims = plt.xlim() frame_res = plt.gcf().add_axes((.1, .1, .8, .2)) # Plot (y - f(x)) / sigma(y) as a function of x plt.errorbar(x, (f.fn(x) - y) / yu, 1, np.diff(x)[0] / 2, fmt="p", c="k") plt.plot(lims, (0, 0), "g--") plt.xlim(*lims) plt.ylim(-5, +5) labels("Drift time (µs)", "Standarized residual") return KrFit(par=np.array(f.values), err=np.array(f.errors), chi2=chi2(f, x, y, yu))
def energy_time_profile(T: np.array, E: np.array, Tnbins: int, Trange: Tuple[float, float], timeStamps: List[datetime.datetime], erange: Tuple[float, float] = (9e+3, 14e+3), figsize: Tuple[float, float] = (10, 8)): xfmt = md.DateFormatter('%d-%m %H:%M') fig = plt.figure(figsize=figsize) x, y, yu = fitf.profileX(T, E, Tnbins, Trange, erange) ax = fig.add_subplot(1, 1, 1) ax.xaxis.set_major_formatter(xfmt) plt.errorbar(timeStamps, y, yu, fmt="kp", ms=7, lw=3) plt.xlabel('date') plt.ylabel('E (pes)') plt.ylim(erange) plt.xticks(rotation=25)
def fit_lifetime_from_profile(kre: KrEvent, kR: KrRanges, kNB: KrNBins, kB: KrBins, kL: KrRanges, title="Lifetime Fit") -> KrFit: sel = in_range(kre.X, *kL.XY) & in_range(kre.Y, *kL.XY) z, e = kre.Z[sel], kre.E[sel] frame_data = plt.gcf().add_axes((.1, .35, .8, .6)) plt.hist2d(z, e, (kB.Z, kB.E)) x, y, yu = fitf.profileX(z, e, kNB.Z, kR.Z, kR.E) plt.errorbar(x, y, yu, np.diff(x)[0] / 2, fmt="kp", ms=7, lw=3) seed = expo_seed(x, y) f = fitf.fit(fitf.expo, x, y, seed, sigma=yu) plt.plot(x, f.fn(x), "r-", lw=4) frame_data.set_xticklabels([]) labels("", "Energy (pes)", title) lims = plt.xlim() frame_res = plt.gcf().add_axes((.1, .1, .8, .2)) plt.errorbar(x, (f.fn(x) - y) / yu, 1, np.diff(x)[0] / 2, fmt="p", c="k") plt.plot(lims, (0, 0), "g--") plt.xlim(*lims) plt.ylim(-5, +5) labels("Drift time (µs)", "Standarized residual") print_fit(f) print('chi2 = {}'.format(chi2(f, x, y, yu))) return KrFit(par=np.array(f.values[1]), err=np.array(f.errors[1]), chi2=np.array(chi2(f, x, y, yu)), valid=np.ones(1))
def lt_lsqfit(Z, E, chi2=True, nbins=12): """ unbinned fit to the lifetime, return e0, lt best estimate, chi2, and valid (bool) flag if chi2 is False, return 0 for chi2, nbins is the number of bins to compute the chi2 (default 12) """ ok = True e0, lt, e0u, ltu, xchi2 = 0, 0, 0, 0, 0 DE = -np.log(E) try: cc, cov = np.polyfit(Z, DE, 1, full=False, cov=True) #print(cov) a, b = cc[0], cc[1] lt = 1 / a ltu = lt * lt * np.sqrt(cov[0, 0]) e0 = np.exp(-b) e0u = e0 * np.sqrt(cov[1, 1]) except: ok = False pass # print('lifetime :', lt, '+-', ult) # print('e0 :', e0, '+-', ue0) me0, mlt = Measurement(e0, e0u), Measurement(lt, ltu) if (not chi2 or not ok): return me0, mlt, xchi2, ok xs, ys, uys = fitf.profileX(Z, DE, nbins) c*k = ~np.isnan(uys) try: res = (a * xs[c*k] + b - ys[c*k]) / uys[c*k] # print(res, uys) xchi2 = np.sum(res * res) / (1. * len(xs) - 1) except: ok = False pass #print(me0, mlt, chi2) return me0, mlt, xchi2, ok
def psf_fit(mapDST, DX, DY, groupZ, groupQ, z_int, Ec_conv, sigma_range, sipm_lim, cut, min_count, run_number): """ Calculates (through a data-driven fit) and returns the transverse spread for a given dataset. Parameters: mapDST = Full raw DST. DX = Difference in position between event and sensor in x-dimension. DY = Difference in position between event and sensor in y-dimension. groupQ = Charge of the SiPMs. groupZ = Mean drift time of the event. z_int = Drift time interval to do the fit. Ec_conv = Collection (for several sigmas) of PSF+gaussian convolutions (for the fit). sigma_range = Collection of sigmas considered in the convolution. sipm_lim = Limit in the distance point-sensor for the fit. cut = Minimum charge in a bin to be used for the fit. min_count = Minimum number of events in a bin to be used for the fit. """ fig, axes = plt.subplots(1, 3, figsize=(24, 6)) chimin = 1e20 sipm_xyrange = (-sipm_lim, sipm_lim) binfactor = 2. sigmamin = 0. bin_lim_min = -50. sipm_lim_min = -50. nbins = int(binfactor * sipm_lim) # Plot the distribution to be fitted. base_selection = ((np.sqrt(DX**2 + DY**2) < 50.) & (mapDST.Q > 0.) & (mapDST.Q.values / groupQ < 1.)) z_interval_selection = base_selection & coref.in_range( groupZ, z_int[0], z_int[1]) xc, Ec, Ece = fitf.profileX(DX[z_interval_selection], mapDST[z_interval_selection].Q/groupQ[z_interval_selection],\ nbins, sipm_xyrange) try: x_min = xc[(Ec < cut) & (xc < 0)].max() x_max = xc[(Ec < cut) & (xc > 0)].min() except: x_min = xc.min() x_max = xc.max() fig1 = plt.figure() ax1 = fig.add_subplot(1, 1, 1) (xc, yc, Ec_m, Ece_m), _, cb = \ histFun.hist2d_profile(DX[z_interval_selection], DY[z_interval_selection], mapDST[z_interval_selection].Q.values / groupQ[z_interval_selection],\ nbins, nbins, sipm_xyrange, sipm_xyrange, new_figure = False, rasterized=True) cb.set_label("Charge fraction") histFun.labels("X (mm)", "Y (mm)", "") ax1.set_xticks(np.linspace(-20., 20., 5)) ax1.set_yticks(np.linspace(-20., 20., 5)) fig1.tight_layout() fig1.savefig("TransPSF_Z{0}-{1}_R{2}.pdf".format(z_int[0], z_int[1], run_number)) fig, axes = plt.subplots(1, 3, figsize=(24, 6)) plt.axes(axes[0]) (xc, yc, Ec_m, Ece_m), _, cb = \ histFun.hist2d_profile(DX[z_interval_selection], DY[z_interval_selection], mapDST[z_interval_selection].Q.values / groupQ[z_interval_selection],\ nbins, nbins, sipm_xyrange, sipm_xyrange, new_figure = False, rasterized=True) cb.set_label("E (pes)") histFun.labels("X (mm)", "Y (mm)", "") # Fit. Ec_fit = 0 Ec = Ec_m Ece = Ece_m counts, *_ = np.histogram2d(DX[z_interval_selection], DY[z_interval_selection],\ [np.linspace(-sipm_lim, sipm_lim, nbins+1), np.linspace(-sipm_lim, sipm_lim, nbins+1)]) for lim in np.arange(sipm_lim, sipm_lim + 1, 1.): bin_lim = [len(xc[xc < x_min]), len(xc[xc < x_max]) + 1] Ec = Ec_m[bin_lim[0]:bin_lim[1], bin_lim[0]:bin_lim[1]] Ece = Ece_m[bin_lim[0]:bin_lim[1], bin_lim[0]:bin_lim[1]] counts_sel = counts[bin_lim[0]:bin_lim[1], bin_lim[0]:bin_lim[1]] for i, sigma in enumerate(sigma_range): Ec_c = Ec_conv[i] Ec_c = Ec_c[bin_lim[0]:bin_lim[1], bin_lim[0]:bin_lim[1]] Ec_c = Ec_c * Ec.sum() / Ec_c.sum() sel = counts_sel > min_count chi = ((Ec[sel] - Ec_c[sel])**2 / (Ece[sel]**2)).sum() / (len(Ec[sel]) - 1) if chi < chimin: chimin = chi sigmamin = sigma Ec_fit = Ec_c # Plot the projection at x = 0 / y = 0 xproj = Ec_m[np.argmax(Ec_m.sum(axis=0))] yproj = Ec_m[:, np.argmax(Ec_m.sum(axis=0))] xprojf = Ec_fit[np.argmax(Ec_fit.sum(axis=0))] yprojf = Ec_fit[:, np.argmax(Ec_fit.sum(axis=1))] h1d(xc, len(xc), [np.min(xc), np.max(xc)], weights=xproj, ax=axes[1]) axes[1].plot(xc[bin_lim[0]:bin_lim[1]], xprojf, "r", lw=1.25, rasterized=True) axes[1].set_xlabel("X (mm)") axes[1].set_ylabel("Charge fraction") axes[1].grid(True) h1d(yc, len(yc), [np.min(yc), np.max(yc)], weights=yproj, ax=axes[2]) axes[2].plot(yc[bin_lim[0]:bin_lim[1]], yprojf, "r", lw=1.25, rasterized=True) axes[2].set_xlabel("Y (mm)") axes[2].set_ylabel("Charge fraction") axes[2].grid(True) fig.tight_layout() extent = axes[0].get_tightbbox(fig.canvas.get_renderer()).transformed( fig.dpi_scale_trans.inverted()) axes[0].set_xticks(np.linspace(-20., 20., 5)) axes[0].set_yticks(np.linspace(-20., 20., 5)) # fig.savefig("TransPSF_Z{0}-{1}_R{2}.pdf".format(z_int[0], z_int[1], run_number), bbox_inches=extent) extent = axes[1].get_tightbbox(fig.canvas.get_renderer()).transformed( fig.dpi_scale_trans.inverted()) fig.savefig("TransPSF_X_Z{0}-{1}_R{2}.pdf".format(z_int[0], z_int[1], run_number), bbox_inches=extent) extent = axes[2].get_tightbbox(fig.canvas.get_renderer()).transformed( fig.dpi_scale_trans.inverted()) fig.savefig("TransPSF_Y_Z{0}-{1}_R{2}.pdf".format(z_int[0], z_int[1], run_number), bbox_inches=extent) axes[0].set_title("Energy vs XY for Z = [{0},{1}]".format( z_int[0], z_int[1])) axes[1].set_title("Charge profile X projection at Y = 0") axes[2].set_title("Charge profile Y projection at X = 0") fig.tight_layout() plt.close(fig) return sigmamin, chimin
def fit_and_plot_slices_2d_expo( kre: KrEvent, krnb: KrNBins, krb: KrBins, krr: KrRanges, fit_var="E", min_entries=1e2, figsize=(12, 12)) -> KrLTSlices: """ Slice the data in x and y, make the profile in z of E, fit it to a exponential and return the relevant values. """ xybins = krb.XY nbins_xy = np.size(xybins) - 1 nbins_z = krnb.Z nbins = nbins_xy, nbins_xy const = np.zeros(nbins) slope = np.zeros(nbins) constu = np.zeros(nbins) slopeu = np.zeros(nbins) chi2 = np.zeros(nbins) valid = np.zeros(nbins, dtype=bool) zrange = krr.Z fig = plt.figure(figsize=figsize) # Creates a new figure k = 0 index = 0 for i in range(nbins_xy): sel_x = in_range(kre.X, *xybins[i:i + 2]) for j in range(nbins_xy): index += 1 #print(f' bin =({i},{j}); index = {index}') if k % 25 == 0: k = 0 fig = plt.figure(figsize=figsize) ax = fig.add_subplot(5, 5, k + 1) k += 1 sel_y = in_range(kre.Y, *xybins[j:j + 2]) sel = sel_x & sel_y entries = np.count_nonzero(sel) if entries < min_entries: print( f'entries ={entries} not enough to fit bin (i,j) =({i},{j})' ) valid[i, j] = False continue try: z = kre.Z[sel] t = kre.E[sel] if fit_var == "Q": t = kre.Q[sel] x, y, yu = fitf.profileX(z, t, nbins_z, zrange) ax.errorbar(x, y, yu, np.diff(x)[0] / 2, fmt="kp", ms=7, lw=3) seed = expo_seed(x, y) f = fitf.fit(fitf.expo, x, y, seed, sigma=yu) plt.plot(x, f.fn(x), "r-", lw=4) plt.grid(True) re = np.abs(f.errors[1] / f.values[1]) #print(f' E +- Eu = {f.values[0]} +- {f.errors[0]}') #print(f' LT +- LTu = {-f.values[1]} +- {f.errors[1]}') #print(f' LTu/LT = {re} chi2 = {f.chi2}') if re > 0.5: print( f'Relative error to large, re ={re} for bin (i,j) =({i},{j})' ) print(f' LT +- LTu = {-f.values[1]} +- {f.errors[1]}') print(f' LTu/LT = {re} chi2 = {f.chi2}') valid[i, j] = False const[i, j] = f.values[0] constu[i, j] = f.errors[0] slope[i, j] = -f.values[1] slopeu[i, j] = f.errors[1] chi2[i, j] = f.chi2 valid[i, j] = True except: print(f'fit failed for bin (i,j) =({i},{j})') pass plt.tight_layout() return KrLTSlices(Ez0=Measurement(const, constu), LT=Measurement(slope, slopeu), chi2=chi2, valid=valid)
def fit_lifetime_slices(kre: KrEvent, krnb: KrNBins, krb: KrBins, krr: KrRanges, fit_var="E", min_entries=1e2) -> KrLTSlices: """ Slice the data in x and y, make the profile in z of E, fit it to a exponential and return the relevant values. """ xybins = krb.XY nbins_xy = np.size(xybins) - 1 nbins_z = krnb.Z nbins = nbins_xy, nbins_xy const = np.zeros(nbins) slope = np.zeros(nbins) constu = np.zeros(nbins) slopeu = np.zeros(nbins) chi2 = np.zeros(nbins) valid = np.zeros(nbins, dtype=bool) zrange = krr.Z for i in range(nbins_xy): sel_x = in_range(kre.X, *xybins[i:i + 2]) for j in range(nbins_xy): #print(f' bin =({i},{j}); index = {index}') sel_y = in_range(kre.Y, *xybins[j:j + 2]) sel = sel_x & sel_y entries = np.count_nonzero(sel) if entries < min_entries: #print(f'entries ={entries} not enough to fit bin (i,j) =({i},{j})') valid[i, j] = False continue try: z = kre.Z[sel] t = kre.E[sel] if fit_var == "Q": t = kre.Q[sel] x, y, yu = fitf.profileX(z, t, nbins_z, zrange) seed = expo_seed(x, y) f = fitf.fit(fitf.expo, x, y, seed, sigma=yu) re = np.abs(f.errors[1] / f.values[1]) #print(f' E +- Eu = {f.values[0]} +- {f.errors[0]}') #print(f' LT +- LTu = {-f.values[1]} +- {f.errors[1]}') #print(f' LTu/LT = {re} chi2 = {f.chi2}') const[i, j] = f.values[0] constu[i, j] = f.errors[0] slope[i, j] = -f.values[1] slopeu[i, j] = f.errors[1] chi2[i, j] = f.chi2 valid[i, j] = True if re > 0.5: # print(f'Relative error to large, re ={re} for bin (i,j) =({i},{j})') # print(f' LT +- LTu = {-f.values[1]} +- {f.errors[1]}') # print(f' LTu/LT = {re} chi2 = {f.chi2}') valid[i, j] = False except: print(f'fit failed for bin (i,j) =({i},{j})') pass return KrLTSlices(Es=Measurement(const, constu), LT=Measurement(slope, slopeu), chi2=chi2, valid=valid)