123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360 |
- import io
- import os
- import sys
- import itertools as it
- from os.path import dirname, join, abspath, normpath, getctime
- from math import ceil, floor, sqrt
- from collections import deque
- from subprocess import run, PIPE
- from IPython.display import Image
- import pydotplus.graphviz as pdp
- import ROOT
- from graph_vals import parse
- PRJ_PATH = normpath(join(dirname(abspath(__file__)), "../"))
- EXE_PATH = join(PRJ_PATH, "build/main")
- PDG = {1: 'd', -1: 'd̄',
- 2: 'u', -2: 'ū',
- 3: 's', -3: 's̄',
- 4: 'c', -4: 'c̄',
- 5: 'b', -5: 'b̄',
- 6: 't', -6: 't̄',
- 11: 'e-', -11: 'e+',
- 12: 'ν_e', -12: 'ῡ_e',
- 13: 'μ-', -13: 'μ+',
- 14: 'ν_μ', -14: 'ῡ_μ',
- 15: 'τ-', -15: 'τ+',
- 16: 'ν_τ', -16: 'ῡ_τ',
- 21: 'g',
- 22: 'γ',
- 23: 'Z0',
- 24: 'W+', -24: 'W-',
- 25: 'H',
- }
- SINGLE_PLOT_SIZE = (600, 450)
- MAX_WIDTH = 1800
- SCALE = .75
- CAN_SIZE_DEF = (int(1600*SCALE), int(1200*SCALE))
- CANVAS = ROOT.TCanvas("c1", "", *CAN_SIZE_DEF)
- ROOT.gStyle.SetPalette(112) # set the "virdidis" color map
- VALUES = {}
- def clear():
- CANVAS.Clear()
- CANVAS.SetCanvasSize(*CAN_SIZE_DEF)
- def get_color(val, max_val, min_val = 0):
- val = (val-min_val)/(max_val-min_val)
- val = round(val * (ROOT.gStyle.GetNumberOfColors()-1))
- col_idx = ROOT.gStyle.GetColorPalette(val)
- col = ROOT.gROOT.GetColor(col_idx)
- r = floor(256*col.GetRed())
- g = floor(256*col.GetGreen())
- b = floor(256*col.GetBlue())
- gs = (r + g + b)//3
- text_color = 'white' if gs < 100 else 'black'
- return '#{:02x}{:02x}{:02x}'.format(r, g, b), text_color
- def show_event(dataset, idx):
- ids = list(dataset.GenPart_pdgId[idx])
- stats = list(dataset.GenPart_status[idx])
- energies = list(dataset.GenPart_energy[idx])
- links = list(dataset.GenPart_motherIndex[idx])
- max_energy = max(energies)
- g = pdp.Dot()
- for i, id_ in enumerate(ids):
- color, text_color = get_color(energies[i], max_energy)
- shape = "ellipse" if stats[i] in (1, 23) else "invhouse"
- label = "{}({})".format(PDG[id_], stats[i])
- # label = PDG[id_]+"({:03e})".format(energies[i])
- g.add_node(pdp.Node(str(i), label=label,
- style="filled",
- shape=shape,
- fontcolor=text_color,
- fillcolor=color))
- for i, mother in enumerate(links):
- if mother != -1:
- g.add_edge(pdp.Edge(str(mother), str(i)))
- return Image(g.create_gif())
- def show_function(dataset, fname):
- from IPython.display import Markdown
- def md_single(fname_):
- impl = dataset._function_impl_lookup[fname_]
- return '*{}*\n-----\n```cpp\n{}\n```\n\n---'.format(fname_, impl)
- try:
- return Markdown('\n'.join(md_single(fname_) for fname_ in iter(fname)))
- except TypeError:
- return Markdown(md_single(fname))
- def show_value(dataset, container):
- if type(container) != str:
- container = container.GetName().split(':')[1]
- g, functions = parse(VALUES[container], container)
- try:
- return Image(g.create_gif()), show_function(dataset, functions)
- except Exception as e:
- print(e)
- print(g.to_string())
- class OutputCapture:
- def __init__(self):
- self.my_stdout = io.StringIO()
- self.my_stderr = io.StringIO()
- def get_stdout(self):
- self.my_stdout.seek(0)
- return self.my_stdout.read()
- def get_stderr(self):
- self.my_stderr.seek(0)
- return self.my_stderr.read()
- def __enter__(self):
- self.stdout = sys.stdout
- self.stderr = sys.stderr
- sys.stdout = self.my_stdout
- sys.stderr = self.my_stderr
- def __exit__(self, *args):
- sys.stdout = self.stdout
- sys.stderr = self.stderr
- self.stdout = None
- self.stderr = None
- def normalize_columns(hist2d):
- normHist = ROOT.TH2D(hist2d)
- cols, rows = hist2d.GetNbinsX(), hist2d.GetNbinsY()
- for col in range(1, cols+1):
- sum_ = 0
- for row in range(1, rows+1):
- sum_ += hist2d.GetBinContent(col, row)
- if sum_ == 0:
- continue
- for row in range(1, rows+1):
- norm = hist2d.GetBinContent(col, row) / sum_
- normHist.SetBinContent(col, row, norm)
- return normHist
- class ResultSet:
- def __init__(self, sample_name, input_filename):
- self.sample_name = sample_name
- self.input_filename = input_filename
- self.output_filename = self.input_filename.replace(".root", "_result.root")
- self.conditional_recompute()
- self.load_objects()
- ResultSet.add_collection(self)
- def conditional_recompute(self):
- def recompute():
- print("Running analysis for sample: ", self.sample_name)
- if run([EXE_PATH, "-s", "-f", self.input_filename, "-l", self.sample_name]).returncode != 0:
- raise RuntimeError(("Failed running analysis code."
- " See log file for more information"))
- if run(["make"], cwd=join(PRJ_PATH, "build"), stdout=PIPE, stderr=PIPE).returncode != 0:
- raise RuntimeError("Failed recompiling analysis code")
- if (not os.path.isfile(self.output_filename) or (getctime(EXE_PATH) > getctime(self.output_filename))):
- recompute()
- else:
- print("Loading unchanged result file ", self.output_filename)
- def load_objects(self):
- file = ROOT.TFile.Open(self.output_filename)
- l = file.GetListOfKeys()
- self.map = {}
- VALUES.update(dict(file.Get("_value_lookup")))
- for i in range(l.GetSize()):
- name = l.At(i).GetName()
- new_name = ":".join((self.sample_name, name))
- obj = file.Get(name)
- try:
- obj.SetName(new_name)
- obj.SetDirectory(0) # disconnects Object from file
- except AttributeError:
- pass
- if 'ROOT.vector<int>' in str(type(obj)) and '_count' in name:
- obj = obj[0]
- self.map[name] = obj
- setattr(self, name, obj)
- file.Close()
- # Now add these histograms into the current ROOT directory (in memory)
- # and remove old versions if needed
- for obj in self.map.values():
- try:
- old_obj = ROOT.gDirectory.Get(obj.GetName())
- ROOT.gDirectory.Remove(old_obj)
- ROOT.gDirectory.Add(obj)
- except AttributeError:
- pass
- self.xsec = self._xsecs[self.sample_name]*1000 # convert to fb
- self.event_count = self._event_counts[self.sample_name]
- self.lumi = self.event_count / self.xsec
- @classmethod
- def calc_shape(cls, n_plots):
- if n_plots*SINGLE_PLOT_SIZE[0] > MAX_WIDTH:
- shape_x = MAX_WIDTH//SINGLE_PLOT_SIZE[0]
- shape_y = ceil(n_plots / shape_x)
- return (shape_x, shape_y)
- else:
- return (n_plots, 1)
- def draw(self, shape=None):
- objs = [obj for obj in self.map.values() if hasattr(obj, "Draw")]
- if shape is None:
- n_plots = len(objs)
- shape = self.calc_shape(n_plots)
- CANVAS.Clear()
- CANVAS.SetCanvasSize(shape[0]*SINGLE_PLOT_SIZE[0], shape[1]*SINGLE_PLOT_SIZE[1])
- CANVAS.Divide(*shape)
- i = 1
- for hist in objs:
- CANVAS.cd(i)
- try:
- hist.SetStats(False)
- except AttributeError:
- pass
- if type(hist) in (ROOT.TH1I, ROOT.TH1F, ROOT.TH1D):
- hist.SetMinimum(0)
- hist.Draw(self.get_draw_option(hist))
- i += 1
- CANVAS.Draw()
- @staticmethod
- def get_draw_option(obj):
- obj_type = type(obj)
- if obj_type in (ROOT.TH1F, ROOT.TH1I, ROOT.TH1D):
- return ""
- elif obj_type in (ROOT.TH2F, ROOT.TH2I, ROOT.TH2D):
- return "COLZ"
- elif obj_type in (ROOT.TGraph,):
- return "A*"
- else:
- return None
- @classmethod
- def get_hist_set(cls, attrname):
- labels, hists = zip(*[(sample_name, getattr(h, attrname))
- for sample_name, h in cls.collections.items()])
- return labels, hists
- @classmethod
- def add_collection(cls, hc):
- if not hasattr(cls, "collections"):
- cls.collections = {}
- cls.collections[hc.sample_name] = hc
- @classmethod
- def stack_hist(cls,
- hist_name,
- title="",
- enable_fill=False,
- normalize_to=0,
- draw=False,
- draw_canvas=True,
- draw_option="",
- make_legend=False,
- _stacks={}):
- labels, hists = cls.get_hist_set(hist_name)
- if draw_canvas:
- CANVAS.Clear()
- CANVAS.SetCanvasSize(SINGLE_PLOT_SIZE[0],
- SINGLE_PLOT_SIZE[1])
- colors = it.cycle([ROOT.kRed, ROOT.kBlue, ROOT.kGreen, ROOT.kYellow])
- stack = ROOT.THStack(hist_name+"_stack", title)
- if labels is None:
- labels = [hist.GetName() for hist in hists]
- if type(normalize_to) in (int, float):
- normalize_to = [normalize_to]*len(hists)
- ens = enumerate(zip(hists, labels, colors, normalize_to))
- for i, (hist, label, color, norm) in ens:
- hist_copy = hist
- hist_copy = hist.Clone(hist.GetName()+"_clone" + draw_option)
- hist_copy.SetTitle(label)
- if enable_fill:
- hist_copy.SetFillColorAlpha(color, 0.75)
- hist_copy.SetLineColorAlpha(color, 0.75)
- if norm:
- integral = hist_copy.Integral()
- hist_copy.Scale(norm/integral, "nosw2")
- hist_copy.SetStats(True)
- stack.Add(hist_copy)
- if draw:
- stack.Draw(draw_option)
- if make_legend:
- CANVAS.BuildLegend(0.75, 0.75, 0.95, 0.95, "")
- # prevent stack from getting garbage collected
- _stacks[stack.GetName()] = stack
- if draw_canvas:
- CANVAS.Draw()
- return stack
- @classmethod
- def stack_hist_array(cls,
- hist_names,
- titles,
- shape=None, **kwargs):
- n_hist = len(hist_names)
- if shape is None:
- if n_hist <= 4:
- shape = (1, n_hist)
- else:
- shape = (ceil(sqrt(n_hist)),)*2
- CANVAS.SetCanvasSize(SINGLE_PLOT_SIZE[0]*shape[0],
- SINGLE_PLOT_SIZE[1]*shape[1])
- CANVAS.Divide(*shape)
- for i, hist_name, title in zip(range(1, n_hist+1), hist_names, titles):
- CANVAS.cd(i)
- cls.stack_hist(hist_name, title=title, draw=True,
- draw_canvas=False, **kwargs)
- CANVAS.cd(n_hist).BuildLegend(0.75, 0.75, 0.95, 0.95, "")
- pts = deque([], 50)
- @classmethod
- def hist_array_single(cls,
- hist_name,
- title=None,
- **kwargs):
- n_hist = len(cls.collections)
- shape = cls.calc_shape(n_hist)
- CANVAS.SetCanvasSize(SINGLE_PLOT_SIZE[0]*shape[0],
- SINGLE_PLOT_SIZE[1]*shape[1])
- CANVAS.Divide(*shape)
- labels, hists = cls.get_hist_set(hist_name)
- def pave_loc():
- hist.Get
- for i, label, hist in zip(range(1, n_hist+1), labels, hists):
- CANVAS.cd(i)
- hist.SetStats(False)
- hist.Draw(cls.get_draw_option(hist))
- pt = ROOT.TPaveText(0.70, 0.87, 0.85, 0.95, "NDC")
- pt.AddText("Dataset: "+label)
- pt.Draw()
- cls.pts.append(pt)
|