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') -> 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 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 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 gaussian_fit(x: np.array, y: np.array, seed: GaussPar, n_sigma: int) -> Tuple[FitPar, FitResult]: """Gaussian fit to x,y variables, with fit range defined by n_sigma""" mu = seed.mu.value std = seed.std.value amp = seed.amp.value fit_range = mu - n_sigma * std, mu + n_sigma * std x, y = x[in_range(x, *fit_range)], y[in_range(x, *fit_range)] yu = poisson_sigma(y) fseed = (amp, mu, std) par, err = par_and_err_from_seed(seed) fr = FitResult(par=par, err=err, chi2=NN, valid=False) fp = None with warnings.catch_warnings(): warnings.filterwarnings( 'error') # in order to handle fit failures here try: fp, fr = gfit(x, y, yu, fseed) except RuntimeWarning: # this is the most usual failure, and usually solved trying fitx # with a different seed print( f' fit failed for seed = {seed}, due to RunTimeWarning, retry fit ' ) fseed = (10 * fseed[0], fseed[1], fseed[2]) try: fp, fr = gfit(x, y, yu, fseed) except RuntimeWarning: # Give up on second failure print( f' fit failed for seed = {seed}, due to RunTimeWarning, give up ' ) except OptimizeWarning: print( f' OptimizeWarning was raised for seed = {seed} due to OptimizeWarning' ) except RuntimeError: print(f' fit failed for seed = {seed} due to RunTimeError') except TypeError: print(f' fit failed for seed = {seed} due to TypeError') return fp, fr
def get_chi2_and_pvalue(ydata, yfit, ndf, sigma=None): """ Gets reduced chi2 and p-value Parameters ---------- ydata : np.ndarray Data points. yfit : np.ndarray Fit values corresponding to ydata array. sigma : np.ndarray Data errors. If sigma is not given, it takes the poisson case: sigma = sqrt(ydata) ndf : int Number of degrees of freedom (number of data points - number of parameters). Returns ------- chi2 : float Reduced chi2 computed as: chi2 = [sum(ydata - yfit)**2 / sigma**2] / ndf pvalue : float Fit p-value. """ if sigma is None: sigma = poisson_sigma(ydata) chi2 = np.sum(((ydata - yfit) / sigma)**2) pvalue = scipy.stats.chi2.sf(chi2, ndf) if ndf > 0: return chi2 / ndf, pvalue else: print(f'Warning in fit_functions_ic:chi2: ndf =0: chi2 = {chi2}') return chi2, pvalue
peaks_dark = find_peaks_cwt(dar, np.arange(2, 20), min_snr=2) if len(peaks_dark) == 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([channs[ich], [0, 0, 0, 0], [0, 0, 0, 0], 0, 0]) print('no peaks in dark spectrum, spec ', ich) continue else: peaks_dark = 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) sel = np.arange(peaks_dark[0] - 5, peaks_dark[0] + 5) errs = poisson_sigma(dar[sel], default=0.1) gauss_fit_dark = fitf.fit(fitf.gauss, bins[sel], dar[sel], sd0, sigma=errs, bounds=gb0) outDict[cpio.generic_params[2]] = (gauss_fit_dark.values[1], gauss_fit_dark.errors[1]) outDict[cpio.generic_params[3]] = (gauss_fit_dark.values[2], gauss_fit_dark.errors[2]) ## Scale just in case we lost a different amount of integrals in dark and led ## scale = led.sum() / dar.sum() scale = 1.5
def measureDriftVelocity(run, drift_field, z, zrange_dv, bins_dv, buffersize, z_cathode, el_time): """ Given a Z distribution, returns the drift velocity with its error. The drift velocity is obtained through an smooth Heavyside fit or logistic function (https://en.wikipedia.org/wiki/Logistic_function). Parameters: run = Run number, to add it to the figure title drift_field = Drift field of the run, to add it to the figure title. groupDST = Grouped DST (by event id). zrange_dv = Range in drift time for the fit. bins_dv = Bins for plotting the region of interest. buffersize = Buffersize of the run, to plot the drift time distribution. z_cathode = Cathode z-position in mm. """ fig, axes = plt.subplots(1, 3, figsize=(24, 6)) st = fig.suptitle( "Run {0} (drift field = {1:.0f} V/cm/bar) Z distribution".format( run, drift_field[0]), fontsize=16) axes[0].hist(z, 100, [0, buffersize], rasterized=True) axes[0].set_xlabel("Z (mm)") axes[0].set_ylabel("") axes[1].hist(z, 100, [0, buffersize], rasterized=True) axes[1].set_xlabel("Z (mm)") axes[1].set_ylabel("") axes[1].set_yscale('log') plt.axes(axes[2]) y, x = h1d(z, bins_dv, zrange_dv, "Z (mm)", "Number of events", ax=axes[2]) axes[2].set_yscale('log') sigmoid = lambda x, A, B, C, D: A / (1 + np.exp(-C * (x - B))) + D seed = np.max(y), np.mean(zrange_dv), np.diff(zrange_dv)[0] / 100, np.min( y) f = fitf.fit(sigmoid, x, y, seed, fit_range=zrange_dv, sigma=statf.poisson_sigma(y)) plt.plot(x, f.fn(x), "r", lw=1.25, rasterized=True) histFun.labels("Drift time ($\mu$s)", "Number of events", "") dv = z_cathode[0] / (f.values[1] - el_time[0]) dv_e_stat = z_cathode[0] / ( (f.values[1] - el_time[0])** 2) * f.errors[1] # Statistic error derived from fit. dv_e_syst = (z_cathode[0]/((f.values[1] - el_time[0])**2) * 1.)**2 + \ (z_cathode[1]/(f.values[1] - el_time[0]))**2 + \ (z_cathode[0]/((f.values[1] - el_time[0])**2) * el_time[1])**2 # Systematic error derived from drift time measurement (asumed to be 1 mus), drift length and drifted time in EL. dv_e_syst = np.sqrt(dv_e_syst) dv = np.array([dv, dv_e_stat, dv_e_syst]) print("Max drift time = {:.3f} +- {:.3f}".format(f.values[1], f.errors[1])) print( "Drift velocity = {:.5f} +- {:.5f} (stat.) +- {:.5f} (syst.)".format( dv[0], dv[1], dv[2])) print("======================================") axes[2].annotate("Drift time = \n{:.3f} $\pm$ {:.3f}".format( f.values[1], f.errors[1]), xy=(0.665, 0.9), xycoords='axes fraction', color="black") #, fontsize = 14) extent = axes[0].get_tightbbox(fig.canvas.get_renderer()).transformed( fig.dpi_scale_trans.inverted()) fig.savefig("DriftVel_R{}.pdf".format(run), bbox_inches=extent) extent = axes[1].get_tightbbox(fig.canvas.get_renderer()).transformed( fig.dpi_scale_trans.inverted()) fig.savefig("DriftVel_Log_R{}.pdf".format(run), bbox_inches=extent) extent = axes[2].get_tightbbox(fig.canvas.get_renderer()).transformed( fig.dpi_scale_trans.inverted()) fig.savefig("DriftVel_LogZoom_R{}.pdf".format(run), bbox_inches=extent) plt.close(fig) return dv