def _add_legend(self, config, factor): l = Legend(0.05, 3, 0.08) for cfg in config['backgrounds']: bkg, color = cfg.items()[0] l.draw_box(1001, self._eval(color), Process.get(bkg).fullname) l.draw_box(3654, r.kBlack, "Bkg. err.", True) # TODO add collisions l.new_row() for cfg in config['signals']: sig, color = cfg.items()[0] label = Process.get(sig).fullname if factor != 1: label += " (#times {0:.1f})".format(factor) l.draw_line(2, self._eval(color), label) return l
def _normalize(self, cutflows): if self.__normalized: return self.__normalized = True for fullname, hist in self.__hists.items(): if fullname.endswith('Up') or fullname.endswith('Down'): name, _ = fullname.rsplit('_CMS', 1) proc = Process.get(name) else: proc = Process.get(fullname) logging.debug("normalizing histogram {0}, process {1}".format(self.__name, proc)) denom = float(cutflows[proc.cutflow][-3][proc]) factor = 0. if denom == 0. else cutflows[proc.cutflow][-1][proc] / denom hist.Scale(factor)
def _get_histogram(self, process, systematic=None): if isinstance(process, Process): proc = process process = str(process) else: proc = Process.get(process) if isinstance(proc, BasicProcess): scale = 1. if systematic and systematic.startswith('Relative'): scale = max(0, 1 + proc.relativesys() * (1. if systematic.endswith('Up') else -1.)) systematic = None suffix = '_' + systematic if systematic else '' hist = self.__hists[process + suffix].Clone() if hist.ClassName().startswith('TH1'): lastbin = hist.GetNbinsX() overbin = lastbin + 1 err = math.sqrt(hist.GetBinError(lastbin) ** 2 + hist.GetBinError(overbin) ** 2) val = hist.GetBinContent(lastbin) + hist.GetBinContent(overbin) hist.SetBinContent(lastbin, val) hist.SetBinError(lastbin, err) if scale != 1.: hist.Scale(scale) return hist hist = None for p in proc.subprocesses: h = self._get_histogram(p, systematic) if hist: hist.Add(h, proc.factor) else: hist = h.Clone() if not hist: raise KeyError(process) return hist
def _add_legend(self, factor): legend = Legend(0.05, 4, 0.03) if len(self.__backgrounds_present) > 0: legend.draw_marker(20, r.kBlack, "Data") for cfg in self._plotconfig['backgrounds']: props = {'SetFillStyle': 1001} props.update(cfg) bkg = props.pop('process') if bkg not in self.__backgrounds_present: continue legend.draw_box({k: self._eval(v) for (k, v) in props.items()}, Process.get(bkg).fullname, centerline=False) if len(self.__signals_present) > 0: legend.new_row() for cfg in self._plotconfig['signals']: sig, color = cfg.items()[0] if sig not in self.__signals_present: continue label = Process.get(sig).fullname if factor != 1: label += " (#times {0:.1f})".format(factor) legend.draw_line(2, self._eval(color), label) legend.new_row() return legend
def write(self, file, cutflows, category, systematics=None, procs=None, fmt="{p}_{c}_{v}"): """Write histograms of the plot to `file`. Use the specified `category` and normalize histograms via the cutflows passed by `cutflows`. Can be limited to processes specified in `procs`. Optionally, use `systematics`, and write with the format specified in `fmt`, where `p` is the process name, `c` the category, and `v` the limit name of the current plot. Systematics always get appended to the format string (with an underscore). """ self._normalize(cutflows) if systematics is None: systematics = [] systematics = set(systematics + ['NA']) uncertainties = [] for systematic in systematics: if systematic == 'NA': uncertainties.append((None, '')) else: uncertainties.append((systematic + 'Up', '_{}Up'.format(systematic))) uncertainties.append((systematic + 'Down', '_{}Down'.format(systematic))) if procs is None: procs = [Process.get(k) for k in self.__hists if (not k.endswith('Up')) and (not k.endswith('Down'))] else: procs = map(Process.get, procs) for proc in procs: for uncertainty, suffix in uncertainties: histname = fmt.format(p=proc.limitname, v=self.__limitname, c=category) histname += suffix logging.debug("writing histogram {0}".format(histname)) try: hist = self._get_histogram(proc, uncertainty) file.WriteObject(hist, histname, "WriteDelete") except KeyError: pass
def normalize(cuts, lumi, limit=None): weights = None processed = None ntuplized = None analyzed = None for cut in cuts: if str(cut).lower() == "dataset processed": processed = cut elif str(cut).lower() == "dataset event weights": weights = cut elif str(cut).lower() == "ntuple": ntuplized = cut elif str(cut).lower() == "ntuple analyzed": analyzed = cut elif processed and weights and ntuplized and analyzed: break dsetnorm = StaticCut("Dataset norm") luminorm = StaticCut("Luminosity norm") for proc in cuts[-1].processes(): if str(proc).startswith("collisions") or str(proc).startswith("fakes"): dsetnorm[proc] = cuts[-1][proc] luminorm[proc] = cuts[-1][proc] else: p = Process.get(proc) scale = processed[proc] / float(weights[proc]) if ntuplized[proc] == 0 or analyzed[proc] == 0: logging.warning("0 event count for {}".format(proc)) fraction = 1. elif (not limit) or analyzed[proc] < limit: fraction = 1. else: fraction = analyzed[proc] / float(ntuplized[proc]) logging.warning("scaling {} by {} to compensate for partially analyzed dataset".format(proc, 1. / fraction)) dsetnorm[proc] = cuts[-1][proc] * scale / fraction luminorm[proc] = cuts[-1][proc] * scale / fraction * lumi * p.cross_section / float(p.events) cuts.append(dsetnorm) cuts.append(luminorm)
def fill(args, config): cutflows = load_cutflows(config) for name, cuts in cutflows.items(): normalize(cuts, config["lumi"], config.get("event limit")) categories, definitions = get_categories(config) atomic_processes = set(sum(map(Process.expand, config['plot'] + config['limits']), [])) limit_processes = config["limits"] all_processes = set(Process.get(n) for n in limit_processes + config['plot']) | atomic_processes if len(all_processes) != len(set([p.limitname for p in all_processes])): logging.error("the limit names of the processes are not unique and will lead to collisions!") if 'indir' in config and config['indir'] != config['outdir']: shutil.copy( os.path.join(config['indir'], 'cutflow.pkl'), os.path.join(config['outdir'], 'cutflow.pkl') ) fn = os.path.join(config.get("indir", config["outdir"]), "ntuple.root") forest = Forest(fn) for category, definition in zip(categories, definitions): logging.info("filling category: " + category) Plot.reset() for proc in atomic_processes: logging.info("filling process: " + str(proc)) weights = config.get(proc.cutflow + ' weights') systematics = config.get(proc.cutflow + ' systematics', []) uncertainties = [('NA', weights)] if args.systematics: uncertainties = expand_systematics(systematics, weights) for n, (systematic, weights) in enumerate(uncertainties): logging.info("using systematics: " + systematic) logging.info("using weights: " + ", ".join(weights)) for p in Plot.plots(): if (not args.essential and n == 0) or p.essential(): p.fill(proc, systematic, weights, definition) uncertainties = None if args.systematics: uncertainties = list(set(sum((config.get(p.cutflow + ' systematics', []) for p in atomic_processes), []))) logging.info("writing out plots for category: " + category) fn = os.path.join(config["outdir"], "plots.root") with open_rootfile(fn) as f: for p in Plot.plots(): p.write(f, cutflows, category, uncertainties, procs=all_processes, fmt=config["histformat"]) discriminants = config.get("discriminants", []) fn = os.path.join(config["outdir"], "limits.root") with open_rootfile(fn) as f: for p in Plot.plots(): if p.limitname in discriminants: p.write(f, cutflows, category, uncertainties, procs=limit_processes, fmt=config["histformat"]) timing = sorted(Plot.plots(), key=lambda p: p._time) for p in timing[:10] + timing[-10:]: logging.debug("plot filling time for {0}: {1}".format(p, p._time)) logging.info("done filling category: " + category) del forest