from __future__ import absolute_import
from math import sqrt
import ROOT
from . import log; log = log[__name__]
from . import MIN_ROOT_VERSION
from ...extern.six import string_types
from ...memory.keepalive import keepalive
from ...base import NamedObject
from ... import asrootpy, QROOT, ROOT_VERSION, ROOTError, IN_NOSETESTS
if ROOT_VERSION < MIN_ROOT_VERSION:
raise NotImplementedError(
"histfactory requires ROOT {0} but you are using {1}".format(
MIN_ROOT_VERSION, ROOT_VERSION))
try:
HistFactory = QROOT.RooStats.HistFactory
except ROOTError:
if IN_NOSETESTS:
from nose.plugins.skip import SkipTest
raise SkipTest("ROOT is not compiled with RooStats enabled")
raise
Constraint = HistFactory.Constraint
__all__ = [
'Constraint',
'Data',
'Sample',
'HistoSys',
'HistoFactor',
'NormFactor',
'OverallSys',
'ShapeFactor',
'ShapeSys',
'Channel',
'Measurement',
]
class _Named(object):
@property
def name(self):
return self.GetName()
@name.setter
def name(self, n):
self.SetName(n)
def __str__(self):
return self.__repr__()
def __repr__(self):
return "{0}('{1}')".format(
self.__class__.__name__, self.GetName())
class _HistNamePathFile(object):
@property
def hist_name(self):
return self.GetHistoName()
@hist_name.setter
def hist_name(self, name):
self.SetHistoName(name)
@property
def hist_path(self):
return self.GetHistoPath()
@hist_path.setter
def hist_path(self, path):
self.SetHistoPath(path)
@property
def hist_file(self):
return self.GetInputFile()
@hist_file.setter
def hist_file(self, infile):
self.SetInputFile(infile)
class _SampleBase(_Named, _HistNamePathFile):
def SetHisto(self, hist):
super(_SampleBase, self).SetHisto(hist)
self.SetHistoName(hist.name)
keepalive(self, hist)
def GetHisto(self):
hist = super(_SampleBase, self).GetHisto()
# NULL pointer check
if hist == None:
return None
return asrootpy(hist)
@property
def hist(self):
return self.GetHisto()
@hist.setter
def hist(self, h):
self.SetHisto(h)
def __add__(self, other):
if self.name != other.name:
raise ValueError("attempting to add samples with different names")
hist1 = self.GetHisto()
hist2 = other.GetHisto()
sample = self.__class__(self.name)
if hist1 is not None and hist2 is not None:
hist3 = hist1 + hist2
hist3.name = '{0}_plus_{1}'.format(hist1.name, hist2.name)
sample.SetHisto(hist3)
return sample
[docs]class Data(_SampleBase, HistFactory.Data):
_ROOT = HistFactory.Data
def __init__(self, name, hist=None):
# require a name
super(Data, self).__init__()
self.name = name
if hist is not None:
self.SetHisto(hist)
[docs] def total(self, xbin1=1, xbin2=-2):
"""
Return the total yield and its associated statistical uncertainty.
"""
return self.hist.integral(xbin1=xbin1, xbin2=xbin2, error=True)
def Clone(self):
clone = Data(self.name)
hist = self.hist
if hist is not None:
clone.hist = hist.Clone(shallow=True)
return clone
[docs]class Sample(_SampleBase, HistFactory.Sample):
_ROOT = HistFactory.Sample
def __init__(self, name, hist=None):
# require a sample name
super(Sample, self).__init__(name)
if hist is not None:
self.SetHisto(hist)
def __add__(self, other):
if self.GetHistoFactorList() or other.GetHistoFactorList():
raise NotImplementedError(
"Samples cannot be summed if "
"they contain HistoFactors")
if self.GetShapeFactorList() or other.GetShapeFactorList():
raise NotImplementedError(
"Samples cannot be summed if "
"they contain ShapeFactors")
if self.GetShapeSysList() or other.GetShapeSysList():
raise NotImplementedError(
"Samples cannot be summed if "
"they contain ShapeSys")
if self.GetNormalizeByTheory() != other.GetNormalizeByTheory():
raise ValueError(
"attempting to sum samples with "
"inconsistent NormalizeByTheory")
sample = super(Sample, self).__add__(other)
sample.SetNormalizeByTheory(self.GetNormalizeByTheory())
# sum the histosys
syslist1 = self.GetHistoSysList()
syslist2 = other.GetHistoSysList()
if len(syslist1) != len(syslist2):
raise ValueError(
"attempting to sum Samples with HistoSys lists of "
"differing lengths")
for sys1, sys2 in zip(syslist1, syslist2):
sample.AddHistoSys(sys1 + sys2)
# include the overallsys
overall1 = self.GetOverallSysList()
overall2 = other.GetOverallSysList()
if len(overall1) != len(overall2):
raise ValueError(
"attempting to sum Samples with OverallSys lists of "
"differing lengths")
for o1, o2 in zip(overall1, overall2):
if o1.name != o2.name:
raise ValueError(
"attempting to sum Samples containing OverallSys "
"with differing names: {0}, {1}".format(
o1.name, o2.name))
# TODO check equality of value, low and high
sample.AddOverallSys(o1)
# include the normfactors
norms1 = self.GetNormFactorList()
norms2 = other.GetNormFactorList()
if len(norms1) != len(norms2):
raise ValueError(
"attempting to sum Samples with NormFactor lists of "
"differing lengths")
for norm1, norm2 in zip(norms1, norms2):
if norm1.name != norm2.name:
raise ValueError(
"attempting to sum Samples containing NormFactors "
"with differing names: {0}, {1}".format(
norm1.name, norm2.name))
# TODO check equality of value, low and high
sample.AddNormFactor(norm1)
return sample
def __radd__(self, other):
# support sum([list of Samples])
if other == 0:
return self
raise TypeError(
"unsupported operand type(s) for +: '{0}' and '{1}'".format(
other.__class__.__name__, self.__class__.__name__))
def __mul__(self, scale):
clone = self.Clone()
clone *= scale
return clone
def __imul__(self, scale):
hist = self.hist
if hist is not None:
hist *= scale
for hsys in self.histo_sys:
low = hsys.low
high = hsys.high
if low is not None:
low *= scale
if high is not None:
high *= scale
return self
[docs] def sys_names(self):
"""
Return a list of unique systematic names from OverallSys and HistoSys
"""
names = {}
for osys in self.overall_sys:
names[osys.name] = None
for hsys in self.histo_sys:
names[hsys.name] = None
return names.keys()
[docs] def iter_sys(self):
"""
Iterate over sys_name, overall_sys, histo_sys.
overall_sys or histo_sys may be None for any given sys_name.
"""
names = self.sys_names()
for name in names:
osys = self.GetOverallSys(name)
hsys = self.GetHistoSys(name)
yield name, osys, hsys
[docs] def sys_hist(self, name=None):
"""
Return the effective low and high histogram for a given systematic.
If this sample does not contain the named systematic then return
the nominal histogram for both low and high variations.
"""
if name is None:
low = self.hist.Clone(shallow=True)
high = self.hist.Clone(shallow=True)
return low, high
osys = self.GetOverallSys(name)
hsys = self.GetHistoSys(name)
if osys is None:
osys_high, osys_low = 1., 1.
else:
osys_high, osys_low = osys.high, osys.low
if hsys is None:
hsys_high = self.hist.Clone(shallow=True)
hsys_low = self.hist.Clone(shallow=True)
else:
hsys_high = hsys.high.Clone(shallow=True)
hsys_low = hsys.low.Clone(shallow=True)
return hsys_low * osys_low, hsys_high * osys_high
def has_sys(self, name):
return (self.GetOverallSys(name) is not None or
self.GetHistoSys(name) is not None)
[docs] def total(self, xbin1=1, xbin2=-2):
"""
Return the total yield and its associated statistical and
systematic uncertainties.
"""
integral, stat_error = self.hist.integral(
xbin1=xbin1, xbin2=xbin2, error=True)
# sum systematics in quadrature
ups = [0]
dns = [0]
for sys_name in self.sys_names():
sys_low, sys_high = self.sys_hist(sys_name)
up = sys_high.integral(xbin1=xbin1, xbin2=xbin2) - integral
dn = sys_low.integral(xbin1=xbin1, xbin2=xbin2) - integral
if up > 0:
ups.append(up**2)
else:
dns.append(up**2)
if dn > 0:
ups.append(dn**2)
else:
dns.append(dn**2)
syst_error = (sqrt(sum(ups)), sqrt(sum(dns)))
return integral, stat_error, syst_error
###########################
# HistoSys
###########################
def AddHistoSys(self, *args):
super(Sample, self).AddHistoSys(*args)
if len(args) == 1:
# args is a HistoSys
keepalive(self, args[0])
def RemoveHistoSys(self, name):
histosys_vect = super(Sample, self).GetHistoSysList()
ivect = histosys_vect.begin()
for histosys in histosys_vect:
if histosys.GetName() == name:
histosys_vect.erase(ivect)
break
ivect.__preinc__()
def GetHistoSys(self, name):
histosys_vect = super(Sample, self).GetHistoSysList()
for histosys in histosys_vect:
if histosys.GetName() == name:
return asrootpy(histosys)
return None
def GetHistoSysList(self):
return [asrootpy(syst) for syst in
super(Sample, self).GetHistoSysList()]
@property
def histo_sys(self):
return self.GetHistoSysList()
###########################
# HistoFactor
###########################
def AddHistoFactor(self, *args):
super(Sample, self).AddHistoFactor(*args)
if len(args) == 1:
# args is a HistoFactor
keepalive(self, args[0])
def RemoveHistoFactor(self, name):
histofactor_vect = super(Sample, self).GetHistoFactorList()
ivect = histosys_factor.begin()
for histofactor in histofactor_vect:
if histofactor.GetName() == name:
histofactor_vect.erase(ivect)
break
ivect.__preinc__()
def GetHistoFactor(self, name):
histofactor_vect = super(Sample, self).GetHistoFactorList()
for histofactor in histofactor_vect:
if histofactor.GetName() == name:
return asrootpy(histofactor)
return None
def GetHistoFactorList(self):
return [asrootpy(syst) for syst in
super(Sample, self).GetHistoFactorList()]
@property
def histo_factors(self):
return self.GetHistoFactorList()
###########################
# NormFactor
###########################
def AddNormFactor(self, *args):
super(Sample, self).AddNormFactor(*args)
if len(args) == 1:
# args is a NormFactor
keepalive(self, args[0])
def RemoveNormFactor(self, name):
normfactor_vect = super(Sample, self).GetNormFactorList()
ivect = normfactor_vect.begin()
for normfactor in normfactor_vect:
if normfactor.GetName() == name:
normfactor_vect.erase(ivect)
break
ivect.__preinc__()
def GetNormFactor(self, name):
normfactor_vect = super(Sample, self).GetNormFactorList()
for normfactor in normfactor_vect:
if normfactor.GetName() == name:
return asrootpy(normfactor)
return None
def GetNormFactorList(self):
return [asrootpy(norm) for norm in
super(Sample, self).GetNormFactorList()]
@property
def norm_factors(self):
return self.GetNormFactorList()
###########################
# OverallSys
###########################
def AddOverallSys(self, *args):
super(Sample, self).AddOverallSys(*args)
if len(args) == 1:
# args is a OverallSys
keepalive(self, args[0])
def RemoveOverallSys(self, name):
overallsys_vect = super(Sample, self).GetOverallSysList()
ivect = overallsys_vect.begin()
for overallsys in overallsys_vect:
if overallsys.GetName() == name:
overallsys_vect.erase(ivect)
break
ivect.__preinc__()
def GetOverallSys(self, name):
overallsys_vect = super(Sample, self).GetOverallSysList()
for overallsys in overallsys_vect:
if overallsys.GetName() == name:
return asrootpy(overallsys)
return None
def GetOverallSysList(self):
return [asrootpy(syst) for syst in
super(Sample, self).GetOverallSysList()]
@property
def overall_sys(self):
return self.GetOverallSysList()
###########################
# ShapeFactor
###########################
def AddShapeFactor(self, shapefactor):
super(Sample, self).AddShapeFactor(shapefactor)
if isinstance(shapefactor, ROOT.RooStats.HistFactory.ShapeFactor):
keepalive(self, shapefactor)
def RemoveShapeFactor(self, name):
shapefactor_vect = super(Sample, self).GetShapeFactorList()
ivect = shapefactor_vect.begin()
for shapefactor in shapefactor_vect:
if shapefactor.GetName() == name:
shapefactor_vect.erase(ivect)
break
ivect.__preinc__()
def GetShapeFactor(self, name):
shapefactor_vect = super(Sample, self).GetShapeFactorList()
for shapefactor in shapefactor_vect:
if shapefactor.GetName() == name:
return asrootpy(shapefactor)
return None
def GetShapeFactorList(self):
return [asrootpy(sf) for sf in
super(Sample, self).GetShapeFactorList()]
@property
def shape_factors(self):
return self.GetShapeFactorList()
###########################
# ShapeSys
###########################
def AddShapeSys(self, *args):
super(Sample, self).AddShapeSys(*args)
if len(args) == 1:
# args is a ShapeSys
keepalive(self, args[0])
def RemoveShapeSys(self, name):
shapesys_vect = super(Sample, self).GetShapeSysList()
ivect = shapesys_vect.begin()
for shapesys in shapesys_vect:
if shapesys.GetName() == name:
shapesys_vect.erase(ivect)
break
ivect.__preinc__()
def GetShapeSys(self, name):
shapesys_vect = super(Sample, self).GetShapeSysList()
for shapesys in shapesys_vect:
if shapesys.GetName() == name:
return asrootpy(shapesys)
return None
def GetShapeSysList(self):
return [asrootpy(ss) for ss in
super(Sample, self).GetShapeSysList()]
@property
def shape_sys(self):
return self.GetShapeSysList()
def Clone(self):
clone = self.__class__(self.name)
hist = self.hist
if hist is not None:
clone.hist = hist.Clone(shallow=True)
# HistoSys
for hsys in self.histo_sys:
clone.AddHistoSys(hsys.Clone())
# HistoFactor
for hfact in self.histo_factors:
clone.AddHistoFactor(hfact.Clone())
# NormFactor
for norm in self.norm_factors:
clone.AddNormFactor(norm.Clone())
# OverallSys
for osys in self.overall_sys:
clone.AddOverallSys(osys.Clone())
# ShapeFactor
for sfact in self.shape_factors:
clone.AddShapeFactor(sfact.Clone())
# ShapeSys
for ssys in self.shape_sys:
clone.AddShapeSys(ssys.Clone())
return clone
class _HistoSysBase(object):
def SetHistoHigh(self, hist):
super(_HistoSysBase, self).SetHistoHigh(hist)
self.SetHistoNameHigh(hist.name)
keepalive(self, hist)
def SetHistoLow(self, hist):
super(_HistoSysBase, self).SetHistoLow(hist)
self.SetHistoNameLow(hist.name)
keepalive(self, hist)
def GetHistoHigh(self):
hist = super(_HistoSysBase, self).GetHistoHigh()
# NULL pointer check
if hist == None:
return None
return asrootpy(hist)
def GetHistoLow(self):
hist = super(_HistoSysBase, self).GetHistoLow()
# NULL pointer check
if hist == None:
return None
return asrootpy(hist)
@property
def low(self):
return self.GetHistoLow()
@low.setter
def low(self, h):
self.SetHistoLow(h)
@property
def high(self):
return self.GetHistoHigh()
@high.setter
def high(self, h):
self.SetHistoHigh(h)
@property
def low_name(self):
return self.GetHistoNameLow()
@low_name.setter
def low_name(self, name):
self.SetHistoNameLow(name)
@property
def high_name(self):
return self.GetHistoNameHigh()
@high_name.setter
def high_name(self, name):
self.SetHistoNameHigh(name)
@property
def low_path(self):
return self.GetHistoPathLow()
@low_path.setter
def low_path(self, path):
self.SetHistoPathLow(path)
@property
def high_path(self):
return self.GetHistoPathHigh()
@high_path.setter
def high_path(self, path):
self.SetHistoPathHigh(path)
@property
def low_file(self):
return self.GetInputFileLow()
@low_file.setter
def low_file(self, infile):
self.SetInputFileLow(infile)
@property
def high_file(self):
return self.GetInputFileHigh()
@high_file.setter
def high_file(self, infile):
self.SetInputFileHigh(infile)
def Clone(self):
clone = self.__class__(self.name)
low = self.low
high = self.high
if low is not None:
clone.low = low.Clone(shallow=True)
if high is not None:
clone.high = high.Clone(shallow=True)
clone.low_name = self.low_name
clone.high_name = self.high_name
clone.low_path = self.low_path
clone.high_path = self.high_path
clone.low_file = self.low_file
clone.high_file = self.high_file
return clone
[docs]class HistoSys(_Named, _HistoSysBase, HistFactory.HistoSys):
_ROOT = HistFactory.HistoSys
def __init__(self, name, low=None, high=None):
# require a name
super(HistoSys, self).__init__(name)
if low is not None:
self.low = low
if high is not None:
self.high = high
def __add__(self, other):
if self.name != other.name:
raise ValueError("attempting to add HistoSys with different names")
histosys = HistoSys(self.name)
low = self.low + other.low
low.name = '{0}_plus_{1}'.format(self.low.name, other.low.name)
histosys.low = low
high = self.high + other.high
high.name = '{0}_plus_{1}'.format(self.high.name, other.high.name)
histosys.high = high
return histosys
[docs]class HistoFactor(_Named, _HistoSysBase,
HistFactory.HistoFactor):
_ROOT = HistFactory.HistoFactor
def __init__(self, name, low=None, high=None):
# require a name
super(HistoFactor, self).__init__(name)
if low is not None:
self.low = low
if high is not None:
self.high = high
def __add__(self, other):
raise NotImplementedError("HistoFactors cannot be summed")
[docs]class NormFactor(_Named, HistFactory.NormFactor):
_ROOT = HistFactory.NormFactor
def __init__(self, name, value=None, low=None, high=None, const=None):
super(NormFactor, self).__init__()
self.name = name
if value is not None:
self.value = value
if low is not None:
self.low = low
if high is not None:
self.high = high
if const is not None:
self.const = const
@property
def const(self):
return self.GetConst()
@const.setter
def const(self, value):
self.SetConst(value)
@property
def value(self):
return self.GetVal()
@value.setter
def value(self, value):
self.SetVal(value)
@property
def low(self):
return self.GetLow()
@low.setter
def low(self, value):
self.SetLow(value)
@property
def high(self):
return self.GetHigh()
@high.setter
def high(self, value):
self.SetHigh(value)
def Clone(self):
return NormFactor(self.name,
value=self.value,
low=self.low,
high=self.high,
const=self.const)
[docs]class OverallSys(_Named, HistFactory.OverallSys):
_ROOT = HistFactory.OverallSys
def __init__(self, name, low=None, high=None):
# require a name
super(OverallSys, self).__init__()
self.name = name
if low is not None:
self.low = low
if high is not None:
self.high = high
@property
def low(self):
return self.GetLow()
@low.setter
def low(self, value):
self.SetLow(value)
@property
def high(self):
return self.GetHigh()
@high.setter
def high(self, value):
self.SetHigh(value)
def Clone(self):
return OverallSys(self.name, low=self.low, high=self.high)
[docs]class ShapeFactor(_Named, HistFactory.ShapeFactor):
_ROOT = HistFactory.ShapeFactor
def __init__(self, name):
# require a name
super(ShapeFactor, self).__init__()
self.name = name
def Clone(self):
return ShapeFactor(self.name)
[docs]class ShapeSys(_Named, _HistNamePathFile, HistFactory.ShapeSys):
_ROOT = HistFactory.ShapeSys
def __init__(self, name):
# require a name
super(ShapeSys, self).__init__()
self.name = name
# ConstraintType not initialized correctly on C++ side
# ROOT.RooStats.HistFactory.Constraint.Gaussian
super(ShapeSys, self).SetConstraintType(Constraint.Gaussian)
def SetConstraintType(self, value):
_value = value.lower() if isinstance(value, string_types) else value
if _value in (Constraint.Gaussian, 'gauss', 'gaussian'):
super(ShapeSys, self).SetConstraintType(Constraint.Gaussian)
elif _value in (Constraint.Poisson, 'pois', 'poisson'):
super(ShapeSys, self).SetConstraintType(Constraint.Poisson)
else:
raise ValueError(
"'{0}' is not a valid constraint".format(value))
@property
def constraint(self):
return super(ShapeSys, self).GetConstraintType()
@constraint.setter
def constraint(self, value):
self.SetConstraintType(value)
def GetErrorHist(self):
hist = super(ShapeSys, self).GetErrorHist()
# NULL pointer check
if hist == None:
return None
return asrootpy(hist)
def SetErrorHist(self, hist):
super(ShapeSys, self).SetErrorHist(hist)
self.SetHistoName(hist.name)
keepalive(self, hist)
@property
def hist(self):
self.GetErrorHist()
@hist.setter
def hist(self, h):
self.SetErrorHist(h)
def Clone(self):
clone = ShapeSys(self.name)
hist = self.hist
if hist is not None:
clone.hist = hist.Clone(shallow=True)
return clone
[docs]class Channel(_Named, HistFactory.Channel):
_ROOT = HistFactory.Channel
def __init__(self, name, samples=None, data=None, inputfile=""):
# require a name
super(Channel, self).__init__(name, inputfile)
if samples is not None:
for sample in samples:
self.AddSample(sample)
if data is not None:
self.SetData(data)
def __add__(self, other):
channel = Channel('{0}_plus_{1}'.format(self.name, other.name))
channel.SetData(self.data + other.data)
samples1 = self.samples
samples2 = other.samples
if len(samples1) != len(samples2):
raise ValueError(
"attempting to add Channels containing differing numbers of "
"Samples")
for s1, s2 in zip(samples1, samples2):
# samples must be compatible
channel.AddSample(s1 + s2)
channel.SetStatErrorConfig(self.GetStatErrorConfig())
return channel
def __radd__(self, other):
# support sum([list of Channels])
if other == 0:
return self
raise TypeError(
"unsupported operand type(s) for +: '{0}' and '{1}'".format(
other.__class__.__name__, self.__class__.__name__))
[docs] def sys_names(self):
"""
Return a list of unique systematic names from OverallSys and HistoSys
"""
names = []
for sample in self.samples:
names.extend(sample.sys_names())
return list(set(names))
[docs] def sys_hist(self, name=None, where=None):
"""
Return the effective total low and high histogram for a given
systematic over samples in this channel.
If a sample does not contain the named systematic then its nominal
histogram is used for both low and high variations.
Parameters
----------
name : string, optional (default=None)
The systematic name otherwise nominal if None
where : callable, optional (default=None)
A callable taking one argument: the sample, and returns True if
this sample should be included in the total.
Returns
-------
total_low, total_high : histograms
The total low and high histograms for this systematic
"""
total_low, total_high = None, None
for sample in self.samples:
if where is not None and not where(sample):
continue
low, high = sample.sys_hist(name)
if total_low is None:
total_low = low.Clone(shallow=True)
else:
total_low += low
if total_high is None:
total_high = high.Clone(shallow=True)
else:
total_high += high
return total_low, total_high
def has_sample(self, name):
for sample in self.samples:
if sample.name == name:
return True
return False
def has_sample_where(self, func):
for sample in self.samples:
if func(sample):
return True
return False
[docs] def total(self, where=None, xbin1=1, xbin2=-2):
"""
Return the total yield and its associated statistical and
systematic uncertainties.
"""
nominal, _ = self.sys_hist(None, where=where)
integral, stat_error = nominal.integral(
xbin1=xbin1, xbin2=xbin2, error=True)
ups = [0]
dns = [0]
for sys_name in self.sys_names():
low, high = self.sys_hist(sys_name, where=where)
up = high.integral(xbin1=xbin1, xbin2=xbin2) - integral
dn = low.integral(xbin1=xbin1, xbin2=xbin2) - integral
if up > 0:
ups.append(up**2)
else:
dns.append(up**2)
if dn > 0:
ups.append(dn**2)
else:
dns.append(dn**2)
syst_error = (sqrt(sum(ups)), sqrt(sum(dns)))
return integral, stat_error, syst_error
def SetData(self, data):
super(Channel, self).SetData(data)
if isinstance(data, ROOT.TH1):
keepalive(self, data)
def GetData(self):
return asrootpy(super(Channel, self).GetData())
@property
def data(self):
return self.GetData()
@data.setter
def data(self, d):
self.SetData(d)
def AddSample(self, sample):
super(Channel, self).AddSample(sample)
keepalive(self, sample)
def RemoveSample(self, name):
sample_vect = super(Channel, self).GetSamples()
ivect = sample_vect.begin()
for sample in sample_vect:
if sample.GetName() == name:
sample_vect.erase(ivect)
break
ivect.__preinc__()
def GetSample(self, name):
samples = super(Channel, self).GetSamples()
for sample in samples:
if sample.GetName() == name:
return asrootpy(sample)
return None
def GetSamples(self):
return [asrootpy(s) for s in super(Channel, self).GetSamples()]
def AddAdditionalData(self, data):
super(Channel, self).AddAdditionalData(data)
keepalive(self, data)
def GetAdditionalData(self):
return [asrootpy(d) for d in super(Channel, self).GetAdditionalData()]
@property
def samples(self):
return self.GetSamples()
@property
def additional_data(self):
return self.GetAdditionalData()
@property
def hist_path(self):
return self.GetHistoPath()
@hist_path.setter
def hist_path(self, path):
self.SetHistoPath(path)
@property
def hist_file(self):
return self.GetInputFile()
@hist_file.setter
def hist_file(self, infile):
self.SetInputFile(infile)
[docs] def apply_snapshot(self, argset):
"""
Create a clone of this Channel where histograms are modified according
to the values of the nuisance parameters in the snapshot. This is
useful when creating post-fit distribution plots.
Parameters
----------
argset : RooArtSet
A RooArgSet of RooRealVar nuisance parameters
Returns
-------
channel : Channel
The modified channel
"""
clone = self.Clone()
args = [var for var in argset if not (
var.name.startswith('binWidth_obs_x_') or
var.name.startswith('gamma_stat') or
var.name.startswith('nom_'))]
# handle NormFactors first
nargs = []
for var in args:
is_norm = False
name = var.name.replace('alpha_', '')
for sample in clone.samples:
if sample.GetNormFactor(name) is not None:
log.info("applying snapshot of {0} on sample {1}".format(
name, sample.name))
is_norm = True
# scale the entire sample
sample *= var.value
# add an OverallSys for the error
osys = OverallSys(name,
low=1. - var.error / var.value,
high=1. + var.error / var.value)
sample.AddOverallSys(osys)
# remove the NormFactor
sample.RemoveNormFactor(name)
if not is_norm:
nargs.append(var)
# modify the nominal shape and systematics
for sample in clone.samples:
# check that hist is not NULL
if sample.hist is None:
raise RuntimeError(
"sample {0} does not have a "
"nominal histogram".format(sample.name))
nominal = sample.hist.Clone(shallow=True)
for var in nargs:
name = var.name.replace('alpha_', '')
if not sample.has_sys(name):
continue
log.info("applying snapshot of {0} on sample {1}".format(
name, sample.name))
low, high = sample.sys_hist(name)
# modify nominal
val = var.value
if val > 0:
sample.hist += (high - nominal) * val
elif val < 0:
sample.hist += (nominal - low) * val
# TODO:
# modify OverallSys
# modify HistoSys
return clone
def Clone(self):
clone = Channel(self.name)
data = self.data
if data:
clone.data = data.Clone()
for sample in self.samples:
clone.AddSample(sample.Clone())
clone.hist_path = self.hist_path
clone.hist_file = self.hist_file
return clone
def __iter__(self):
for sample in super(Channel, self).GetSamples():
yield asrootpy(sample)
def __len__(self):
return len(super(Channel, self).GetSamples())
[docs]class Measurement(NamedObject, HistFactory.Measurement):
_ROOT = HistFactory.Measurement
def __init__(self, name, channels=None, title=""):
# require a name
super(Measurement, self).__init__(name=name, title=title)
self.SetExportOnly(True)
if channels is not None:
for channel in channels:
self.AddChannel(channel)
@property
def lumi(self):
return self.GetLumi()
@lumi.setter
def lumi(self, l):
self.SetLumi(l)
@property
def lumi_rel_error(self):
return self.GetLumiRelErr()
@lumi_rel_error.setter
def lumi_rel_error(self, err):
self.SetLumiRelErr(err)
@property
def poi(self):
return list(self.GetPOIList())
@poi.setter
def poi(self, p):
# this also adds a new POI so calling this multiple times will add
# multiple POIs
self.SetPOI(p)
def AddChannel(self, channel):
super(Measurement, self).AddChannel(channel)
keepalive(self, channel)
def RemoveChannel(self, name):
channel_vect = super(Measurement, self).GetChannels()
ivect = channel_vect.begin()
for channel in channel_vect:
if channel.GetName() == name:
channel_vect.erase(ivect)
break
ivect.__preinc__()
def GetChannel(self, name):
channels = super(Measurement, self).GetChannels()
for channel in channels:
if channel.GetName() == name:
return asrootpy(channel)
return None
def GetChannels(self):
return [asrootpy(c) for c in super(Measurement, self).GetChannels()]
@property
def channels(self):
return self.GetChannels()
def GetConstantParams(self):
return list(super(Measurement, self).GetConstantParams())
@property
def const_params(self):
return self.GetConstantParams()
def Clone(self):
clone = Measurement(self.name, self.title)
clone.lumi = self.lumi
clone.lumi_rel_error = self.lumi_rel_error
for channel in self.channels:
clone.AddChannel(channel.Clone())
for poi in self.GetPOIList():
clone.AddPOI(poi)
for const_param in self.const_params:
clone.AddConstantParam(const_param)
return clone
def __iter__(self):
for channel in super(Measurement, self).GetChannels():
yield asrootpy(channel)
def __len__(self):
return len(super(Measurement, self).GetChannels())