def plot_detector_unfolded(self, do_dijet=True, do_zpj=True): for ibin, (bin_edge_low, bin_edge_high) in enumerate( zip(self.bins[:-1], self.bins[1:])): hbc_args = dict(ind=ibin, axis='pt', do_norm=True, do_div_bin_width=True, binning_scheme='generator') print("----------pt bin", ibin, "=", bin_edge_low, "-", bin_edge_high) def determine_num_dp(values): """Determine correct number of decimal places so that the smallest value has 1 digit If smallest > 1, then just returns 0 """ smallest_value = min([v for v in values if v > 0]) print('determine_num_dp', smallest_value) if smallest_value > 1: return 0 # use log10 to figure out exponent, then floor it # e.g. log10(5.5E-3) = -2.25...., floor(-2.25...) = -3 n_dp = abs(floor(log10(smallest_value))) return n_dp def setup_beta_function(name): fit = ROOT.TF1(name, "[2]*TMath::BetaDist(x,[0],[1])", 0, 1) fit.SetParameter(0, 3) fit.SetParLimits(0, 0, 100) fit.SetParameter(1, 5) fit.SetParLimits(1, 0, 100) fit.SetParameter(2, 0.05) fit.SetParLimits(2, 0, 10) # fit.SetParameter(3, 0) # fit.SetParLimits(3, 0, 10) # fit.SetParameter(4, 1) # fit.SetParLimits(4, 0.1, 10) return fit fit_opts = "S" # Weird setup here: basically need all the values going into legend first, # to be able to determine the number of decimal points # Then we can actually create the Contributions and plot afterwards dijet_detector_hist, dijet_unfolded_hist = None, None dijet_detector_mean, dijet_detector_mean_err = None, None dijet_unfolded_mean, dijet_unfolded_mean_err = None, None zpj_detector_hist, zpj_unfolded_hist = None, None zpj_detector_mean, zpj_detector_mean_err = None, None zpj_unfolded_mean, zpj_unfolded_mean_err = None, None dijet_entries, zpj_entries = [], [] errors = [] if do_dijet: # get detector-level data dijet_detector_hist = self.dijet_hbc.get_bin_plot( 'input_hist_gen_binning_bg_subtracted', **hbc_args) dijet_detector_mean, dijet_detector_mean_err = self.get_uncorrelated_mean_err( dijet_detector_hist, is_density=True) errors.append(dijet_detector_mean_err) # fit with beta function # dijet_detector_fit = setup_beta_function("beta_fit_dijet_detector") # fit_result = dijet_detector_hist.Fit(dijet_detector_fit, fit_opts, "") # fit_result.Print() # get unfolded data dijet_unfolded_hist = self.dijet_hbc.get_bin_plot( "unfolded", **hbc_args) dijet_cov_matrix = self.dijet_hbc.get_bin_plot( self.dijet_region['unfolder'].total_ematrix_name, **hbc_args) dijet_unfolded_mean, dijet_unfolded_mean_err = self.get_correlated_mean_err( dijet_unfolded_hist, dijet_cov_matrix, is_density=True) errors.append(dijet_unfolded_mean_err) # dijet_unfolded_fit = setup_beta_function("beta_fit_dijet_unfolded") # fit_result = dijet_unfolded_hist.Fit(dijet_unfolded_fit, fit_opts, "") # fit_result.Print() if do_zpj: # get detector-level data zpj_detector_hist = self.zpj_hbc.get_bin_plot( 'input_hist_gen_binning_bg_subtracted', **hbc_args) zpj_detector_mean, zpj_detector_mean_err = self.get_uncorrelated_mean_err( zpj_detector_hist, is_density=True) errors.append(zpj_detector_mean_err) # zpj_detector_fit = setup_beta_function("beta_fit_zpj_detector") # fit_result = zpj_detector_hist.Fit(zpj_detector_fit, fit_opts, "") # fit_result.Print() # get unfolded data zpj_unfolded_hist = self.zpj_hbc.get_bin_plot( "unfolded", **hbc_args) zpj_cov_matrix = self.zpj_hbc.get_bin_plot( self.zpj_region['unfolder'].total_ematrix_name, **hbc_args) zpj_unfolded_mean, zpj_unfolded_mean_err = self.get_correlated_mean_err( zpj_unfolded_hist, zpj_cov_matrix, is_density=True) errors.append(zpj_unfolded_mean_err) # zpj_unfolded_fit = setup_beta_function("beta_fit_zpj_unfolded") # fit_result = zpj_unfolded_hist.Fit(zpj_unfolded_fit, fit_opts, "") # fit_result.Print() # n_dp = determine_num_dp(errors) n_dp = 3 # kerning necessary as it puts massive space around #pm # but the first #kern doesn't affect the space after #pm as much (?!), # so I have to add another one with harder kerning # we use %.(n_dp)f as our float format str to ensure the correct number of dp are shown (and not rounded off) stat_template = 'Mean = {:.%df}#kern[-0.2dx]{{ #pm}}#kern[-0.5dx]{{ }}{}' % ( n_dp) err_template = "{:.%df}" % n_dp def _stat_label(mean, err, dp): err_str = err_template.format(err) # if the error is so small that it would display as 0.000, # i.e. < 0.0005, instead show < 0.001 if err < 5 * 10**(-dp - 1): err_str = "#lower[-0.09dy]{<}#kern[-0.75dx]{ }" + err_template.format( 1 * 10**(-dp)) return stat_template.format(round(mean, dp), err_str) if do_dijet: dijet_entries.append( Contribution( dijet_detector_hist, label='Detector-level (stat. only)\n%s' % (_stat_label(dijet_detector_mean, dijet_detector_mean_err, n_dp)), line_color=self.plot_colours['dijet_colour'], line_width=self.line_width, line_style=self.line_style_detector, marker_color=self.plot_colours['dijet_colour'], marker_style=cu.Marker.get('circle', filled=False), marker_size=0.75, subplot=zpj_detector_hist if do_zpj else None)) dijet_entries.append( Contribution( dijet_unfolded_hist, label='Particle-level\n%s' % (_stat_label(dijet_unfolded_mean, dijet_unfolded_mean_err, n_dp)), line_color=self.plot_colours['dijet_colour'], line_width=self.line_width, line_style=1, marker_color=self.plot_colours['dijet_colour'], marker_style=cu.Marker.get('circle', filled=True), marker_size=0.75, subplot=zpj_unfolded_hist if do_zpj else None)) if do_zpj: zpj_entries.append( Contribution( zpj_detector_hist, label='Detector-level (stat. only)\n%s' % (_stat_label( zpj_detector_mean, zpj_detector_mean_err, n_dp)), line_color=self.plot_colours['zpj_colour'], line_width=self.line_width, line_style=self.line_style_detector, marker_color=self.plot_colours['zpj_colour'], marker_style=cu.Marker.get('square', filled=False), marker_size=0.75)) zpj_entries.append( Contribution( zpj_unfolded_hist, label='Particle-level\n%s' % (_stat_label( zpj_unfolded_mean, zpj_unfolded_mean_err, n_dp)), line_color=self.plot_colours['zpj_colour'], line_width=self.line_width, line_style=1, marker_color=self.plot_colours['zpj_colour'], marker_style=cu.Marker.get('square', filled=True), marker_size=0.75)) all_entries = list(chain(dijet_entries, zpj_entries)) plot = Plot( all_entries, ytitle=self.pt_bin_normalised_differential_label, title=self.get_pt_bin_title(bin_edge_low, bin_edge_high), legend=True, xlim=qgp.calc_auto_xlim( all_entries), # set x lim to where data is non-0 what="hist", xtitle=self.particle_title, has_data=self.has_data, ylim=[0, None], is_preliminary=self.is_preliminary) # plot.default_canvas_size = (600, 600) # plot.left_margin = 0.2 # plot.left_title_offset_fudge_factor = 8 plot.y_padding_max_linear = 1.8 plot.top_margin = 0.07 plot.title_start_y = 0.888 plot.cms_text_y = 0.94 plot.lumi = cu.get_lumi_str(do_dijet=do_dijet, do_zpj=do_zpj) if do_zpj and do_dijet: plot.subplot_type = 'ratio' plot.subplot_title = 'Dijet / Z+jet' plot.subplot_limits = (0.25, 2.75) # disable adding objects to legend & drawing - we'll do it manually # since we want proper error bar plot.do_legend = False plot.splitline_legend = False # plot.legend.SetFillColor(ROOT.kRed) # plot.legend.SetFillStyle(1001) plot.plot("NOSTACK E1 X0") # plot.get_modifier().GetYaxis().SetTitleOffset(plot.get_modifier().GetYaxis().GetTitleOffset()*1.5) plot.main_pad.cd() for e in [plot.contributions[0].obj, plot.contributions[2].obj]: e.Draw("HIST SAME") for e in [plot.contributions[1].obj, plot.contributions[3].obj]: e.Draw("L SAME") plot.canvas.cd() # unfolded_fit = ROOT.TF1("beta_fit_dijet_unfolded", "[2]*TMath::BetaDist(x,[0],[1])", 0, 1) # unfolded_fit.SetParameter(0, 3) # unfolded_fit.SetParLimits(0, 0, 100) # unfolded_fit.SetParameter(1, 5) # unfolded_fit.SetParLimits(1, 0, 100) # unfolded_fit.SetParameter(2, .1) # unfolded_fit.SetParLimits(2, 0, 1000) # # fit_result = unfolded_hist.Fit(unfolded_fit, "EMSR", "", 0, 1) # # fit_result.Print() # unfolded_fit.SetLineColor(ROOT.kRed) # unfolded_fit.SetLineWidth(2) # plot.main_pad.cd() # unfolded_fit.Draw("SAME") # Create dummy graphs with the same styling to put into the legend dummy_gr = ROOT.TGraphErrors(1, array('d', [1]), array('d', [1]), array('d', [1]), array('d', [1])) dummies = [] # to stop garbage collection dummy_entries = [] # to stop garbage collection label_height = 0.03 legend_height = 0.12 legend_x1 = 0.54 legend_x2 = 0.75 # this doesn't really control width - legend_text_size mainly does label_left_offset = 0.01 label_text_size = 0.032 label_top = plot.title_start_y legend_text_size = 0.028 inter_region_offset = 0.025 if do_dijet: dijet_legend = plot.legend.Clone() dijet_legend.SetX1(legend_x1) dijet_legend.SetX2(legend_x2) dijet_legend.SetY1(label_top - label_height - legend_height) dijet_legend.SetY2(label_top - label_height + 0.01) dijet_legend.SetTextSize(legend_text_size) # Add text with region label dijet_pt = ROOT.TPaveText(legend_x1 - label_left_offset, label_top - label_height, legend_x2 - label_left_offset, label_top, "NDC NB") dijet_pt.SetFillStyle(0) dijet_pt.SetBorderSize(0) text = dijet_pt.AddText(qgc.Dijet_CEN_LABEL) text.SetTextAlign(11) text.SetTextFont(62) text.SetTextSize(label_text_size) dummies.append(dijet_pt) dummies.append(text) dijet_pt.Draw() for cont in dijet_entries: this_dummy_entry = dummy_gr.Clone() cont.update_obj_styling(this_dummy_entry) if "\n" in cont.label: parts = cont.label.split("\n") dijet_legend.AddEntry( this_dummy_entry, "#splitline{%s}{%s}" % (parts[0], parts[1]), "LEP") else: dijet_legend.AddEntry(this_dummy_entry, cont.label, "LEP") dummy_entries.append( this_dummy_entry) # to avoid garbage collection dijet_legend.Draw() dummies.append(dijet_legend) # setup for Z+J label_top -= label_height + legend_height + inter_region_offset if do_zpj: zpj_legend = plot.legend.Clone() zpj_legend.SetX1(legend_x1) zpj_legend.SetX2(legend_x2) zpj_legend.SetY1(label_top - label_height - legend_height) zpj_legend.SetY2(label_top - label_height + 0.01) zpj_legend.SetTextSize(legend_text_size) # Add text with region label zpj_pt = ROOT.TPaveText(legend_x1 - label_left_offset, label_top - label_height, legend_x2 - label_left_offset, label_top, "NDC NB") zpj_pt.SetFillStyle(0) zpj_pt.SetBorderSize(0) text = zpj_pt.AddText(qgc.ZpJ_LABEL) text.SetTextAlign(11) text.SetTextFont(62) text.SetTextSize(label_text_size) dummies.append(zpj_pt) dummies.append(text) zpj_pt.Draw() for cont in zpj_entries: this_dummy_entry = dummy_gr.Clone() cont.update_obj_styling(this_dummy_entry) if "\n" in cont.label: parts = cont.label.split("\n") zpj_legend.AddEntry( this_dummy_entry, "#splitline{%s}{%s}" % (parts[0], parts[1]), "LEP") else: zpj_legend.AddEntry(this_dummy_entry, cont.label, "LEP") dummy_entries.append(this_dummy_entry) zpj_legend.Draw() dummies.append(zpj_legend) # Add legend to ratio plot plot.subplot_pad.cd() for e in [plot.subplot_contributions[0]]: e.Draw("HIST SAME") for e in [plot.subplot_contributions[1]]: e.Draw("L SAME") plot.subplot_leg = ROOT.TLegend(0.3, 0.73, 0.9, 0.9) plot.subplot_leg.SetTextSize(0.07) plot.subplot_leg.SetFillStyle(0) plot.subplot_leg.SetNColumns(2) plot.subplot_leg.AddEntry(dummy_entries[0], "Detector-level", "LEP") plot.subplot_leg.AddEntry(dummy_entries[1], "Particle-level", "LEP") plot.subplot_leg.Draw() plot.canvas.cd() parts = [ 'detector_unfolded', 'dijet' if do_dijet else None, 'zpj' if do_zpj else None, self.append, 'bin_%d' % ibin, 'divBinWidth', f'{self.paper_str}.{self.output_fmt}' ] filename = '_'.join([x for x in parts if x]) plot.save("%s/%s" % (self.output_dir, filename)) print("%s/%s" % (self.output_dir, filename))