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 fit_slices_1d_gauss(xdata, ydata, xbins, ybins, min_entries=1e2, ignore_errors=_FIT_EXCEPTIONS): """ Slice the data in x, histogram each slice, fit it to a gaussian and return the relevant values. Parameters ---------- xdata, ydata: array_likes Values of each coordinate. xbins: array_like The bins in the x coordinate. ybins: array_like The bins in the y coordinate for histograming the data. min_entries: int (optional) Minimum amount of entries to perform the fit. Returns ------- mean: Measurement(np.ndarray, np.ndarray) Values of mean with errors. sigma: Measurement(np.ndarray, np.ndarray) Values of sigma with errors. chi2: np.ndarray Chi2 from each fit. valid: boolean np.ndarray Where the fit has been succesfull. """ nbins = np.size(xbins) - 1 mean = np.zeros(nbins) sigma = np.zeros(nbins) meanu = np.zeros(nbins) sigmau = np.zeros(nbins) chi2 = np.zeros(nbins) valid = np.zeros(nbins, dtype=bool) for i in range(nbins): sel = in_range(xdata, *xbins[i:i + 2]) if np.count_nonzero(sel) < min_entries: continue try: f = quick_gauss_fit(ydata[sel], ybins) mean[i] = f.values[1] meanu[i] = f.errors[1] sigma[i] = f.values[2] sigmau[i] = f.errors[2] chi2[i] = f.chi2 valid[i] = True except Exception as exc: if not isinstance(exc, ignore_errors): raise return Measurement(mean, meanu), Measurement(sigma, sigmau), chi2, valid
def lt_vs_t(z, v, t, znbins, zrange, vnbins, vrange, tnbins, trange): """ returns the profile-fit to the lifetime in slices of time """ tbins = np.linspace(*trange, tnbins + 1) fs = [] for i in range(tnbins): sel = in_range(t, tbins[i], tbins[i + 1]) fi = lt(z[sel], v[sel], znbins, zrange, vnbins, vrange, plot=False) fs.append(fi) return fs
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 drift_velocity(plots_dir, dst, label=''): """ Input Z distribution Return value for the drift velocity """ # To do: make plotting of drift velocities as for energy resolution Z = dst[in_range(dst.Z, 305, 350)].Z fit_z_range = (305, 350) fig = plt.figure(figsize=(12, 4)) ax = fig.add_subplot(1, 2, 1) (_) = hist(dst.DT, bins=100, range=(0, 400), histtype='stepfilled', color='crimson') labels('Drit time ($\mu$s)', 'Entries') plt.legend(loc='upper right') ax = fig.add_subplot(1, 2, 2) (_) = hist(Z, bins=100, range=fit_z_range, histtype='stepfilled', color='crimson') labels('Drit time ($\mu$s)', 'Entries') plt.legend(loc='upper right') plt.show() sigmoid = lambda x, A, B, C, D: A / (1 + np.exp((x - B) / C)) + D mypdf = Extended(sigmoid) #describe(mypdf) chi2 = BinnedChi2(mypdf, Z, bins=100, bound=fit_z_range) #create cost function plt.figure(figsize=(7, 5)) chi2.show(args={ 'A': 1400, 'B': 330, 'C': 1.1, 'D': 43, 'N': 1 }) #another way to draw it m = Minuit(chi2, A=800, B=330, C=1.3, D=100, N=1) m.migrad() my_parmloc = (0.60, 0.90) #chi2.draw(m, parmloc=my_parmloc) plt.figure(figsize=(7, 5)) chi2.show(m, parmloc=my_parmloc) plt.savefig(f'{plots_dir}/fit_energy_reso_{label}.png') print('plots saved in ' + plots_dir)
def fbi_conf(fbisp, xnum=0, xmol='FBI'): if xmol == 'FBI': lr = fbisp.lrfbi else: lr = fbisp.lrfbi_ba df_1 = fbisp.fbidf[in_range(fbisp.fbidf.II, *lr[xnum])] df1 = pd_find_location(df_1, xmol, df_1[xmol].max()) fabm = fbisp.abs_maxima assert df1["II"] == fabm[f'max{xnum+1}_{xmol.lower()}'].loc[0] assert df1["FBI_Ba"] == fabm[f'max{xnum+1}_{xmol.lower()}'].loc[1] assert df1["FBI"] == fabm[f'max{xnum+1}_{xmol.lower()}'].loc[2]
def select_in_TRange(kre: KrEvent, tmin: float, tmax: float) -> KrEvent: """ Selects a KrEvent in a range of T values""" sel = in_range(kre.T, tmin, tmax) return KrEvent(X=kre.X[sel], Y=kre.Y[sel], Z=kre.Z[sel], E=kre.E[sel], S1=kre.S1[sel], T=kre.T[sel], Q=kre.Q[sel])
def get_sensor_response(sns_response : pd.DataFrame, detector : Detector, sns_type : str ) -> pd.DataFrame: """ Returns a data frame with the charge and time of each sensor of the provided sensor type. """ assert sns_type in detector.get_sensor_types(), f"{sns_type} not present in {detector.name}" sns_range = detector.get_sensor_ids(sns_type) return sns_response[in_range(sns_response.index.get_level_values("sensor_id"), sns_range[0], sns_range[1])].sort_index()
def s1s2_selection(dst, fout, dst_out_dir, run, rmax, save=False): """ Input one file with a dst dataframe and run number Returns dst and writes to disk a reduced dst with events that pass the 1s1 and 1s2 selection criteria """ dst_s1 = dst [in_range(dst.nS1, 1,2)] dst_s2 = dst_s1 [in_range(dst_s1.nS2, 1,2)] tot_ev = dst. event.nunique() s1_ev = dst_s1.event.nunique() s1s2_ev = dst_s2.event.nunique() eff_s1 = s1_ev / tot_ev eff_s2 = s1s2_ev / tot_ev eff_s1s2 = s1s2_ev / s1_ev fout.write(f'ev_1s1 {s1_ev }\n') fout.write(f'ev_1s1s2 {s1s2_ev }\n') fout.write(f'eff_1s1_check {eff_s1*100:.5f}\n') fout.write(f'eff_1s1_u_check {error_eff(tot_ev, s1_ev/tot_ev)*100:.5f}\n') fout.write(f'eff_1s1s2 {eff_s2*100:.5f}\n') fout.write(f'eff_1s1s2_u {error_eff(tot_ev, s1s2_ev/tot_ev)*100:.5f}\n') fout.write(f'rel_eff_1s2_from_1s1 {eff_s1s2*100:.5f}\n') fout.write(f'rel_eff_1s2_from_1s1_u {error_eff(s1_ev, s1s2_ev/s1_ev)*100:.5f}\n') if save: dir_file_name = f'{dst_out_dir}/reduced_{run}_kdst_{rmax}.h5' save_dst_to_file(dst_s2, dir_file_name) print(f'Save reduced kdst with 1s1 and 1s2 in: {dir_file_name}') return dst_s2
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 ltmap_vs_t_lsqfit(X, Y, Z, E, T, XYbins, Tbins, min_entries=20): """ returns the LT-maps and Geo-maps in time T intervals using the unbinned fit """ fs = [] for i in range(len(Tbins) - 1): sel = in_range(T, Tbins[i], Tbins[i + 1]) fi = ltmap_lsqfit(X[sel], Y[sel], Z[sel], E[sel], XYbins, min_entries=min_entries) fs.append(fi) return fs
def plt_var_xymap_wslice(V, X, Y, W, Vnbins, Vrange, XYnbins, XYrange, Wnbins, Wrange, label = ''): """ plot the XY map of a variable V in slices of second W variable. Vrange fix the range of the xymap """ vmin, vmax = Vrange c = hst.Canvas(Vnbins/2, Vnbins/2) zzs = np.linspace(Wrange[0], Wrange[1], Wnbins+1) for ii in range(len(zzs)-1): sel = in_range(W, zzs[ii], zzs[ii+1]) x, y, z, uz = fitf.profileXY(X[sel], Y[sel], V[sel], XYnbins, XYnbins, xrange=XYrange, yrange=XYrange) hst.display_matrix(x, y, z, cmap = default_cmap, canvas=c(ii+1), vmin = vmin, vmax=vmax, xylabels=('x (mm)', 'y (mm)', label+ ' in ['+str(zzs[ii])+', '+str(zzs[ii+1])+']') ); plt.tight_layout() return c
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 fbi_abs_spectrum(self): """Compute the absorption spectrum and related variables""" self.fbidf = \ pd.read_excel(f"{os.environ['SABATDATA']}/EDI_029_absorption.xlsx") # normalise to the value of the cross section in l_peak if self.norm: s = pd_find_location(self.fbidf, "II", self.l_peak) sfbi_ba_au = s['FBI_Ba'] sfbi_au = s['FBI'] self.sfbi_ba_nm = self.s_fbi_ba / sfbi_ba_au self.sfbi_nm = self.s_fbi / sfbi_au else: self.sfbi_ba_nm = 1 self.sfbi_nm = 1 # Define Sector data frames according to lrfbi and lrfbi_ba # These are portions of the spectrum that contained a single maximum fbiS = [ self.fbidf[in_range(self.fbidf.II, *self.lrfbi[i])] for i in range(4) ] fbibaS = [ self.fbidf[in_range(self.fbidf.II, *self.lrfbi_ba[i])] for i in range(4) ] # Store a DF with W, FBI, FBI_Ba data for each sector self.max_fbi = [ pd_find_location(fbi, 'FBI', fbi['FBI'].max()) for fbi in fbiS ] self.max_fbi_ba = [ pd_find_location(fbi, 'FBI_Ba', fbi['FBI_Ba'].max()) for fbi in fbibaS ]
def quality_cut(dst: pd.DataFrame, r_max: float) -> pd.DataFrame: """ Does basic quality cut : R inside the r_max Parameters ---------- dst : pd.DataFrame Input dst to obtain the mask from. r_max: float Upper limit for R. Returns ---------- mask : pd.DataFrame Mask for filtering events not matching the criteria """ mask = in_range(dst.R, 0, r_max) return mask
def get_time_series(time_bins: Number, time_range: Tuple[float, float], kre: KrEvent) -> Tuple[np.array, List[np.array]]: """ Returns a time series (ts) and a list of masks which are used to divide the event in time tranches. Parameters ---------- time_bins Number of time bines. time_range Time range. kre A kr_event (a subset of dst). Returns ------- A Tuple with: np.array : This is the ts vector List[np.array] : This are the list of masks defining the events in the time series. """ logging.debug(f'function: get_time_series') nt = time_bins x = int((time_range[-1] - time_range[0]) / nt) tfirst = int(time_range[0]) tlast = int(time_range[-1]) if x == 1: indx = [(tfirst, tlast)] else: indx = [(i, i + x) for i in range(tfirst, int(tlast - x), x)] indx.append((x * (nt - 1), tlast)) ts = [(indx[i][0] + indx[i][1]) / 2 for i in range(len(indx))] logging.debug( f' number of time bins = {nt}, t_first = {tfirst} t_last = {tlast}') logging.debug(f'indx = {indx}') logging.debug(f'ts = {ts}') masks = [ in_range(kre.DT, indx[i][0], indx[i][1]) for i in range(len(indx)) ] return np.array(ts), masks
def xymap_vprofile_zslice(V, X, Y, Z, Zbins, XYnbins, XYrange, std=True): vs, vus, voks = [], [], [] for ii in range(len(Zbins) - 1): sel_ii = in_range(Z, Zbins[ii], Zbins[ii + 1]) x, y, z, uz = fitf.profileXY(X[sel_ii], Y[sel_ii], V[sel_ii], XYnbins, XYnbins, xrange=XYrange, yrange=XYrange, std=std) ok = z > 0 vs.append(z) vus.append(uz) voks.append(ok) return vs, vus, voks
def plt_entries_xymap_wslice(X, Y, W, XYnbins, XYrange, Wnbins, Wrange, Crange, vname = ''): """ plot the number of entries in a XY map in slices of W variable, Crange fix the range of events of the xymap """ wmin, wmax = Wrange c = hst.Canvas(Wnbins/2, Wnbins/2) zzs = np.linspace(wmin, wmax, Wnbins+1) for ii in range(len(zzs)-1): sel = in_range(W, zzs[ii], zzs[ii+1]) hst.hist2d(X[sel], Y[sel], (XYnbins, XYnbins), (XYrange, XYrange), cmap = cmap_default, canvas = c(ii+1), xylabels = ('x (mm)', 'y (mm)', 'evts '+vname+' in ['+str(zzs[ii])+', '+str(zzs[ii+1])+']') ); plt.colorbar().set_label("Number of events") plt.clim(*Crange) plt.tight_layout() return c
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 find_outliers(maps: ASectorMap, x2range: Tuple[float, float] = (0, 2)): """ For a given maps and deserved range, it returns a mask where values are within the interval. Parameters --------- maps: ASectorMap Map to check the outliers x2range : Tuple[float, float] Range for chi2 Returns --------- mask: pd.DataFrame Mask. """ mask = in_range(maps.chi2, *x2range) return mask
def event_energy_cut(dst, emin, emax): """ input dst in which each entry correspond to a given sipm""" group = dst.groupby(['event']).sum() group_Ecut = group[in_range(group.E, emin, emax)] dstCoreE = dst[dst.event.isin(group_Ecut.index)] group_CoreE = dstCoreE.groupby(['event']).sum() # plot selected energy sns.set_style("white") sns.set_style("ticks") plot_event_energy(dstCoreE, group_CoreE, 50, [3000, 7000], 50, [100, 300], loc_E= 'upper left', loc_Q= 'upper right', figsize=(15,11)) # plot e/q for sipm sns.set_style("white") sns.set_style("ticks") plot_EQ(dst, 50, [0, 1000], 50, [0, 100], figsize=(15,11)) return dstCoreE
def ltmap_vs_t(X, Y, Z, E, T, XYbins, Znbins, Zrange, Tnbins, Trange): """ returns the LT XYmap and Geo XYMap in time T intervals """ xs, ys = 0.5 * (XYbins[:-1] + XYbins[1:]), 0.5 * (XYbins[:-1] + XYbins[1:]) # e0map = XYMap(xs, ys, Escale_rel.value, Escale_rel.uncertainty, Eok) # ltmap = XYMap(xs, ys, -ELT_rel.value , ELT_rel.uncertainty , Eok) # return e0map, ltmap tbins = np.linspace(*Trange, Tnbins + 1) e0maps, ltmaps = [], [] for i in range(Tnbins): sel = in_range(T, tbins[i], tbins[i + 1]) Escale, ELT, Echi2, Eok = ltmap(X[sel], Y[sel], Z[sel], E[sel], XYbins, Znbins, Zrange) e0map = XYMap(xs, ys, Escale.value, Escale.uncertainty, Eok) e0map.chi2 = Echi2 ltmap = XYMap(xs, ys, ELT.value, ELT.uncertainty, Eok) ltmap.chi2 = Echi2 e0maps.append(ie0map) ltmaps.append(iltmap) return e0maps, ltmaps
def get_time_series_df( time_bins: Number, time_range: Tuple[float, float], dst: DataFrame, time_column: str = 'time') -> Tuple[np.array, List[np.array]]: """ Given a dst (DataFrame) with a time column specified by the name time, this function returns a time series (ts) and a list of masks which are used to divide the event in time tranches. More generically, one can produce a "time series" using any column of the dst simply specifying time_column = ColumName Parameters ---------- time_bins Number of time bines. time_range Time range. dst A Data Frame time_column A string specifyng the dst column to be divided in time slices. Returns ------- A Tuple with: np.array : This is the ts vector List[np.array] : This are the list of masks defining the events in the time series. """ #Add small number to right edge to be included with in_range function modified_right_limit = np.nextafter(time_range[-1], np.inf) ip = np.linspace(time_range[0], modified_right_limit, time_bins + 1) masks = np.array([ in_range(dst[time_column].values, ip[i], ip[i + 1]) for i in range(len(ip) - 1) ]) return shift_to_bin_centers(ip), masks
def gaussian_parameters(x : np.array, range : Tuple[Number], bin_size : float = 1)->GaussPar: """ Return the parameters defining a Gaussian g = N * exp(x - mu)**2 / (2 * std**2) where N is the normalization: N = 1 / (sqrt(2 * np.pi) * std) The parameters returned are the mean (mu), standard deviation (std) and the amplitude (inverse of N). """ mu, std = mean_and_std(x, range) ff = np.sqrt(2 * np.pi) * std amp = len(x) * bin_size / ff sel = in_range(x, *range) N = len(x[sel]) # number of samples in range mu_u = std / np.sqrt(N) std_u = std / np.sqrt(2 * (N -1)) amp_u = np.sqrt(2 * np.pi) * std_u return GaussPar(mu = Measurement(mu, mu_u), std = Measurement(std, std_u), amp = Measurement(amp, amp_u))
def mean_and_std(x: np.array, range_: Tuple[Number, Number]) -> Tuple[Number, Number]: """Computes mean and std for an array within a range: takes into account nans""" mu = NN std = NN if all(np.isnan(x)): # all elements are nan mu = NN std = NN else: x_nonnan = x[np.isfinite(x)] y = x_nonnan[in_range(x_nonnan, *range_)] if len(y) == 0: warnings.warn( f'warning, empty slice of x = {x} in range = {range_}') mu = NN std = NN else: mu = np.mean(y) std = np.std(y) return mu, std
def event_energy_cut(dst, emin, emax, qmin=100, qmax=300, e_sipm_max=1000, q_sipm_max=100): """ input dst in which each entry correspond to a given sipm""" group = dst.groupby(['event']).sum() group_Ecut = group[in_range(group.E, emin, emax)] dstCoreE = dst[dst.event.isin(group_Ecut.index)] group_CoreE = dstCoreE.groupby(['event']).sum() # plot selected energy sns.set_style("white") sns.set_style("ticks") plot_event_energy(dstCoreE, group_CoreE, 50, [emin, emax], 50, [qmin, qmax], loc_E='upper left', loc_Q='upper right', figsize=(15, 11)) # plot e/q for sipm sns.set_style("white") sns.set_style("ticks") # 24 juliol he canviat dst --> dstcore, no se si hi havia rao. #plot_EQ(dst, 50, [0, 1000], 50, [0, 100], figsize=(15,11)) plot_EQ(dstCoreE, 50, [0, e_sipm_max], 50, [0, q_sipm_max], figsize=(15, 11)) return dstCoreE
def mean_and_std(x : np.array, range_ : Tuple[Number, Number])->Tuple[Number, Number]: """Computes mean and std for an array within a range: takes into account nans""" mu = NN std = NN if np.count_nonzero(np.isnan(x)) == len(x): # all elements are nan mu = NN std = NN elif np.count_nonzero(np.isnan(x)) > 0: mu = np.nanmean(x) std = np.nanstd(x) else: x = np.array(x) if len(x) > 0: y = x[in_range(x, *range_)] if len(y) == 0: print(f'warning, empty slice of x = {x} in range = {range_}') print(f'returning mean and std of x = {x}') y = x mu = np.mean(y) std = np.std(y) return mu, std
def fit_lifetime_unbined( z: np.array, e: np.array, nbins_z: int, range_z: Tuple[float, float]) -> Tuple[FitPar, FitPar, FitResult]: """ Based on numpy.polyfit(x, y, deg, rcond=None, full=False, w=None, cov=False) Fit a polynomial p(x) = p[0] * x**deg + ... + p[deg] of degree deg to points (x, y). Returns a vector of coefficients p that minimises the squared error. E = E0 exp(-z/lt) y = -log(E) = (z/lt) - log(E) 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 FirResults: Fit results (lt, e0, errors, chi2) """ logging.debug(' fit_liftime_unbined') logging.debug(f' len (z) ={len(z)}, len (e) ={len(e)} ') logging.debug(f' nbins_z ={nbins_z}, range_z ={range_z} ') fp = None fp2 = None valid = True c2 = NN par = NN * np.ones(2) err = NN * np.ones(2) try: el = -np.log(e) z_sel = in_range(z, *range_z) cc, cov = np.polyfit(z[z_sel], el[z_sel], deg=1, full=False, w=None, cov=True) a, b = cc[0], cc[1] lt = 1 / a par[1] = lt err[1] = lt**2 * np.sqrt(cov[0, 0]) e0 = np.exp(-b) par[0] = e0 err[0] = e0 * np.sqrt(cov[1, 1]) x, y, yu = profile1d(z, e, nbins_z, range_z) xs, ys, yus = profile1d(z, el, nbins_z, range_z) xu = np.diff(x) * 0.5 xus = np.diff(xs) * 0.5 logging.debug(f' after profile: len (x) ={len(x)}, len (y) ={len(y)} ') c2 = chi2f(lambda z: a * xs + b, 2, xs, ys, yus) 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=lambda z: e0 * np.exp(-z / lt)) fp2 = FitPar(x=xs, y=ys, xu=xus, yu=yus, f=lambda z: a * xs + b) except ValueError: logging.warn( f'Value Error found in fit_lifetime_unbined: not enough events for fit' ) valid = False except TypeError: logging.warn( f'Type error found in fit_lifetime_unbined: not enough events for fit' ) valid = False except LinAlgError: logging.warn( f'LinAlgError error found in fit_lifetime_unbined: not enough events for fit' ) valid = False fr = FitResult(par=par, err=err, chi2=c2, valid=valid) return fp, fp2, fr
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_slices_2d_expo(xdata, ydata, zdata, tdata, xbins, ybins, nbins_z, zrange=None, min_entries = 1e2, ignore_errors = _FIT_EXCEPTIONS): """ Slice the data in x and y, make the profile in z of t, fit it to a exponential and return the relevant values. Parameters ---------- xdata, ydata, zdata, tdata: array_likes Values of each coordinate. xbins, ybins: array_like The bins in the x coordinate. nbins_z: int The number of bins in the z coordinate for the profile. zrange: length-2 tuple (optional) Fix the range in z. Default is computed from min and max of the input data. min_entries: int (optional) Minimum amount of entries to perform the fit. Returns ------- const: Measurement(np.ndarray, np.ndarray) Values of const with errors. slope: Measurement(np.ndarray, np.ndarray) Values of slope with errors. chi2: np.ndarray Chi2 from each fit. valid: boolean np.ndarray Where the fit has been succesfull. """ nbins_x = np.size (xbins) - 1 nbins_y = np.size (ybins) - 1 nbins = nbins_x, nbins_y 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) if zrange is None: zrange = np.min(zdata), np.max(zdata) for i in range(nbins_x): sel_x = in_range(xdata, *xbins[i:i + 2]) for j in range(nbins_y): sel_y = in_range(ydata, *ybins[j:j + 2]) sel = sel_x & sel_y if np.count_nonzero(sel) < min_entries: continue try: f = fit_profile_1d_expo(zdata[sel], tdata[sel], nbins_z, xrange=zrange) 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 Exception as exc: if not isinstance(exc, ignore_errors): raise return Measurement(const, constu), Measurement(slope, slopeu), chi2, valid