Source code for MPF.processProjector

from .histProjector import HistProjector
from .process import Process
from .commonHelpers.options import checkUpdateOpt, checkUpdateDict
from . import pyrootHelpers as PH
from .histograms import getHM
from .commonHelpers.logger import logger
logger = logger.getChild(__name__)


[docs]class ProcessProjector(object): """ Serves as a base class for use cases where multiple histograms should be projected from trees - defined by process instances Used by :py:meth:`~MPF.treePlotter.TreePlotter` and :py:meth:`~MPF.signalGridProjector.SignalGridProjector` {parameters} """ _doc_parameters = """ :param cut: Cut expression to be applied for all registered processes (default: "1") :param weight: Weight expression to be applied for all registered processes (default: "1") :param varexp: Expression to be used for filling histograms (default: "1") :param inputLumi: luminosity the trees are normalised to :param targetLumi: luminosity the histograms should be scaled to :param xmin: minimum on the x axis :param xmax: maximum on the x axis :param nbins: number of bins :param binLowEdges: list of low edges of bins (in this case xmin, xmax and nbins are ignored) :param useMultiHistDraw: use :py:meth:`~MPF.multiHistDrawer` when calling :py:meth:`~MPF.processProjector.ProcessProjector.fillHists` (loop tree only once and fill all histograms) (default: True) :param cutsDict: if this is given, fetch only yields for all cuts and create a histogram and yieldsDict for each process """ __doc__ = __doc__.format(parameters=_doc_parameters) defaults = dict( cut = "1", weight = "1", varexp = "1", inputLumi = 1., targetLumi = 1., xmin = 0., xmax = 2., nbins = 1, binLowEdges = None, useMultiHistDraw = True, cutsDict=None, ) def __init__(self, **kwargs): self.processes = [] self.processDict = {} self.sysProcesses = {} self.projector = HistProjector() self.defaults = checkUpdateDict(self.defaults, **kwargs)
[docs] def addProcess(self, process): """ Add a :py:meth:`~MPF.process.Process` """ self.processes.append(process) if process.name in self.processDict: raise KeyError("Process {} already exists!".format(process.name)) self.processDict[process.name] = process
[docs] def addProcessTree(self, name, filename, treename, **kwargs): """ Create and add a process from one tree in one file. The kwargs are passed to :py:meth:`~MPF.process.Process`: {Process_parameters} """ process = Process(name, **kwargs) process.addTree(filename, treename) self.addProcess(process)
addProcessTree.__doc__ = addProcessTree.__doc__.format(Process_parameters=Process._doc_parameters)
[docs] def addSysTreeToProcess(self, nomProcessName, sysName, filename, treename, **kwargs): """ Create and add a process from one tree in one file and register it as a systematic variation for the nominal process. The kwargs are passed to :py:meth:`~MPF.process.Process` :param nomProcessName: name of the nominal process :param sysName: name of the systematic variation :param treename: name of the tree :param filename: path to the rootfile containing the tree :param normToProcess: normalise the histogram to the same integral as the given process (by name) before plotting (only used in :py:class:`~MPF.treePlotter.TreePlotter`). """ process = Process(sysName+"_"+nomProcessName, sysTag=sysName, style="systematic", **kwargs) process.addTree(filename, treename) self.addProcess(process) if not nomProcessName in self.sysProcesses: self.sysProcesses[nomProcessName] = [] self.sysProcesses[nomProcessName].append(process)
[docs] def setProcessOptions(self, processName, **kwargs): """ Change the options of an existing process - referenced by its name. Only change the given options, leave the existing ones """ process = self.getProcess(processName) process.updateOptions(**kwargs)
[docs] def getProcesses(self, *selection): for p in self.processes: if len(selection) < 1: yield p elif p.style in selection: yield p
[docs] def getProcess(self, processName): for p in self.processes: if p.name == processName: return p
[docs] def registerToProjector(self, *selection, **kwargs): """Register hists for each process to the histProjector (to be filled later with multiHistDraw). Mainly for internal use in :py:meth:`~MPF.treePlotter.TreePlotter.registerPlot` and :py:meth:`~MPF.signalGridProjector.SignalGridProjector.registerHarvestList` :param opt: namedtuple containing :py:meth:`~MPF.processProjector.ProcessProjector` options :param selection: only register processes of this style(s) (like "background") """ opt = kwargs.pop("opt", self.getOpt()) logger.debug("Filtering the following processes: {}".format(selection)) for p in self.getProcesses(*selection): logger.debug("Registering hists for process {}".format(p.name)) if not p.cut is None: cut = "({})*({})".format(opt.cut, p.cut) else: cut = opt.cut if not p.varexp is None: varexp = p.varexp else: varexp = opt.varexp for filename, treename, treeKwargs in p.trees: if treeKwargs["cut"] is not None: treecut = "({})*({})".format(cut, treeKwargs["cut"]) else: treecut = cut if opt.cutsDict is not None: self.projector.registerYieldsDict(treename, opt.cutsDict, filename, weight=opt.weight) else: # this can be made more generic self.projector.registerTH1Path(treename, treecut, filename, weight=opt.weight, xmin=opt.xmin, xmax=opt.xmax, varexp=varexp, nbins=opt.nbins, binLowEdges=opt.binLowEdges)
[docs] def fillHists(self, opt=None): """ Project histograms for all processes :param opt: if given use these options instead of the current ones (see :py:meth:`~MPF.processProjector.ProcessProjector.getOpt`) """ if opt is None: opt = self.getOpt() if opt.cutsDict is not None: self.fillYieldsDicts(opt) return for p in self.processes: logger.debug("Creating hists for process {}".format(p.name)) hists = [] if not p.cut is None: cut = "({})*({})".format(opt.cut, p.cut) else: cut = opt.cut if not p.varexp is None: varexp = p.varexp else: varexp = opt.varexp for filename, treename, treeKwargs in p.trees: if treeKwargs["cut"] is not None: treecut = "({})*({})".format(cut, treeKwargs["cut"]) else: treecut = cut hists.append(self.projector.getTH1Path(treename, treecut, filename, weight=opt.weight, xmin=opt.xmin, xmax=opt.xmax, varexp=varexp, nbins=opt.nbins, binLowEdges=opt.binLowEdges)) p.hist = PH.getMergedHist(hists) if (not p.style == "data" and opt.targetLumi is not None and opt.inputLumi is not None and not p.noLumiNorm): p.hist.Scale(opt.targetLumi/opt.inputLumi) if p.norm: try: p.hist.Scale(1./p.hist.Integral()) except ZeroDivisionError: logger.error("Can't normalise histogram for process {} - Integral is empty".format(p.name)) if p.scale is not None: p.hist.Scale(p.scale)
[docs] def fillHistsSysErrors(self): """Adds errors based on variational histograms for all processes to their histograms. Should only be used if variations are not correlated across different processes (e.g. **don't** use it for :py:class:`~MPF.treePlotter.TreePlotter` - there is a treatment included for this via :py:meth:`~MPF.plotStore.PlotStore.registerSysHist`) """ for p in self.processes: if p.style == "systematic": continue if not p.name in self.sysProcesses: continue p.hist = getHM(p.hist) p.hist.addSystematicError(*[_p.hist for _p in self.sysProcesses[p.name]])
[docs] def fillYieldsDicts(self, opt=None): """ Fill yields dicts from cutsDict :param opt: if given use these options instead of the current ones (see :py:meth:`~MPF.processProjector.ProcessProjector.getOpt`) """ if opt is None: opt = self.getOpt() for p in self.processes: logger.debug("Creating yieldsDict for process {}".format(p.name)) if not p.cut is None: weight = "({})*({})".format(opt.weight, p.cut) else: weight = opt.weight yieldsDicts = [] for filename, treename, treeKwargs in p.trees: if treeKwargs["cut"] is not None: treeweight = "({})*({})".format(weight, treeKwargs["cut"]) else: treeweight = weight yieldsDicts.append(self.projector.getYieldsDict(treename, opt.cutsDict, filename, weight=treeweight)) p.yieldsDict = PH.getMergedYieldsDict(*yieldsDicts) if (not p.style == "data" and opt.targetLumi is not None and opt.inputLumi is not None): PH.scaleYieldsDict(p.yieldsDict, opt.targetLumi/opt.inputLumi) if p.scale is not None: PH.scaleYieldsDict(p.yieldsDict, p.scale) p.hist = PH.getHistFromYieldsDict(p.yieldsDict) if p.norm: # only do this for hist p.hist.Scale(1./p.hist.Integral())
[docs] def setDefaults(self, **kwargs): self.defaults = checkUpdateDict(self.defaults, **kwargs)
[docs] def getOpt(self, **kwargs): """ Get the namedtuple containing the current :py:meth:`~MPF.processProjector.ProcessProjector` options, updated by the given arguments. """ return checkUpdateOpt(self.defaults, **kwargs)