def seeds_and_bounds(bins, spec, ped_vals): norm_seed = spec.sum() GSeed = 0 GSSeed = 0 pDL = find_peaks_cwt(spec, np.arange(4, 20), min_snr=1, noise_perc=5) p1pe = pDL[(bins[pDL] > 10) & (bins[pDL] < 20)] if len(p1pe) == 0: p1pe = np.argwhere(bins == 15)[0][0] else: p1pe = p1pe[spec[p1pe].argmax()] p1 = fitf.fit(fitf.gauss, bins[p1pe - 5:p1pe + 5], spec[p1pe - 5:p1pe + 5], seed=(spec[p1pe], bins[p1pe], 3.)) GSeed = p1.values[1] - ped_vals[1] if p1.values[2] <= ped_vals[2]: GSSeed = 0.5 else: GSSeed = np.sqrt(p1.values[2]**2 - ped_vals[2]**2) dscale = spec[bins < 5].sum() / fitf.gauss(bins[bins < 5], *ped_vals).sum() errs = np.sqrt(spec[bins < 5]) errs[errs == 0] = 1 fscale = fitf.fit(scaler, bins[bins < 5], spec[bins < 5], (dscale), sigma=errs) muSeed = -np.log(fscale.values[0]) if muSeed < 0: muSeed = 0.001 sd0 = (norm_seed, muSeed, GSeed, GSSeed) bd0 = [(0, 0, 0, 0.001), (1e10, 10000, 10000, 10000)] return sd0, bd0
def seeds_and_bounds(indx, func, bins, spec, ped_vals, ped_errs, lim_ped): global GainSeeds, SigSeeds, useSavedSeeds, scalerChis norm_seed = spec.sum() GSeed = 0 GSSeed = 0 if useSavedSeeds: GSeed = GainSeeds[indx] GSSeed = SigSeeds[indx] else: pDL = find_peaks_cwt(spec, np.arange(4, 20), min_snr=1, noise_perc=5) p1pe = pDL[(bins[pDL]>10) & (bins[pDL]<20)] if len(p1pe) == 0: p1pe = np.argwhere(bins==15)[0][0] else: p1pe = p1pe[spec[p1pe].argmax()] p1 = fitf.fit(fitf.gauss, bins[p1pe-5:p1pe+5], spec[p1pe-5:p1pe+5], seed=(spec[p1pe], bins[p1pe], 3.)) GSeed = p1.values[1] - ped_vals[1] if p1.values[2] <= ped_vals[2]: GSSeed = 0.5 else: GSSeed = np.sqrt(p1.values[2]**2 - ped_vals[2]**2) dscale = spec[bins<5].sum() / fitf.gauss(bins[bins<5], *ped_vals).sum() errs = np.sqrt(spec[bins<5]) errs[errs==0] = 1 fscale = fitf.fit(scaler, bins[bins<5], spec[bins<5], (dscale), sigma=errs) scalerChis.append(fscale.chi2) if scalerChis[-1] >= 500: print('Suspect channel index ', indx) muSeed = -np.log(fscale.values[0]) if muSeed < 0: muSeed = 0.001 if 'gau' in func: ped_seed = ped_vals[1] ped_min = ped_seed - lim_ped * ped_errs[1] ped_max = ped_seed + lim_ped * ped_errs[1] ped_sig_seed = ped_vals[2] ped_sig_min = max(0.001, ped_sig_seed - lim_ped * ped_errs[2]) ped_sig_max = ped_sig_seed + lim_ped * ped_errs[2] sd0 = (norm_seed, muSeed, ped_seed, ped_sig_seed, GSeed, GSSeed) bd0 = [(0, 0, ped_min, ped_sig_min, 0, 0.001), (1e10, 10000, ped_max, ped_sig_max, 10000, 10000)] #print('Seed check: ', sd0) return sd0, bd0 sd0 = (norm_seed, muSeed, GSeed, GSSeed) bd0 = [(0, 0, 0, 0.001), (1e10, 10000, 10000, 10000)] print('Seed check: ', sd0) return sd0, bd0
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 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 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 fit_s2_energy_in_z_bins_within_XY_limits( kre: KrEvent, kL: KrRanges, kNB: KrNBins, kB: KrBins, eR: Ranges, figsize=(12, 12)) -> KrFit: fig = plt.figure(figsize=figsize) # Creates a new figure EMU = [] ES = [] CHI2 = [] for j, i in enumerate(range(kNB.Z)): ax = fig.add_subplot(5, 2, i + 1) ax.set_xlabel('S2 energy (pes)', fontsize=11) #xlabel ax.set_ylabel('Number of events', fontsize=11) #ylabel zlim = kB.Z[i], kB.Z[i + 1] sel = in_range(kre.X, *kL.XY) & in_range(kre.Y, *kL.XY) & in_range( kre.Z, *zlim) e = kre.E[sel] print( f'bin : {j}, energy range for fit: lower = {eR.lower[j]}, upper = {eR.upper[j]}' ) sel = in_range(e, eR.lower[j], eR.upper[j]) er = e[sel] y, b, _ = ax.hist(er, bins=kB.E, histtype='step', edgecolor='black', linewidth=1.5) x = shift_to_bin_centers(b) df = pd.DataFrame(dict(y=y, x=x)) df = df[df.y > 0] fit_range = (df.x.values[0], df.x.values[-1]) x = df.x.values y = df.y.values yu = np.sqrt(y) seed = gauss_seed(x, y) f = fitf.fit(fitf.gauss, x, y, seed, fit_range=fit_range, sigma=yu) plt.plot(x, f.fn(x), "r-", lw=4) EMU.append(f.values[1]) ES.append(f.errors[1]) CHI2.append(chi2(f, x, y, yu)) plt.grid(True) plt.tight_layout() return KrFit(par=np.array(EMU), err=np.array(ES), chi2=np.array(CHI2), valid=np.ones(len(EMU)))
def _line_cut(sign): x = Zcenters[ok] y = mean.value[ok] + sign * nsigma * sigma.value[ok] yu = mean.uncertainty[ok] seed = expo_seed(x, y) efit = fitf.fit(fitf.expo, x, y, seed, sigma=yu) assert np.all(efit.values != seed) return efit.fn
def fit_lifetime(kB, kf, title="Lifetime Fit"): x = shift_to_bin_centers(kB.Z) y = kf.par yu = kf.err 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) print_fit(f) print('chi2 = {}'.format(chi2(f, x, y, yu)))
def quick_gauss_fit(data, bins): """ Histogram input data and fit it to a gaussian with the parameters automatically estimated. """ y, x = np.histogram(data, bins) x = shift_to_bin_centers(x) seed = gauss_seed(x, y) f = fitf.fit(fitf.gauss, x, y, seed) assert np.all(f.values != seed) return f
def test_chi2f_str_line(): def line(x, m, n): return m * x + n y = np.array([9.108e3, 10.34e3, 1.52387e5, 1.6202e5]) ey = np.array([3.17, 13.5, 70, 21]) x = np.array([29.7, 33.8, 481, 511]) fit = fitf.fit(line, x, y, seed=(1, 1), sigma=ey) f = lambda x: line(x, *fit.values) assert chi2f(f, 2, x, y, ey) == approx(14, rel=1e-02)
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 compute_drift_v(zdata: np.array, nbins: int = 35, zrange: Tuple[float, float] = (500, 640), seed: Tuple[float, float, float, float] = None, detector: str = 'new') -> Tuple[float, float]: """ Computes the drift velocity for a given distribution using the sigmoid function to get the cathode edge. Parameters ---------- zdata: array_like Values of Z coordinate. nbins: int (optional) The number of bins in the z coordinate for the binned fit. zrange: length-2 tuple (optional) Fix the range in z. seed: length-4 tuple (optional) Seed for the fit. detector: string (optional) Used to get the cathode position from DB. plot_fit: boolean (optional) Flag for plotting the results. Returns ------- dv: float Drift velocity. dvu: float Drift velocity uncertainty. """ y, x = np.histogram(zdata, nbins, zrange) x = shift_to_bin_centers(x) if seed is None: seed = np.max(y), np.mean(zrange), 0.5, np.min(y) z_cathode = DB.DetectorGeo(detector).ZMAX[0] try: f = fitf.fit(sigmoid, x, y, seed, sigma=poisson_sigma(y), fit_range=zrange) dv = z_cathode / f.values[1] dvu = dv / f.values[1] * f.errors[1] except RuntimeError: print( "WARNING: Sigmoid fit for dv computation fails. NaN value will be set in its place." ) dv, dvu = np.nan, np.nan return dv, dvu
def plot_and_fit(data: List, title: str = '', xlabel: str = 'Charge (pes)', ylabel: str = 'Entries / bin', num_bins: int = 100) -> Tuple[float, float, float]: # Fitting function def gauss(x, amplitude, mu, sigma): return amplitude / (2 * np.pi)**.5 / sigma * np.exp( -0.5 * (x - mu)**2. / sigma**2.) # Plotting the data fig = plt.figure(figsize=(8, 5)) mean = np.mean(data) max_range = 0.1 plt_range = ((1. - max_range) * mean, (1. + max_range) * mean) y, x, _ = plt.hist(data, bins=num_bins, range=plt_range) x = shift_to_bin_centers(x) # Fitting data seed = 1000, mean, mean * 0.01 sigma = poisson_sigma(y) max_range = 0.05 fit_range = ((1. - max_range) * mean, (1. + max_range) * mean) f = fitf.fit(gauss, x, y, seed, fit_range=fit_range, sigma=sigma) amplitude, mu, sigma = f.values[0], f.values[1], f.values[2] mu_err, sigma_err = f.errors[1], f.errors[2] fwhm = 2.35 * sigma / mu # Plotting the gauss fit mx = np.linspace(fit_range[0], fit_range[1], 1000) legend = f"$\mu$ = {mu:.3f}\n" legend += f"$\sigma$ = {sigma:.3f}\n" legend += f"fwhm = {fwhm/units.perCent:.2f} %" plt.plot(mx, f.fn(mx), 'r-') plt.plot(mx, gauss(mx, amplitude, mu, sigma), 'r-', label=legend) plt.title(title, size=14) plt.xlabel(xlabel, size=14) plt.ylabel(ylabel, size=14) plt.legend(loc=1) plt.show() # Verbosing print(f"DATA from {title} ...\n" + \ f"mu = {mu:10.3f} +- {mu_err:.3f}\n" +\ f"sigma = {sigma:10.3f} +- {sigma_err:.3f} -> " + \ f"fwhm = {fwhm/units.perCent:.3f} %\n" f"Chi2 = {f.chi2:10.3f}\n") return mu, sigma, fwhm
def compute_drift_v(zdata: np.array, nbins: int = 35, zrange: Tuple[float, float] = (500, 640), seed: Tuple[float, float, float, float] = None, detector: str = 'new', plot_fit: bool = False) -> Tuple[float, float]: """ Computes the drift velocity for a given distribution using the sigmoid function to get the cathode edge. Parameters ---------- zdata: array_like Values of Z coordinate. nbins: int (optional) The number of bins in the z coordinate for the binned fit. zrange: length-2 tuple (optional) Fix the range in z. seed: length-4 tuple (optional) Seed for the fit. detector: string (optional) Used to get the cathode position from DB. plot_fit: boolean (optional) Flag for plotting the results. Returns ------- dv: float Drift velocity. dvu: float Drift velocity uncertainty. """ y, x = np.histogram(zdata, nbins, zrange) x = shift_to_bin_centers(x) if seed is None: seed = np.max(y), np.mean(zrange), 0.5, np.min(y) f = fitf.fit(sigmoid, x, y, seed, sigma=poisson_sigma(y), fit_range=zrange) z_cathode = DB.DetectorGeo(detector).ZMAX[0] dv = z_cathode / f.values[1] dvu = dv / f.values[1] * f.errors[1] if plot_fit: plt.figure() plt.hist(zdata, nbins, zrange) xx = np.linspace(zrange[0], zrange[1], nbins) plt.plot(xx, sigmoid(xx, *f[1]), color='red') return dv, dvu
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 fit_intensity(DF, sigma, imax=200, figsize=(10, 10)): I = avg_intensity(DF) X = np.arange(len(I)) seed = expo_seed(X, I) f = fitf.fit(fitf.expo, X, I, seed, sigma=sigma * np.ones(len(I))) fig = plt.figure(figsize=figsize) plt.errorbar(X, I, fmt="kp", yerr=sigma * np.ones(len(I)), ms=7, ls='none') plt.plot(X, f.fn(X), lw=3) plt.ylim(0, imax) plt.xlabel('shot number') plt.ylabel('I (a.u.)') plt.show() print(f'Fit function -->{f}') return f.values, f.errors
def gfit(x : np.array, y : np.array, yu : np.array, fseed : Tuple[float, float, float]) ->Tuple[FitPar, FitResult]: f = fitf.fit(fitf.gauss, x, y, fseed, sigma=yu) c2 = chi2(f, x, y, yu) par = np.array(f.values) err = np.array(f.errors) xu = np.diff(x) * 0.5 fr = FitResult(par = par, err = err, chi2 = c2, valid = True) fp = FitPar(x = x, y = y, xu = xu, yu = yu, f = f.fn) return fp, fr
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 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 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 energy_in_XYRange(kre: KrEvent, xr: Tuple[float], yr: Tuple[float], ernb: ExyzNBins) -> KrFit: sel = in_range(kre.X, *xr) & in_range(kre.Y, *yr) e = kre.E[sel] bins = ernb.E frame_data = plt.gcf().add_axes((.1, .3, .8, .6)) y, b, _ = plt.hist(e, bins=bins, histtype='step', edgecolor='black', linewidth=1.5) x = shift_to_bin_centers(b) seed = gauss_seed(x, y) fit_range = seed[1] - 2.0 * seed[2], seed[1] + 2.0 * seed[2] x, y = x[in_range(x, *fit_range)], y[in_range(x, *fit_range)] f = fitf.fit(fitf.gauss, x, y, seed, sigma=poisson_sigma(y)) yu = poisson_sigma(y) plt.plot(x, f.fn(x), "r-", lw=4) frame_data.set_xticklabels([]) labels("", "Entries", "Energy fit example") 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) kf = KrFit(par=np.array(f.values), err=np.array(f.errors), chi2=chi2(f, x, y, yu)) return kf
def main(): """ Fitting for pmt response to ~spe led pulses """ file_name = sys.argv[1] func_name = sys.argv[2] min_stat = 0 fix_ped = False if len(sys.argv) > 3: use_db_gain_seeds = True if 'true' in sys.argv[3] else False min_stat = int(sys.argv[4]) dats = tb.open_file(file_name, 'r') bins = np.array(dats.root.HIST.pmt_dark_bins) specsD = np.array(dats.root.HIST.pmt_dark).sum(axis=0) specsL = np.array(dats.root.HIST.pmt_spe).sum(axis=0) run_no = get_run_number(dats) sensor_type = SensorType.SIPM if 'sipm' in file_name else SensorType.PMT ffuncs = { 'ngau': speR.poisson_scaled_gaussians(n_gaussians=7), 'intgau': speR.poisson_scaled_gaussians(min_integral=100), 'dfunc': partial(speR.scaled_dark_pedestal, min_integral=100), 'conv': partial(speR.dark_convolution, min_integral=100) } pOrders = { 'ngau': 'norm err poismu err ped err pedSig err gain err 1peSig err', 'intgau': 'norm err poismu err ped err pedSig err gain err 1peSig err', 'dfunc': 'norm err poismu err gain err 1peSig err', 'conv': 'norm err poismu err gain err 1peSig err' } fnam = { 'ngau': 'poisson_scaled_gaussians_ngau', 'intgau': 'poisson_scaled_gaussians_min', 'dfunc': 'scaled_dark_pedestal', 'conv': 'dark_convolution' } ## pOut = open('pmtCalParOut_R'+file_name[-7:-3]+'_F'+func_name+'.dat', 'w') posRunNo = file_name.find('R') pOut = tb.open_file( 'pmtCalParOut_R' + file_name[posRunNo + 1:posRunNo + 5] + '_F' + func_name + '.h5', 'w') param_writer = pIO.channel_param_writer(pOut, sensor_type='pmt', func_name=fnam[func_name], param_names=pIO.generic_params) test_names = ['normalization', 'Pedestal', 'Pedestal_sig'] pTest_writer = pIO.channel_param_writer(pOut, sensor_type='pmt', func_name='Pedestal_gaussian', param_names=test_names, covariance=(3, 3)) outDict = {} testDict = {} for ich, (dspec, lspec) in enumerate(zip(specsD, specsL)): b1 = 0 b2 = len(dspec) if min_stat != 0: valid_bins = np.argwhere(lspec >= min_stat) b1 = valid_bins[0][0] b2 = valid_bins[-1][0] outDict[pIO.generic_params[-2]] = (bins[b1], bins[min(len(bins) - 1, b2)]) ## Fit the dark spectrum with a Gaussian (not really necessary for the conv option) gb0 = [(0, -100, 0), (1e99, 100, 10000)] av, rms = weighted_av_std(bins[dspec > 100], dspec[dspec > 100]) sd0 = (dspec.sum(), av, rms) errs = np.sqrt(dspec[dspec > 100]) errs[errs == 0] = 0.0001 gfitRes = fitf.fit(fitf.gauss, bins[dspec > 100], dspec[dspec > 100], sd0, sigma=errs, bounds=gb0) outDict[pIO.generic_params[2]] = (gfitRes.values[1], gfitRes.errors[1]) outDict[pIO.generic_params[3]] = (gfitRes.values[2], gfitRes.errors[2]) testDict[test_names[0]] = (gfitRes.values[0], gfitRes.errors[0]) testDict[test_names[1]] = (gfitRes.values[1], gfitRes.errors[1]) testDict[test_names[2]] = (gfitRes.values[2], gfitRes.errors[2]) testDict["covariance"] = gfitRes.cov pTest_writer(ich, testDict) ## Scale just in case we lost a different amount of integrals in dark and led scale = lspec.sum() / dspec.sum() print('Scale check: ', scale) if 'dfunc' in func_name: respF = ffuncs[func_name](dark_spectrum=dspec[b1:b2] * scale, pedestal_mean=gfitRes.values[1], pedestal_sigma=gfitRes.values[2]) elif 'conv' in func_name: respF = ffuncs[func_name](dark_spectrum=dspec[b1:b2] * scale, bins=bins[b1:b2]) elif fix_ped: respF = partial(ffuncs[func_name], pedestal_mean=gfitRes.values[1], pedestal_sigma=gfitRes.values[2]) else: respF = ffuncs[func_name] ped_vals = np.array( [gfitRes.values[0] * scale, gfitRes.values[1], gfitRes.values[2]]) dark = dspec[b1:b2] scaler_func = dark_scaler(dark[bins[b1:b2] < 0]) seeds, bounds = seeds_and_bounds(sensor_type, run_no, ich, scaler_func, bins[b1:b2], lspec[b1:b2], ped_vals, 'new', gfitRes.errors, func='dfunc', use_db_gain_seeds=True) ## The fit errs = np.sqrt(lspec[b1:b2]) if not 'gau' in func_name: errs = np.sqrt(errs**2 + np.exp(-2 * seeds[1]) * dspec[b1:b2]) errs[errs == 0] = 1 #0.001 rfit = fitf.fit(respF, bins[b1:b2], lspec[b1:b2], seeds, sigma=errs, bounds=bounds) ## plot the result plt.errorbar(bins, lspec, xerr=0.5 * np.diff(bins)[0], yerr=np.sqrt(lspec), fmt='b.') plt.plot(bins[b1:b2], rfit.fn(bins[b1:b2]), 'r') plt.plot(bins[b1:b2], respF(bins[b1:b2], *seeds), 'g') plt.title('Spe response fit to channel ' + str(ich)) plt.xlabel('ADC') plt.ylabel('AU') outDict[pIO.generic_params[0]] = (rfit.values[0], rfit.errors[0]) outDict[pIO.generic_params[1]] = (rfit.values[1], rfit.errors[1]) gIndx = 2 if 'gau' in func_name: gIndx = 4 outDict[pIO.generic_params[4]] = (rfit.values[gIndx], rfit.errors[gIndx]) outDict[pIO.generic_params[5]] = (rfit.values[gIndx + 1], rfit.errors[gIndx + 1]) outDict[pIO.generic_params[-1]] = (respF.n_gaussians, rfit.chi2) param_writer(ich, outDict) plt.show(block=False) next_plot = input('press enter to move to next fit') if 's' in next_plot: plt.savefig('FitPMTCh' + str(ich) + '.png') plt.clf() plt.close() pOut.close()
def selection_in_band(E, Z, Erange, Zrange, Zfitrange, nsigma=3.5, Znbins=50, Enbins=100, plot=True): """ This returns a selection of the events that are inside the Kr E vz Z returns: np.array(bool) If plot=True, it draws E vs Z and the band """ Zfit = Zfitrange Zbins = np.linspace(*Zrange, Znbins + 1) Ebins = np.linspace(*Erange, Enbins + 1) Zcenters = shift_to_bin_centers(Zbins) Zerror = np.diff(Zbins) * 0.5 sel_e = in_range(E, *Erange) mean, sigma, chi2, ok = fit_slices_1d_gauss(Z[sel_e], E[sel_e], Zbins, Ebins, min_entries=5e2) ok = ok & in_range(Zcenters, *Zfit) def _line_cut(sign): x = Zcenters[ok] y = mean.value[ok] + sign * nsigma * sigma.value[ok] yu = mean.uncertainty[ok] seed = expo_seed(x, y) efit = fitf.fit(fitf.expo, x, y, seed, sigma=yu) assert np.all(efit.values != seed) return efit.fn lowE_cut = _line_cut(-1.) highE_cut = _line_cut(+1.) sel_inband = in_range(E, lowE_cut(Z), highE_cut(Z)) if (plot == False): return sel_inband plt.hist2d(Z, E, (Zbins, Ebins), cmap=default_cmap) plt.errorbar(Zcenters[ok], mean.value[ok], sigma.value[ok], Zerror[ok], "kp", label="Kr peak energy $\pm 1 \sigma$") f = fitf.fit(fitf.expo, Zcenters[ok], mean.value[ok], (1e4, -1e3)) plt.plot(Zcenters, f.fn(Zcenters), "r-") print(f.values) plt.plot(Zbins, lowE_cut(Zbins), "m", lw=2, label="$\pm " + str(nsigma) + " \sigma$ region") plt.plot(Zbins, highE_cut(Zbins), "m", lw=2) plt.legend() labels("Drift time (µs)", "S2 energy (pes)", "Energy vs drift") return sel_inband
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)
def fit_dataset(dataF=None, funcName=None, minStat=None, limitPed=None): """ Check new fit function on SiPM spectra """ global useSavedSeeds, GainSeeds, SigSeeds file_name = dataF func_name = funcName min_stat = minStat limit_ped = limitPed optimise = True if not file_name: optimise = False file_name = sys.argv[1] func_name = sys.argv[2] min_stat = 0 limit_ped = 10000. if len(sys.argv) > 3: useSavedSeeds = True if 'true' in sys.argv[3] else False min_stat = int(sys.argv[4]) limit_ped = int(sys.argv[5]) run_no = file_name[file_name.find('R')+1:file_name.find('R')+5] run_no = int(run_no) chNos = DB.DataSiPM(run_no).SensorID.values if useSavedSeeds: dodgy = DB.DataSiPM(run_no).index[DB.DataSiPM(run_no).Active==0].values GainSeeds = DB.DataSiPM(run_no).adc_to_pes.values SigSeeds = DB.DataSiPM(run_no).Sigma.values ## Give generic values to previously dead or dodgy channels GainSeeds[dodgy] = 15 SigSeeds[dodgy] = 2 sipmIn = tb.open_file(file_name, 'r') ## Bins are the same for dark and light, just use light for now bins = np.array(sipmIn.root.HIST.sipm_spe_bins) ## LED correlated and anticorrelated spectra: specsL = np.array(sipmIn.root.HIST.sipm_spe).sum(axis=0) specsD = np.array(sipmIn.root.HIST.sipm_dark).sum(axis=0) ffuncs = {'ngau':speR.poisson_scaled_gaussians(n_gaussians=7), 'intgau':speR.poisson_scaled_gaussians(min_integral=100), 'dfunc':partial(speR.scaled_dark_pedestal, min_integral=100), 'conv':partial(speR.dark_convolution, min_integral=100)} ## Loop over the specra: outData = [] outDict = {} llchans = [] if not optimise: fnam = {'ngau':'poisson_scaled_gaussians_ngau', 'intgau':'poisson_scaled_gaussians_min', 'dfunc':'scaled_dark_pedestal', 'conv':'dark_convolution'} pOut = tb.open_file('sipmCalParOut_R'+str(run_no)+'_F'+func_name+'.h5', 'w') param_writer = pIO.channel_param_writer(pOut, sensor_type='sipm', func_name=fnam[func_name], param_names=pIO.generic_params) ## Extra protection since 3065 is weird knownDead = [ 3056, 11009, 12058, 14010, 22028, 22029, 25049 ] specialCheck = [1006, 1007, 3000, 3001, 5010, 7000, 22029, 28056, 28057] for ich, (led, dar) in enumerate(zip(specsL, specsD)): if chNos[ich] in knownDead: outData.append([chNos[ich], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], 0, 0]) if not optimise: for kname in pIO.generic_params: outDict[kname] = (0, 0) param_writer(chNos[ich], outDict) print('no peaks in dark spectrum, spec ', ich) continue ## Limits for safe fit b1 = 0 b2 = len(dar) if min_stat != 0: valid_bins = np.argwhere(led>=min_stat) b1 = valid_bins[0][0] b2 = valid_bins[-1][0] outDict[pIO.generic_params[-2]] = (bins[b1], bins[min(len(bins)-1, b2)]) # Seed finding pD = find_peaks_cwt(dar, np.arange(2, 20), min_snr=2) if len(pD) == 0: ## Try to salvage in case not a masked channel ## Masked channels have al entries in one bin. if led[led>0].size == 1: outData.append([0., 0., 0., 0., 0., 0., 0.]) print('no peaks in dark spectrum, spec ', ich) continue else: pD = np.array([dar.argmax()]) ## Fit the dark spectrum with a Gaussian (not really necessary for the conv option) gb0 = [(0, -100, 0), (1e99, 100, 10000)] sd0 = (dar.sum(), 0, 2) errs = np.sqrt(dar[pD[0]-5:pD[0]+5]) errs[errs==0] = 0.1 gfitRes = fitf.fit(fitf.gauss, bins[pD[0]-5:pD[0]+5], dar[pD[0]-5:pD[0]+5], sd0, sigma=errs, bounds=gb0) outDict[pIO.generic_params[2]] = (gfitRes.values[1], gfitRes.errors[1]) outDict[pIO.generic_params[3]] = (gfitRes.values[2], gfitRes.errors[2]) ## Scale just in case we lost a different amount of integrals in dark and led ## scale = led.sum() / dar.sum() scale = 1 ## Take into account the scale in seed finding (could affect Poisson mu)???? ped_vals = np.array([gfitRes.values[0] * scale, gfitRes.values[1], gfitRes.values[2]]) binR = bins[b1:b2] global darr darr = dar[b1:b2] * scale darr = darr[binR<5] seeds, bounds = seeds_and_bounds(ich, func_name, bins[b1:b2], led[b1:b2], ped_vals, gfitRes.errors, limit_ped) ## Protect low light channels if seeds[1] < 0.2: llchans.append(chNos[ich]) ## Dodgy setting of high charge dark bins to zero dar[bins>gfitRes.values[1] + 3*gfitRes.values[2]] = 0 ## if 'dfunc' in func_name: respF = ffuncs[func_name](dark_spectrum=dar[b1:b2] * scale, pedestal_mean=gfitRes.values[1], pedestal_sigma=gfitRes.values[2]) elif 'conv' in func_name: respF = ffuncs[func_name](dark_spectrum=dar[b1:b2] * scale, bins=bins[b1:b2]) else: respF = ffuncs[func_name] ## The fit errs = np.sqrt(led) if not 'gau' in func_name: errs = np.sqrt(errs**2 + np.exp(-2 * seeds[1]) * dar) errs[errs==0] = 0.001 print('About to fit channel ', chNos[ich]) rfit = fitf.fit(respF, bins[b1:b2], led[b1:b2], seeds, sigma=errs[b1:b2], bounds=bounds) chi = rfit.chi2 ## Attempt to catch bad fits and refit (currently only valid for dfunc and conv) if chi >= 7 or rfit.values[3] >= 2.5 or rfit.values[3] <= 1: ## The offending parameter seems to be the sigma in most cases nseed = rfit.values nseed[3] = 1.7 nbound = [(bounds[0][0], bounds[0][1], bounds[0][2], 1), (bounds[1][0], bounds[1][1], bounds[1][2], 2.5)] rfit = fitf.fit(respF, bins[b1:b2], led[b1:b2], nseed, sigma=errs[b1:b2], bounds=nbound) chi = rfit.chi2 if not optimise: if chNos[ich] in specialCheck or chi >= 10 or rfit.values[2] < 12 or rfit.values[2] > 19 or rfit.values[3] > 3: if chNos[ich] in specialCheck: print('Special check channel '+str(chNos[ich])) print('Channel fit: ', rfit.values, chi) plt.errorbar(bins, led, xerr=0.5*np.diff(bins)[0], yerr=errs, fmt='b.') plt.plot(bins[b1:b2], respF(bins[b1:b2], *rfit.values), 'r') plt.plot(bins[b1:b2], respF(bins[b1:b2], *seeds), 'g') plt.title('Spe response fit to channel '+str(chNos[ich])) plt.xlabel('ADC') plt.ylabel('AU') plt.show() outData.append([chNos[ich], rfit.values, rfit.errors, respF.n_gaussians, chi]) outDict[pIO.generic_params[0]] = (rfit.values[0], rfit.errors[0]) outDict[pIO.generic_params[1]] = (rfit.values[1], rfit.errors[1]) gIndx = 2 if 'gau' in func_name: gaIndx = 4 outDict[pIO.generic_params[4]] = (rfit.values[gIndx], rfit.errors[gIndx]) outDict[pIO.generic_params[5]] = (rfit.values[gIndx+1], rfit.errors[gIndx+1]) outDict[pIO.generic_params[-1]] = (respF.n_gaussians, rfit.chi2) if not optimise: param_writer(chNos[ich], outDict) ## Couple of plots gainIndx = 2 if 'gau' in func_name: gainIndx = 4 plot_names = ["Gain", "1pe sigma", "Poisson mu", "chi2"] pVals = [np.fromiter((ch[1][gainIndx] for ch in outData), np.float), np.fromiter((ch[1][gainIndx+1] for ch in outData), np.float), np.fromiter((ch[1][1] for ch in outData), np.float), np.fromiter((ch[4] for ch in outData), np.float)] if optimise: sipmIn.close() return pVals pOut.close() #global scalerChis pos_x = DB.DataSiPM(run_no).X.values pos_y = DB.DataSiPM(run_no).Y.values chNos = DB.DataSiPM(run_no).SensorID.values ## vals2D = np.zeros((int((pos_x.max()-pos_x.min())/10)+1, int((pos_y.max()-pos_y.min())/10)+1)) ## print('shape: ', vals2D.shape) ## *_, chis = display_matrix(pos_x, pos_y, pVals[3]) ## Trampa #pVals[3][pVals[3]>10] = 0 plt.scatter(pos_x, pos_y, c=pVals[3]) plt.title("Fit chi^2 map") plt.xlabel("X (mm)") plt.ylabel("Y (mm)") plt.colorbar() plt.show() #mask = np.argwhere((pVals[2]>=2) & (pVals[2]<8)) #mask = np.argwhere((chNos<9000) | (chNos>=11000)) #plt.scatter(pos_x[mask], pos_y[mask], c=pVals[2][mask]) plt.scatter(pos_x, pos_y, c=pVals[2]) plt.title("Fit poisson mu") plt.xlabel("X (mm)") plt.ylabel("Y (mm)") plt.colorbar() plt.show() ## fg2, ax2 = plt.subplots() ## p = ax2.pcolor(pos_x, pos_y, scalerChis, cmap=cm.Spectral, vmin=np.abs(scalerChis).min(), vmax=np.abs(scalerChis).max()) ## plt.colorbar(p, ax=ax2) ## fg2.show() #plt.hist(scalerChis, bins=1000) #plt.show() fig, axes = plt.subplots(nrows=2, ncols=2, figsize=(20,6)) chiVs = pVals[3] for ax, val, nm in zip(axes.flatten(), pVals, plot_names): ax.hist(val[(chiVs<10) & (chiVs!=0)], bins=100) ax.set_title(nm) fig.show() input('finished with plots?') print('Low light chans:', llchans)
def fit_lifetime_profile( z: np.array, e: np.array, nbins_z: int, range_z: Tuple[float, float]) -> Tuple[FitPar, FitPar, FitResult]: """ Make a profile of the input data and fit it to an exponential function. Parameters ---------- z Array of z values. e Array of energy values. nbins_z Number of bins in Z for the profile fit. range_z Range in Z for fit. Returns ------- A Tuple with: FitPar : Fit parameters (arrays of fitted values and errors, fit function) FitPar : Fit parameters (duplicated to make it compatible with fit_liftime_unbined) FirResults: Fit results (lt, e0, errors, chi2) @dataclass class ProfilePar: x : np.array y : np.array xu : np.array yu : np.array @dataclass class FitPar(ProfilePar): f : FitFunction @dataclass class FitResult: par : np.array err : np.array chi2 : float valid : bool """ logging.debug(' fit_liftime_profile') logging.debug(f' len (z) ={len(z)}, len (e) ={len(e)} ') logging.debug(f' nbins_z ={nbins_z}, range_z ={range_z} ') fp = None valid = True c2 = NN par = NN * np.ones(2) err = NN * np.ones(2) x, y, yu = profile1d(z, e, nbins_z, range_z) xu = np.diff(x) * 0.5 seed = expo_seed(x, y) logging.debug(f' after profile: len (x) ={len(x)}, len (y) ={len(y)} ') try: f = fit(expo, x, y, seed, sigma=yu) c2 = f.chi2 par = np.array(f.values) par[1] = -par[1] err = np.array(f.errors) logging.debug(f' e0z ={par[0]} +- {err[0]} ') logging.debug(f' lt ={par[1]} +- {err[1]} ') logging.debug(f' c2 ={c2} ') fp = FitPar(x=x, y=y, xu=xu, yu=yu, f=f.fn) except: warnings.warn( f' fit failed for seed = {seed} in fit_lifetime_profile', UserWarning) valid = False raise fr = FitResult(par=par, err=err, chi2=c2, valid=valid) return fp, fp, fr
def main(): """ Fitting for pmt response to ~spe led pulses """ fileName = sys.argv[1] funcName = sys.argv[2] min_stat = 0 limit_ped = 10000. fix_ped = False if len(sys.argv) > 3: min_stat = int(sys.argv[3]) limit_ped = int(sys.argv[4]) if limit_ped == 0: fix_ped = True limit_ped = 10000 dats = tb.open_file(fileName, 'r') bins = np.array(dats.root.HIST.pmt_dark_bins) specsD = np.array(dats.root.HIST.pmt_dark).sum(axis=0) specsL = np.array(dats.root.HIST.pmt_spe).sum(axis=0) ## bins = np.array(dats.root.HIST.pmtdar_bins) ## specsD = np.array(dats.root.HIST.pmtdar).sum(axis=0) ## specsL = np.array(dats.root.HIST.pmtspe).sum(axis=0) #respF = fitf.SensorSpeResponse(bins) #ffuncs = {'ngau':respF.set_gaussians, 'intgau':respF.min_integ_gaussians, 'dfunc': respF.scaled_dark_pedestal, 'conv':respF.dark_convolution} #pOrders = {'ngau':'norm err ped err gain err poismu err pedSig err 1peSig err', 'intgau':'norm err ped err gain err poismu err pedSig err 1peSig err', 'dfunc':'norm err gain err poismu err 1peSig err', 'conv':'norm err gain err poismu err 1peSig err'} ffuncs = { 'ngau': speR.poisson_scaled_gaussians(n_gaussians=7), 'intgau': speR.poisson_scaled_gaussians(min_integral=100), 'dfunc': partial(speR.scaled_dark_pedestal, min_integral=100), 'conv': partial(speR.dark_convolution, min_integral=100) } pOrders = { 'ngau': 'norm err poismu err ped err pedSig err gain err 1peSig err', 'intgau': 'norm err poismu err ped err pedSig err gain err 1peSig err', 'dfunc': 'norm err poismu err gain err 1peSig err', 'conv': 'norm err poismu err gain err 1peSig err' } ## Not ideal... fnam = { 'ngau': 'poisson_scaled_gaussians_ngau', 'intgau': 'poisson_scaled_gaussians_min', 'dfunc': 'scaled_dark_pedestal', 'conv': 'dark_convolution' } ## pOut = open('pmtCalParOut_R'+fileName[-7:-3]+'_F'+funcName+'.dat', 'w') posRunNo = fileName.find('R') pOut = tb.open_file( 'pmtCalParOut_R' + fileName[posRunNo + 1:posRunNo + 5] + '_F' + funcName + '.h5', 'w') ## pOut.write('InputFile: '+fileName+'\n') ## pOut.write('FuncName: '+funcName+'\n') ## pOut.write('Minimum stats: '+str(min_stat)+'\n') ## pOut.write('Pedestal nsig limits: +/-'+str(limit_ped)+'\n') ## pOut.write('\n \n') ## pOut.write('Parameter order: '+pOrders[funcName]+'\n') param_writer = pIO.channel_param_writer(pOut, sensor_type='pmt', func_name=fnam[funcName], param_names=pIO.generic_params) test_names = ['normalization', 'Pedestal', 'Pedestal_sig'] pTest_writer = pIO.channel_param_writer(pOut, sensor_type='pmt', func_name='Pedestal_gaussian', param_names=test_names, covariance=(3, 3)) outDict = {} testDict = {} for i, (dspec, lspec) in enumerate(zip(specsD, specsL)): b1 = 0 b2 = len(dspec) if min_stat != 0: valid_bins = np.argwhere(lspec >= min_stat) b1 = valid_bins[0][0] b2 = valid_bins[-1][0] outDict[pIO.generic_params[-2]] = (bins[b1], bins[min(len(bins) - 1, b2)]) ## Fit the dark spectrum with a Gaussian (not really necessary for the conv option) gb0 = [(0, -100, 0), (1e99, 100, 10000)] av, rms = weighted_av_std(bins[dspec > 100], dspec[dspec > 100]) sd0 = (dspec.sum(), av, rms) errs = np.sqrt(dspec[dspec > 100]) errs[errs == 0] = 0.0001 gfitRes = fitf.fit(fitf.gauss, bins[dspec > 100], dspec[dspec > 100], sd0, sigma=errs, bounds=gb0) outDict[pIO.generic_params[2]] = (gfitRes.values[1], gfitRes.errors[1]) outDict[pIO.generic_params[3]] = (gfitRes.values[2], gfitRes.errors[2]) testDict[test_names[0]] = (gfitRes.values[0], gfitRes.errors[0]) testDict[test_names[1]] = (gfitRes.values[1], gfitRes.errors[1]) testDict[test_names[2]] = (gfitRes.values[2], gfitRes.errors[2]) testDict["covariance"] = gfitRes.cov pTest_writer(i, testDict) ## Scale just in case we lost a different amount of integrals in dark and led scale = lspec.sum() / dspec.sum() #print('Scale check: ', scale) #respF.set_dark_func(dspec[b1:b2], cent=gfitRes.values[1], sig=gfitRes.values[2], scale=scale) #respF.redefine_bins(bins[b1:b2]) if 'dfunc' in funcName: respF = ffuncs[funcName](dark_spectrum=dspec[b1:b2] * scale, pedestal_mean=gfitRes.values[1], pedestal_sigma=gfitRes.values[2]) elif 'conv' in funcName: respF = ffuncs[funcName](dark_spectrum=dspec[b1:b2] * scale, bins=bins[b1:b2]) elif fix_ped: respF = partial(ffuncs[funcName], pedestal_mean=gfitRes.values[1], pedestal_sigma=gfitRes.values[2]) else: respF = ffuncs[funcName] ## Take into account the scale in seed finding (could affect Poisson mu)???? ped_vals = np.array( [gfitRes.values[0] * scale, gfitRes.values[1], gfitRes.values[2]]) binR = bins[b1:b2] global darr darr = dspec[b1:b2] * scale darr = darr[binR < 0] seeds, bounds = seeds_and_bounds(i, funcName, bins[b1:b2], lspec[b1:b2], ped_vals, gfitRes.errors, limit_ped) print(seeds) #print(bounds) ## The fit errs = np.sqrt(lspec[b1:b2]) if not 'gau' in funcName: errs = np.sqrt(errs**2 + np.exp(-2 * seeds[1]) * dspec[b1:b2]) errs[errs == 0] = 1 #0.001 ## rfit = fitf.fit(ffuncs[funcName], bins[b1:b2], lspec[b1:b2], seeds, sigma=errs, bounds=bounds) rfit = fitf.fit(respF, bins[b1:b2], lspec[b1:b2], seeds, sigma=errs, bounds=bounds) ## plot the result plt.errorbar(bins, lspec, xerr=0.5 * np.diff(bins)[0], yerr=np.sqrt(lspec), fmt='b.') ## plt.plot(bins[b1:b2], rfit.fn(bins[b1:b2]), 'r') ## plt.plot(bins[b1:b2], ffuncs[funcName](bins[b1:b2], *seeds), 'g') plt.plot(bins[b1:b2], rfit.fn(bins[b1:b2]), 'r') plt.plot(bins[b1:b2], respF(bins[b1:b2], *seeds), 'g') plt.title('Spe response fit to channel ' + str(i)) plt.xlabel('ADC') plt.ylabel('AU') #print('Sensor index: ', i) #print('Fit values: ', rfit.values) #print('Fit errors: ', rfit.errors) ## print('Number of Gaussians: ', respF.nGau) #print('Number of Gaussians: ', respF.n_gaussians) #print('Fit chi2: ', rfit.chi2) ## pOut.write('Indx: '+str(i)+', params: '+str(np.vstack((rfit.values, rfit.errors)).reshape((-1,), order='F'))+', ngaus = '+str(respF.nGau)+', chi2 = '+str(rfit.chi2)+'\n') ##pOut.write('Indx: '+str(i)+', params: '+str(np.vstack((rfit.values, rfit.errors)).reshape((-1,), order='F'))+', ngaus = '+str(respF.n_gaussians)+', chi2 = '+str(rfit.chi2)+'\n') outDict[pIO.generic_params[0]] = (rfit.values[0], rfit.errors[0]) outDict[pIO.generic_params[1]] = (rfit.values[1], rfit.errors[1]) gIndx = 2 if 'gau' in funcName: gIndx = 4 outDict[pIO.generic_params[4]] = (rfit.values[gIndx], rfit.errors[gIndx]) outDict[pIO.generic_params[5]] = (rfit.values[gIndx + 1], rfit.errors[gIndx + 1]) outDict[pIO.generic_params[-1]] = (respF.n_gaussians, rfit.chi2) param_writer(i, outDict) next_plot = input('press enter to move to next fit') if 's' in next_plot: plt.savefig('FitPMTCh' + str(i) + '.png') plt.clf() plt.close() pOut.close()
def seeds_and_bounds(indx, func, bins, spec, ped_vals, ped_errs, lim_ped): norm_seed = spec.sum() ped_seed = ped_vals[1] ped_min = ped_seed - lim_ped * ped_errs[1] ped_max = ped_seed + lim_ped * ped_errs[1] #print('ped check: ', ped_seed, ped_min, ped_max) ped_sig_seed = ped_vals[2] ped_sig_min = max(0.001, ped_sig_seed - lim_ped * ped_errs[2]) ped_sig_max = ped_sig_seed + lim_ped * ped_errs[2] #print('rms check: ', ped_sig_seed, ped_sig_min, ped_sig_max) ## Remove the ped prediction and check try to get seeds for 1pe # first scale the dark pedestal dscale = spec[bins < 0].sum() / fitf.gauss(bins[bins < 0], *ped_vals).sum() GSeed = 0 GSSeed = 0 if not useSavedSeeds: # Case of not having seeds l_subtract_d = spec - fitf.gauss(bins, *ped_vals) * dscale pDL = find_peaks_cwt(l_subtract_d, np.arange(10, 20), min_snr=1, noise_perc=5) print('pDL', pDL) p1pe = pDL[(bins[pDL] > 15) & (bins[pDL] < 50)] p1pe = p1pe[spec[p1pe].argmax()] # The maximum is taken ## Now fit a Gaussian fgaus = fitf.fit( fitf.gauss, bins[p1pe - 10:p1pe + 10], l_subtract_d[p1pe - 10:p1pe + 10], (l_subtract_d[p1pe - 10:p1pe + 10].max(), bins[p1pe], 7), sigma=np.sqrt(l_subtract_d[p1pe - 10:p1pe + 10])) #print('1pe fit check: ', fgaus.values, fgaus.errors) GSeed = fgaus.values[1] - ped_vals[1] GSSeed = np.sqrt(fgaus.values[2]**2 - ped_vals[2]**2) else: GSeed = GainSeeds[indx] GSSeed = SigSeeds[indx] ## Test scale ftest = fitf.fit(scaler, bins[bins < 0], spec[bins < 0], (dscale)) print(ftest) print(dscale) #print('ftest par = ', ftest.values[0], -np.log(ftest.values[0])) if 'gau' in func: # There are 6 variables: normalization, pedestal pos., spe mean, poisson mean, pedestal sigma, 1pe sigma sd0 = (norm_seed, -np.log(ftest.values[0]), ped_seed, ped_sig_seed, GSeed, GSSeed) bd0 = [(0, 0, ped_min, ped_sig_min, 0, 0.001), (1e10, 10000, ped_max, ped_sig_max, 10000, 10000)] #print('Seed check: ', sd0) return sd0, bd0 ## The other functions only have four parameters: normalization, spe mean, poisson mean, 1pe sigma sd0 = (norm_seed, -np.log(ftest.values[0]), GSeed, GSSeed) bd0 = [(0, 0, 0, 0.001), (1e10, 10000, 10000, 10000)] #print('Seed check: ', sd0) return sd0, bd0