def get_data(zchain=getChains('v11')['z']): 'Get the nominal data that is used for smearing.' ## The TFormula expression defining the data is given in the titles. weight.SetTitle('pileup.weight') phoERes.SetTitle('100 * phoERes') mmgMassPhoGenE.SetTitle('threeBodyMass(mu1Pt, mu1Eta, mu1Phi, 0.106, ' ' mu2Pt, mu2Eta, mu2Phi, 0.106, ' ' phoGenE * phoPt / phoE, ' ' phoEta, phoPhi, 0)') ## Create a preselected tree tree = zchain.CopyTree('&'.join(cuts)) ## Have to copy aliases by hand for a in zchain.GetListOfAliases(): tree.SetAlias(a.GetName(), a.GetTitle()) ## Get the nominal dataset global data data = dataset.get(tree=tree, weight=weight, cuts=cuts, variables=[mmgMass, mmMass, phoERes, mmgMassPhoGenE]) ## Set units and nice titles for x, t, u in zip([mmgMass, mmgMassPhoGenE, mmMass, phoERes], [ 'reconstructed m_{#mu#mu#gamma}', 'reconstructed m_{#mu#mu#gamma} with E_{gen}^{#gamma}', 'm_{#mu#mu}', 'E_{reco}^{#gamma}/E_{gen}^{#gamma} - 1', ], 'GeV GeV GeV %'.split()): x.SetTitle(t) x.setUnit(u) ##-- Get Smeared Data ------------------------------------------------------ global calibrator calibrator = MonteCarloCalibrator(data)
def get_data(zchain=getChains('v11')['z']): 'Get the nominal data that is used for smearing.' global data, calibrator ## The TFormula expression defining the data is given in the titles. weight.SetTitle('pileup.weight') phoERes.SetTitle('100 * phoERes') ## Create a preselected tree tree = zchain.CopyTree('&'.join(cuts)) ## Have to copy aliases by hand for a in zchain.GetListOfAliases(): tree.SetAlias(a.GetName(), a.GetTitle()) ## Get the nominal dataset data = dataset.get(tree=tree, weight=weight, cuts=cuts, variables=[ mmgMass, mmMass, phoERes, ]) ## Set units and nice titles for x, t, u in zip([mmgMass, mmMass, phoERes], [ 'm_{#mu#mu#gamma}', 'm_{#mu#mu}', 'E_{reco}^{#gamma}/E_{gen}^{#gamma} - 1', ], 'GeV GeV %'.split()): x.SetTitle(t) x.setUnit(u) ##-- Get Smeared Data ------------------------------------------------------ ## Enlarge the range of the observable to get vanishing tails. # range_save = (phoERes.getMin(), phoERes.getMax()) # phoERes.setRange(-90, 150) calibrator = MonteCarloCalibrator(data) # phoERes.setRange(*range_save) ##-- Set the nominal energy scale and resolution targets ------------------- calibrator.w.loadSnapshot('sr0_mctruth') for i, (s, r) in enumerate(zip(stargets, rtargets)): if s == 'nominal': stargets[i] = calibrator.s0.getVal() if r == 'nominal': rtargets[i] = calibrator.r0.getVal()
def get_data(zchain=getChains('v11')['z']): 'Get the nominal data that is used for smearing.' ## The TFormula expression defining the data is given in the titles. weight.SetTitle('pileup.weight') phoERes.SetTitle('100 * phoERes') ## Create a preselected tree tree = zchain.CopyTree('&'.join(cuts)) ## Have to copy aliases by hand for a in zchain.GetListOfAliases(): tree.SetAlias(a.GetName(), a.GetTitle()) ## Get the nominal dataset global data data = dataset.get(tree=tree, weight=weight, cuts=cuts, variables=[ mmgMass, mmMass, phoERes, ]) ## Set units and nice titles for x, t, u in zip([mmgMass, mmMass, phoERes, phoRes, phoScale], [ 'm_{#mu#mu#gamma}', 'm_{#mu#mu}', 'E_{reco}^{#gamma}/E_{gen}^{#gamma} - 1', 'photon energy resolution', 'photon energy scale', ], 'GeV GeV %'.split()): x.SetTitle(t) x.setUnit(u) ##-- Get Smeared Data ------------------------------------------------------ ## Enlarge the range of the observable to get vanishing tails. range_save = (phoERes.getMin(), phoERes.getMax()) phoERes.setRange(-90, 150) global calibrator calibrator = MonteCarloCalibrator(data) phoERes.setRange(*range_save)
def __init__(self, name, title, mass, phos, phor, data, workspace, phostarget, phortargets, rho=1.5, mirror=ROOT.RooKeysPdf.NoMirror, mrangetrain=(40,140), mrangenorm=(50,130), mrangefit=(60,120)): '''PhosphorModel4(str name, str title, RooRealVar mass, RooRealVar phos, RooRealVar phor, RooDataSet data, float phostarget, [float] phortargets, float rho=1.5, int mirror=ROOT.RooKeysPdf.NoMirror) name - PDF name title - PDF title mass - mumugamma invariant mass (GeV), observable phos - photon energy scale (%), parameter phor - photon energy resolution (%), paramter data - (mmMass, mmgMass, phoERes = 100*(phoEreco/phoEgen - 1), dataset on which the shapes of reference PDFs are trained. phostarget - target reference value of the photon energy scale at which the model is being trained phortargetss - a list of target photon energy resolution values for the moment morphing rho - passed to trained RooKeysPdfs mirror - passed to trained RooKeysPdfs ''' ## Attach args. self._name = name self._title = title self._mass = mass self._phos = phos self._phor = phor self._data = data self._phostarget = phostarget self._phortargets = phortargets[:] self._rho = rho self._mirror = mirror ## Attach other attributes. self._massrange = (mass.getMin(), mass.getMax()) self._sdata_list = [] self._phostrue_list = [] self._phortrue_list = [] self._dlogm_dphos_list = [] self._msubs_list = [] self._keys_pdfs = [] self._keys_modes = [] self._keys_effsigmas = [] self._keys_fitresults = [] self._pdfs = [] self._custs = [] self._pdfrefs = [] self._mrefs = [] self._workspace = workspace ## self._workspace = ROOT.RooWorkspace(name + '_workspace', ## title + ' workspace') w = self._workspace self.w = w ## Import important args in workspace. # w.Import(ROOT.RooArgSet(mass, phos, phor)) w.Import(mass) w.Import(phos) w.Import(phor) w.Import(self._data) w.factory( 'ConstVar::{name}_phostarget({value})'.format( name=name, value=self._phostarget ) ) ## Define the morphing parameter. This an identity with the ## photon resolution for now. mpar = self._mpar = w.factory( 'expr::{name}_mpar("{phor}", {{{phor}}})'.format( ## 'expr::{name}_mpar("sqrt(3^2 + {phor}^2)", {{{phor}}})'.format( ## 'expr::{name}_mpar("2.325+sqrt(0.4571 + (0.1608*{phor})^2)", {{{phor}}})'.format( name=name, phor=phor.GetName() ) ) ## Define the formula for dlog(m)/dphos. self._dlogm_dphos_func = w.factory(''' expr::dlogm_dphos_func("0.5 * (1 - mmMass^2 / mmgMass^2) * mmgMass", {mmMass, mmgMass}) ''') ## Get the calibrator. self._calibrator = MonteCarloCalibrator(self._data) ## Loop over target reference photon energy resolutions in phortargets. for index, phortarget in enumerate(self._phortargets): ## Store the target photon resolution value. w.factory( 'ConstVar::{name}_phortarget_{index}({value})'.format( name=name, index=index, value=phortarget ) ) ## Get the corresponding smeared RooDataSet sdata named ## {name}_sdata_{index} and attach it to self._sdata sdata = self._calibrator.get_smeared_data( self._phostarget, phortarget, name + '_sdata_%d' % index, title + ' sdata %d' % index, ## Get the true scale and resolution with errors. (This can be ## added to the calibrator and snapshots of ## self._calibrator.s and self._calibrator.r stored as ## {name}_sdata_{index}_sr in self._calibrator.w) dofit=True ) self._sdata_list.append(sdata) w.Import(sdata) phostrue = ROOT.RooRealVar(self._calibrator.s, name + '_phostrue_%d' % index) phortrue = ROOT.RooRealVar(self._calibrator.r, name + '_phortrue_%d' % index) phostrue.setConstant(True) phortrue.setConstant(True) self._phostrue_list.append(phostrue) self._phortrue_list.append(phortrue) w.Import(phostrue) w.Import(phortrue) ## Calculate the dlogm/dphos {name}_dlogm_dphos_{index} ## for the smeared dataset. sdata.addColumn(self._dlogm_dphos_func) dlogm_dphos = w.factory( ''' {name}_dlogm_dphos_{index}[{mean}, 0, 1] '''.format(name=name, index=index, mean=sdata.mean(sdata.get()['dlogm_dphos_func'])) ) dlogm_dphos.setConstant(True) self._dlogm_dphos_list.append(dlogm_dphos) ## Define the mass scaling {name}_msubs{i} introducing ## the dependence on phos. This needs self._calibrator.s and ## dlogm_dphos. ## msubs = w.factory( ## ''' ## cexpr::{msubs}( ## "{mass}*(1 - 0.01 * {dlogm_dphos} * ({phos} - {phostrue}))", ## {{ {mass}, {dlogm_dphos}, {phos}, {phostrue} }} ## ) ## '''.format(msubs = name + '_msubs_%d' % index, ## mass = self._mass.GetName(), ## dlogm_dphos = dlogm_dphos.GetName(), ## phos = self._phos.GetName(), ## phostrue = phostrue.GetName()) ## ) ## msubs = w.factory( ## ''' ## LinearVar::{msubs}( ## {mass}, ## expr::{slope}( ## "(1 - 0.01 * {dlogm_dphos} * ({phos} - {phostrue}))", ## {{ {dlogm_dphos}, {phos}, {phostrue} }} ## ), ## 0 ## ) ## '''.format(msubs = name + '_msubs_%d' % index, ## slope = name + '_msubs_slope_%d' % index, ## mass = self._mass.GetName(), ## dlogm_dphos = dlogm_dphos.GetName(), ## phos = self._phos.GetName(), ## phostrue = phostrue.GetName()) ## ) msubs = w.factory( ''' LinearVar::{msubs}( {mass}, 1, expr::{offset}( "- 0.01 * {dlogm_dphos} * ({phos} - {phostrue})", {{ {dlogm_dphos}, {phos}, {phostrue} }} ) ) '''.format(msubs = name + '_msubs_%d' % index, offset = name + '_msubs_offset_%d' % index, mass = self._mass.GetName(), dlogm_dphos = dlogm_dphos.GetName(), phos = self._phos.GetName(), phostrue = phostrue.GetName()) ) self._msubs_list.append(msubs) ## Build the corresponding parametrized KEYS PDF {name}_kyes_{index} ## with {name}_keys_mode_{index} and ## {name}_keys_effsigma_{index}. keys_mode = w.factory( '{name}_keys_mode_{index}[91.2, 60, 120]'.format( name=name, index=index ) ) keys_effsigma = w.factory( '{name}_keys_effsigma_{index}[3, 0.1, 60]'.format( name=name, index=index ) ) mass.setRange(*mrangetrain) keys_pdf = ParametrizedKeysPdf(name + '_keys_pdf_%d' % index, name + '_keys_pdf_%d' % index, mass, keys_mode, keys_effsigma, sdata) self._keys_modes.append(keys_mode) self._keys_effsigmas.append(keys_effsigma) self._keys_pdfs.append(keys_pdf) ## Fit the KEYS PDF to the training data and save the result ## {name}_keys_fitresult_{index} and parameter snapshots ## {name}_keys_mctrue_{index}. mass.setRange(*mrangenorm) keys_fitresult = keys_pdf.fitTo(sdata, roo.Range(*mrangefit), roo.Strategy(2), roo.NumCPU(8), roo.Save(True)) self._keys_fitresults.append(keys_fitresult) w.Import(keys_fitresult, name + '_keys_fitresult_%d' % index) w.saveSnapshot(name + '_mctrue_%d' % index, ','.join([phostrue.GetName(), phortrue.GetName(), keys_mode.GetName(), keys_effsigma.GetName()])) ## Sample the fitted KEYS PDF to a histogram {name}_hist_{index}. mass.setRange(*mrangetrain) hist = keys_pdf.createHistogram(name + '_hist_%d' % index, mass, roo.Binning(1000)) ## Build a RooDataHist {name}_dhist{index} of the sampled histogram. dhist = ROOT.RooDataHist(name + '_dhist_%d' % index, name + '_dhist_%d' % index, ROOT.RooArgList(mass), hist) w.Import(dhist) ## Build a RooHistPdf {name}_pdf_{index} using the dhist and msubs. ## pdf = w.factory( ## 'HistPdf::{name}({{{msubs}}}, {{{mass}}}, {dhist}, 1)'.format( ## name = name + '_pdf_%d' % index, msubs = msubs.GetName(), ## mass = mass.GetName(), dhist = dhist.GetName() ## ) ## ) pdf = w.factory( 'HistPdf::{name}({{{mass}}}, {dhist}, 1)'.format( name = name + '_pdf_%d' % index, msubs = msubs.GetName(), mass = mass.GetName(), dhist = dhist.GetName() ) ) self._pdfs.append(pdf) mass.setRange(*self._massrange) ## Supstitute for mass using customizer. cust = ROOT.RooCustomizer(pdf, 'msubs_%d' % index) self._custs.append(cust) cust.replaceArg(mass, msubs) pdfref = cust.build() pdfref.addOwnedComponents(ROOT.RooArgSet(msubs)) pdfref.SetName(name + '_pdfref_%d' % index) pdfref.SetTitle(name + '_pdfref_%d' % index) w.Import(pdfref) self._pdfrefs.append(pdfref) ## Calculate morphing parameter reference values float mref[index]. phorval = phor.getVal() phor.setVal(phortrue.getVal()) self._mrefs.append(mpar.getVal()) phor.setVal(phorval) ## End of loop over target phortargets ## Define the RooMomentMorph model. model = w.factory( ''' MomentMorph::{name}({mpar}, {{{mass}}}, {{{pdfs}}}, {{{mrefs}}}) '''.format(name=name, mpar=mpar.GetName(), mass=mass.GetName(), pdfs=','.join([f.GetName() for f in self._pdfs]), # pdfs=','.join([f.GetName() for f in self._pdfrefs]), mrefs=','.join([str(m) for m in self._mrefs])) ) ## Quick hack to make things work. cust = ROOT.RooCustomizer(model, 'msub') cust.replaceArg(mass, self._msubs_list[0]) model2 = cust.build() ROOT.RooMomentMorph.__init__(self, model2) self.SetName(name) self.SetTitle(title)
def get_data(chains = getChains('v11')): ''' Get the nominal data that is used for smearing. ''' global cuts if Globals.debug: print '====== CUTS INSIDE phosphorcalculator ======', cuts ## TODO: Break this down into several smaller methods. ## Map of variable names and corresponding TTree expressions to ## calculate it. expression_map = { 'mmgMass': 'mmgMass', 'mmMass' : 'mmMass' , 'phoERes' : '100 * phoERes', 'mmgMassPhoGenE': ('threeBodyMass(mu1Pt, mu1Eta, mu1Phi, 0.106, ' ' mu2Pt, mu2Eta, mu2Phi, 0.106, ' ' phoGenE * phoPt / phoE, ' ' phoEta, phoPhi, 0)'), 'weight' : 'pileup.weight', } ## The TFormula expression defining the data is given in the titles. # print '+++ DEBUG: before title replacement:', mmgMass.GetTitle() latex_map = replace_variable_titles(expression_map, w) # print '+++ DEBUG: after title replacement:', mmgMass.GetTitle() #global cuts ## Create a preselected tree tree = {} tree['z'] = chains['z'].CopyTree('&'.join(cuts)) # dtree = chains['data']. ## Have to copy aliases by hand for a in chains['z'].GetListOfAliases(): tree['z'].SetAlias(a.GetName(), a.GetTitle()) cuts0 = cuts[:] cuts1 = cuts[:] if use_independent_fake_data: cuts0.append('!(%s)' % fake_data_cut) cuts1.append(fake_data_cut) ## Get the nominal dataset global data data = {} for xvar in [weight, mmgMass, mmMass, phoERes, mmgMassPhoGenE]: print xvar.GetName(), ':', xvar.GetTitle() data['fsr0'] = dataset.get(tree=tree['z'], weight=weight, cuts = cuts0 + ['isFSR'], variables=[mmgMass, mmMass, phoERes, mmgMassPhoGenE]) data['fsr1'] = dataset.get(tree=tree['z'], weight=weight, cuts=cuts1 + ['isFSR'], variables=[mmgMass, mmMass, phoERes, mmgMassPhoGenE]) data['zj0'] = dataset.get(tree=tree['z'], weight=weight, cuts=cuts0 + ['!isFSR'], variables=[mmgMass, mmMass]) data['zj1'] = dataset.get(tree=tree['z'], weight=weight, cuts=cuts1 + ['!isFSR'], variables=[mmgMass, mmMass]) ## Set units and nice titles replace_variable_titles(latex_map, w) ## Do we want to reduce the data? if reduce_data: reduced_entries = int( (1 - fit_data_fraction) * data['fsr0'].numEntries() ) data['fsr0'] = data['fsr0'].reduce( roo.EventRange(0, int(reduced_entries)) ) data['zj0'].SetName('zj0_mc') w.Import(data['zj0']) ##-- Calculate MC Truth Purity --------------------------------------------- if use_independent_fake_data: num_fsr_events = data['fsr1'].sumEntries() num_zj_events = data['zj1'].sumEntries() else: num_fsr_events = data['fsr0'].sumEntries() num_zj_events = data['zj0'].sumEntries() global fsr_purity fsr_purity = 100 * num_fsr_events / (num_fsr_events + num_zj_events) ##-- Get Smeared Data ------------------------------------------------------ old_precision = set_default_integrator_precision(2e-9, 2e-9) global calibrator0, calibrator1, fit_calibrator calibrator0 = MonteCarloCalibrator(data['fsr0'], printlevel=1, rho=1.5) if use_independent_fake_data: calibrator1 = MonteCarloCalibrator(data['fsr1'], printlevel=1, rho=1.5) fit_calibrator = calibrator1 else: fit_calibrator = calibrator0 set_default_integrator_precision(*old_precision) ##-- Check the time ------------------------------------------------------- check_timer( '1. init and get_data (%d entries)' % ( data['fsr0'].numEntries() + data['fsr1'].numEntries() + data['zj0'].numEntries() + data['zj1'].numEntries() ) )
class PhosphorModel5(ROOT.RooPhosphorPdf): ##-------------------------------------------------------------------------- def __init__(self, name, title, mass, phos, phor, data, workspace, phostarget, phortargets, rho=1.5, mirror=ROOT.RooKeysPdf.NoMirror, mrangetrain=(40,140), mrangenorm=(50,130), mrangefit=(60,120)): '''PhosphorModel4(str name, str title, RooRealVar mass, RooRealVar phos, RooRealVar phor, RooDataSet data, float phostarget, [float] phortargets, float rho=1.5, int mirror=ROOT.RooKeysPdf.NoMirror) name - PDF name title - PDF title mass - mumugamma invariant mass (GeV), observable phos - photon energy scale (%), parameter phor - photon energy resolution (%), paramter data - (mmMass, mmgMass, phoERes = 100*(phoEreco/phoEgen - 1), dataset on which the shapes of reference PDFs are trained. phostarget - target reference value of the photon energy scale at which the model is being trained phortargetss - a list of target photon energy resolution values for the moment morphing rho - passed to trained RooKeysPdfs mirror - passed to trained RooKeysPdfs ''' ## Attach args. self._name = name self._title = title self._mass = mass self._phos = phos self._phor = phor self._data = data self._phostarget = phostarget self._phortargets = phortargets[:] self._rho = rho self._mirror = mirror ## Attach other attributes. self._massrange = (mass.getMin(), mass.getMax()) self._sdata_list = [] self._phostrue_list = [] self._phortrue_list = [] self._dm_dphos_list = [] self._msubs_list = [] self._keys_pdfs = [] self._keys_modes = [] self._keys_effsigmas = [] self._keys_fitresults = [] self._pdfs = [] self._custs = [] # self._pdfrefs = [] self._mrefs = [] self._phormorphs = [] self._phorhists = [] self._workspace = workspace ## self._workspace = ROOT.RooWorkspace(name + '_workspace', ## title + ' workspace') w = self._workspace self.w = w ## Import important args in workspace. # w.Import(ROOT.RooArgSet(mass, phos, phor)) w.Import(mass) w.Import(phos) w.Import(phor) w.Import(self._data) w.factory( 'ConstVar::{name}_phostarget({value})'.format( name=name, value=self._phostarget ) ) ## Define the morphing parameter. This an identity with the ## photon resolution for now. mpar = self._mpar = w.factory( 'expr::{name}_mpar("{phor}", {{{phor}}})'.format( ## 'expr::{name}_mpar("2 + sqrt(0.5^2 + 0.05 * {phor}^2)", {{{phor}}})'.format( ## 'expr::{name}_mpar("2.325+sqrt(0.4571 + (0.1608*{phor})^2)", {{{phor}}})'.format( name=name, phor=phor.GetName() ) ) ## Define the formula for dlog(m)/dphos. self._dm_dphos_func = w.factory(''' expr::dm_dphos_func("0.5 * (1 - mmMass^2 / mmgMass^2) * mmgMass", {mmMass, mmgMass}) ''') ## Get the calibrator. self._calibrator = MonteCarloCalibrator(self._data) ## Loop over target reference photon energy resolutions in phortargets. for index, phortarget in enumerate(self._phortargets): ## Store the target photon resolution value. w.factory( 'ConstVar::{name}_phortarget_{index}({value})'.format( name=name, index=index, value=phortarget ) ) ## Get the corresponding smeared RooDataSet sdata named ## {name}_sdata_{index} and attach it to self._sdata sdata = self._calibrator.get_smeared_data( self._phostarget, phortarget, name + '_sdata_%d' % index, title + ' sdata %d' % index, ## Get the true scale and resolution with errors. (This can be ## added to the calibrator and snapshots of ## self._calibrator.s and self._calibrator.r stored as ## {name}_sdata_{index}_sr in self._calibrator.w) dofit=True ) self._sdata_list.append(sdata) w.Import(sdata) phostrue = ROOT.RooRealVar(self._calibrator.s, name + '_phostrue_%d' % index) phortrue = ROOT.RooRealVar(self._calibrator.r, name + '_phortrue_%d' % index) phostrue.setConstant(True) phortrue.setConstant(True) self._phostrue_list.append(phostrue) self._phortrue_list.append(phortrue) w.Import(phostrue) w.Import(phortrue) ## Calculate the dlogm/dphos {name}_dm_dphos_{index} ## for the smeared dataset. sdata.addColumn(self._dm_dphos_func) dm_dphos = w.factory( ''' {name}_dm_dphos_{index}[{mean}, 0, 1] '''.format(name=name, index=index, mean=sdata.mean(sdata.get()['dm_dphos_func'])) ) dm_dphos.setConstant(True) self._dm_dphos_list.append(dm_dphos) ## Define the mass scaling {name}_msubs{i} introducing ## the dependence on phos. This needs self._calibrator.s and ## dm_dphos. ## msubs = w.factory( ## ''' ## LinearVar::{msubs}( ## {mass}, 1, ## expr::{offset}( ## "- 0.01 * {dm_dphos} * ({phos} - {phostrue})", ## {{ {dm_dphos}, {phos}, {phostrue} }} ## ) ## ) ## '''.format(msubs = name + '_msubs_%d' % index, ## offset = name + '_msubs_offset_%d' % index, ## mass = self._mass.GetName(), ## dm_dphos = dm_dphos.GetName(), ## phos = self._phos.GetName(), ## phostrue = phostrue.GetName()) ## ) ## LinearVar cannot be persisted. msubs = w.factory( ''' expr::{msubs}( "{mass} - 0.01 * {dm_dphos} * ({phos} - {phostrue})", {{ {mass}, {dm_dphos}, {phos}, {phostrue} }} ) '''.format(msubs = name + '_msubs_%d' % index, mass = self._mass.GetName(), dm_dphos = dm_dphos.GetName(), phos = self._phos.GetName(), phostrue = phostrue.GetName()) ) self._msubs_list.append(msubs) ## Build the corresponding parametrized KEYS PDF {name}_kyes_{index} ## with {name}_keys_mode_{index} and ## {name}_keys_effsigma_{index}. keys_mode = w.factory( '{name}_keys_mode_{index}[91.2, 60, 120]'.format( name=name, index=index ) ) keys_effsigma = w.factory( '{name}_keys_effsigma_{index}[3, 0.1, 60]'.format( name=name, index=index ) ) mass.setRange(*mrangetrain) keys_pdf = ParametrizedKeysPdf(name + '_keys_pdf_%d' % index, name + '_keys_pdf_%d' % index, mass, keys_mode, keys_effsigma, sdata, rho=self._rho) self._keys_modes.append(keys_mode) self._keys_effsigmas.append(keys_effsigma) self._keys_pdfs.append(keys_pdf) ## Fit the KEYS PDF to the training data and save the result ## {name}_keys_fitresult_{index} and parameter snapshots ## {name}_keys_mctrue_{index}. mass.setRange(*mrangenorm) keys_fitresult = keys_pdf.fitTo(sdata, roo.Range(*mrangefit), roo.Strategy(2), roo.NumCPU(8), roo.Save(True)) self._keys_fitresults.append(keys_fitresult) w.Import(keys_fitresult, name + '_keys_fitresult_%d' % index) w.saveSnapshot(name + '_mctrue_%d' % index, ','.join([phostrue.GetName(), phortrue.GetName(), keys_mode.GetName(), keys_effsigma.GetName()])) ## Sample the fitted KEYS PDF to a histogram {name}_hist_{index}. mass.setRange(*mrangetrain) hist = keys_pdf.createHistogram(name + '_hist_%d' % index, mass, roo.Binning('cache')) ## Build a RooDataHist {name}_dhist{index} of the sampled histogram. dhist = ROOT.RooDataHist(name + '_dhist_%d' % index, name + '_dhist_%d' % index, ROOT.RooArgList(mass), hist) w.Import(dhist) ## Build a RooHistPdf {name}_pdf_{index} using the dhist and msubs. ## pdf = w.factory( pdf = w.factory( 'HistPdf::{name}({{{mass}}}, {dhist}, 1)'.format( name = name + '_pdf_%d' % index, msubs = msubs.GetName(), mass = mass.GetName(), dhist = dhist.GetName() ) ) self._pdfs.append(pdf) mass.setRange(*self._massrange) ## Calculate morphing parameter reference values float mref[index]. phorval = phor.getVal() phor.setVal(phortrue.getVal()) self._mrefs.append(mpar.getVal()) phor.setVal(phorval) ## End of loop over target phortargets ## Make the reference and cache binnings in phor self._check_phor_ranges() partitions = self._partition_binning(self._phor, 'cache', 'reference') ## Loop over phor reference bins and define pairwise RooMomentMorphs. for ilo in range(len(self._mrefs) - 1): ihi = ilo + 1 mlo = self._mrefs[ilo] mhi = self._mrefs[ihi] pdflo = self._pdfs[ilo] pdfhi = self._pdfs[ihi] phormorph = w.factory( ''' MomentMorph::{name}_phormorph_{ilo}to{ihi}( {mpar}, {{{mass}}}, {{{pdfs}}}, {{{mrefs}}} ) '''.format(name=name, ilo=ilo, ihi=ihi, mpar=mpar.GetName(), mass=mass.GetName(), pdfs = '%s, %s' % (pdflo.GetName(), pdfhi.GetName()), mrefs = '%f, %f' % (mlo, mhi)) ) self._phormorphs.append(phormorph) ## Sample the morph in a 2D histogram in mass and phor. phorhist = phormorph.createHistogram( name + '_phorhist_%dto%d' % (ilo, ihi), mass, roo.Binning('cache'), roo.YVar(phor, roo.Binning(partitions[ilo])) ) self._phorhists.append(phorhist) ## End of loop over phor reference bins. self._stitch_phorhists() self._phor_dhist = phor_dhist = ROOT.RooDataHist( name + '_phor_dhist', name + '_phor_dhist', ROOT.RooArgList(mass, phor), self._phorhist ) average_index = (len(self._msubs_list) + 1) / 2 msubs = self._msubs_list[average_index] ## self._model = model = ROOT.RooHistPdf( ## name + '_phor_histpdf', name + '_phor_histpdf', ## ROOT.RooArgList(msubs, phor), ROOT.RooArgList(mass, phor), phor_dhist, 2 ## ) ## self._model = model = ROOT.RooPhosphorPdf( ## name + '_phor_histpdf', name + '_phor_histpdf', mass, msubs, phor, phor_dhist, 2 ## ) ## ## Quick hack to make things work. ## self._customizer = customizer = ROOT.RooCustomizer(model, 'msub') ## customizer.replaceArg(mass, msubs) ## model = customizer.build() # ROOT.RooHistPdf.__init__(self, model) ROOT.RooPhosphorPdf.__init__(self, name, title, mass, msubs, phos, phor, phor_dhist, 2) self.SetName(name) self.SetTitle(title) ## End of __init__() ##-------------------------------------------------------------------------- def make_mctrue_graph(self, xname='phor', yname='keys_effsigma'): vardict = {'phos': self._phostrue_list, 'phor': self._phortrue_list, 'keys_mode': self._keys_modes, 'keys_effsigma': self._keys_effsigmas} x = array.array('d', [v.getVal() for v in vardict[xname]]) y = array.array('d', [v.getVal() for v in vardict[yname]]) ex = array.array('d', [v.getError() for v in vardict[xname]]) ey = array.array('d', [v.getError() for v in vardict[yname]]) graph = ROOT.TGraphErrors(len(vardict[xname]), x, y, ex, ey) return graph ## for xvar in ## End of make_mctrue_graph() ##-------------------------------------------------------------------------- def _partition_binning(self, x, fine, coarse): '''partition_binning(RooAbsArg x, str fine, str coarse) fine .. name of the binning of x to be partitioned coarse .. name of the binning of x to define the partitioning ''' fbins = x.getBinning(fine) cbins = x.getBinning(coarse) ## Create empty lists of bins, one for each group. boundaries = [] for cbin in range(cbins.numBins()): boundaries.append([]) ## Loop over the fine bins for fbin in range(fbins.numBins()): groupindex = cbins.binNumber(fbins.binCenter(fbin)) ## Add the low boundary to the right group. boundaries[groupindex].append(fbins.binLow(fbin)) ## Check if the high boundary has to be added. if fbin == fbins.numBins() - 1: ## This is the last bin. Have to add the high boundary to the ## current group. boundaries[groupindex].append(fbins.binHigh(fbin)) continue if fbin == 0: ## This is the first bin. There is no previous bin, ## so that's it. The low edge of this bin will not ## be addede anywhere else. continue if len(boundaries[groupindex]) == 1: ## This is the first bin in this group. Add the boundary also ## to the previous group. boundaries[groupindex-1].append(fbins.binLow(fbin)) ## End of loop over boundaries. names = [] for i, iboundaries in enumerate(boundaries): barray = array.array('d', iboundaries) nbins = len(barray) - 1 name = '%s_%dto%d' % (fine, i, i+1) names.append(name) binning = ROOT.RooBinning(nbins, barray, name) x.setBinning(binning, name) return names ## End of partition_binning(). ##-------------------------------------------------------------------------- def _check_phor_ranges(self): 'Make sure that phor ranges reference and cache are well defined.' if not self._phor.hasBinning('cache'): self._phor.setBins(100, 'cache') boundaries = array.array('d', [x.getVal() for x in self._phortrue_list]) nbins = len(boundaries) - 1 refbins = ROOT.RooBinning(nbins, boundaries, 'reference') self._phor.setBinning(refbins, 'reference') ## End of _check_phor_ranges() ##-------------------------------------------------------------------------- def _stitch_phorhists(self): 'Assemble the phorhists in one histogram with larger phor range.' self._phorhist = ROOT.TH2F(self._name + '_phorhist', self._name + '_phorhist', self._mass.getBinning('cache').numBins(), self._mass.getBinning('cache').array(), self._phor.getBinning('cache').numBins(), self._phor.getBinning('cache').array()) ## Loop over the source histograms. for h in self._phorhists: ## Loop over the x-axis bins. for binx in range(1, h.GetNbinsX() + 1): xcenter = h.GetXaxis().GetBinCenter(binx) ## Loop over the y-axis bins. for biny in range(1, h.GetNbinsY() + 1): ## Set the content of the target histogram for this bin. ycenter = h.GetYaxis().GetBinCenter(biny) targetbin = self._phorhist.FindBin(xcenter, ycenter) self._phorhist.SetBinContent(targetbin, h.GetBinContent(binx, biny))
def __init__(self, name, title, mass, phos, phor, data, workspace, phostarget, phortargets, rho=1.5, mirror=ROOT.RooKeysPdf.NoMirror, mrangetrain=(40, 140), mrangenorm=(50, 130), mrangefit=(60, 120)): '''PhosphorModel4(str name, str title, RooRealVar mass, RooRealVar phos, RooRealVar phor, RooDataSet data, float phostarget, [float] phortargets, float rho=1.5, int mirror=ROOT.RooKeysPdf.NoMirror) name - PDF name title - PDF title mass - mumugamma invariant mass (GeV), observable phos - photon energy scale (%), parameter phor - photon energy resolution (%), paramter data - (mmMass, mmgMass, phoERes = 100*(phoEreco/phoEgen - 1), dataset on which the shapes of reference PDFs are trained. phostarget - target reference value of the photon energy scale at which the model is being trained phortargetss - a list of target photon energy resolution values for the moment morphing rho - passed to trained RooKeysPdfs mirror - passed to trained RooKeysPdfs ''' ## Attach args. self._name = name self._title = title self._mass = mass self._phos = phos self._phor = phor self._data = data self._phostarget = phostarget self._phortargets = phortargets[:] self._rho = rho self._mirror = mirror ## Attach other attributes. self._massrange = (mass.getMin(), mass.getMax()) self._sdata_list = [] self._phostrue_list = [] self._phortrue_list = [] self._dm_dphos_list = [] self._msubs_list = [] self._keys_pdfs = [] self._keys_modes = [] self._keys_effsigmas = [] self._keys_fitresults = [] self._pdfs = [] self._custs = [] # self._pdfrefs = [] self._mrefs = [] self._phormorphs = [] self._phorhists = [] self._workspace = workspace ## self._workspace = ROOT.RooWorkspace(name + '_workspace', ## title + ' workspace') w = self._workspace self.w = w ## Import important args in workspace. # w.Import(ROOT.RooArgSet(mass, phos, phor)) w.Import(mass) w.Import(phos) w.Import(phor) w.Import(self._data) w.factory('ConstVar::{name}_phostarget({value})'.format( name=name, value=self._phostarget)) ## Define the morphing parameter. This an identity with the ## photon resolution for now. mpar = self._mpar = w.factory( 'expr::{name}_mpar("{phor}", {{{phor}}})'.format( ## 'expr::{name}_mpar("2 + sqrt(0.5^2 + 0.05 * {phor}^2)", {{{phor}}})'.format( ## 'expr::{name}_mpar("2.325+sqrt(0.4571 + (0.1608*{phor})^2)", {{{phor}}})'.format( name=name, phor=phor.GetName())) ## Define the formula for dlog(m)/dphos. self._dm_dphos_func = w.factory(''' expr::dm_dphos_func("0.5 * (1 - mmMass^2 / mmgMass^2) * mmgMass", {mmMass, mmgMass}) ''') ## Get the calibrator. self._calibrator = MonteCarloCalibrator(self._data) ## Loop over target reference photon energy resolutions in phortargets. for index, phortarget in enumerate(self._phortargets): ## Store the target photon resolution value. w.factory('ConstVar::{name}_phortarget_{index}({value})'.format( name=name, index=index, value=phortarget)) ## Get the corresponding smeared RooDataSet sdata named ## {name}_sdata_{index} and attach it to self._sdata sdata = self._calibrator.get_smeared_data( self._phostarget, phortarget, name + '_sdata_%d' % index, title + ' sdata %d' % index, ## Get the true scale and resolution with errors. (This can be ## added to the calibrator and snapshots of ## self._calibrator.s and self._calibrator.r stored as ## {name}_sdata_{index}_sr in self._calibrator.w) dofit=True) self._sdata_list.append(sdata) w.Import(sdata) phostrue = ROOT.RooRealVar(self._calibrator.s, name + '_phostrue_%d' % index) phortrue = ROOT.RooRealVar(self._calibrator.r, name + '_phortrue_%d' % index) phostrue.setConstant(True) phortrue.setConstant(True) self._phostrue_list.append(phostrue) self._phortrue_list.append(phortrue) w.Import(phostrue) w.Import(phortrue) ## Calculate the dlogm/dphos {name}_dm_dphos_{index} ## for the smeared dataset. sdata.addColumn(self._dm_dphos_func) dm_dphos = w.factory(''' {name}_dm_dphos_{index}[{mean}, 0, 1] '''.format(name=name, index=index, mean=sdata.mean(sdata.get()['dm_dphos_func']))) dm_dphos.setConstant(True) self._dm_dphos_list.append(dm_dphos) ## Define the mass scaling {name}_msubs{i} introducing ## the dependence on phos. This needs self._calibrator.s and ## dm_dphos. ## msubs = w.factory( ## ''' ## LinearVar::{msubs}( ## {mass}, 1, ## expr::{offset}( ## "- 0.01 * {dm_dphos} * ({phos} - {phostrue})", ## {{ {dm_dphos}, {phos}, {phostrue} }} ## ) ## ) ## '''.format(msubs = name + '_msubs_%d' % index, ## offset = name + '_msubs_offset_%d' % index, ## mass = self._mass.GetName(), ## dm_dphos = dm_dphos.GetName(), ## phos = self._phos.GetName(), ## phostrue = phostrue.GetName()) ## ) ## LinearVar cannot be persisted. msubs = w.factory(''' expr::{msubs}( "{mass} - 0.01 * {dm_dphos} * ({phos} - {phostrue})", {{ {mass}, {dm_dphos}, {phos}, {phostrue} }} ) '''.format(msubs=name + '_msubs_%d' % index, mass=self._mass.GetName(), dm_dphos=dm_dphos.GetName(), phos=self._phos.GetName(), phostrue=phostrue.GetName())) self._msubs_list.append(msubs) ## Build the corresponding parametrized KEYS PDF {name}_kyes_{index} ## with {name}_keys_mode_{index} and ## {name}_keys_effsigma_{index}. keys_mode = w.factory( '{name}_keys_mode_{index}[91.2, 60, 120]'.format(name=name, index=index)) keys_effsigma = w.factory( '{name}_keys_effsigma_{index}[3, 0.1, 60]'.format(name=name, index=index)) mass.setRange(*mrangetrain) keys_pdf = ParametrizedKeysPdf(name + '_keys_pdf_%d' % index, name + '_keys_pdf_%d' % index, mass, keys_mode, keys_effsigma, sdata, rho=self._rho) self._keys_modes.append(keys_mode) self._keys_effsigmas.append(keys_effsigma) self._keys_pdfs.append(keys_pdf) ## Fit the KEYS PDF to the training data and save the result ## {name}_keys_fitresult_{index} and parameter snapshots ## {name}_keys_mctrue_{index}. mass.setRange(*mrangenorm) keys_fitresult = keys_pdf.fitTo(sdata, roo.Range(*mrangefit), roo.Strategy(2), roo.NumCPU(8), roo.Save(True)) self._keys_fitresults.append(keys_fitresult) w.Import(keys_fitresult, name + '_keys_fitresult_%d' % index) w.saveSnapshot( name + '_mctrue_%d' % index, ','.join([ phostrue.GetName(), phortrue.GetName(), keys_mode.GetName(), keys_effsigma.GetName() ])) ## Sample the fitted KEYS PDF to a histogram {name}_hist_{index}. mass.setRange(*mrangetrain) hist = keys_pdf.createHistogram(name + '_hist_%d' % index, mass, roo.Binning('cache')) ## Build a RooDataHist {name}_dhist{index} of the sampled histogram. dhist = ROOT.RooDataHist(name + '_dhist_%d' % index, name + '_dhist_%d' % index, ROOT.RooArgList(mass), hist) w.Import(dhist) ## Build a RooHistPdf {name}_pdf_{index} using the dhist and msubs. ## pdf = w.factory( pdf = w.factory('HistPdf::{name}({{{mass}}}, {dhist}, 1)'.format( name=name + '_pdf_%d' % index, msubs=msubs.GetName(), mass=mass.GetName(), dhist=dhist.GetName())) self._pdfs.append(pdf) mass.setRange(*self._massrange) ## Calculate morphing parameter reference values float mref[index]. phorval = phor.getVal() phor.setVal(phortrue.getVal()) self._mrefs.append(mpar.getVal()) phor.setVal(phorval) ## End of loop over target phortargets ## Make the reference and cache binnings in phor self._check_phor_ranges() partitions = self._partition_binning(self._phor, 'cache', 'reference') ## Loop over phor reference bins and define pairwise RooMomentMorphs. for ilo in range(len(self._mrefs) - 1): ihi = ilo + 1 mlo = self._mrefs[ilo] mhi = self._mrefs[ihi] pdflo = self._pdfs[ilo] pdfhi = self._pdfs[ihi] phormorph = w.factory(''' MomentMorph::{name}_phormorph_{ilo}to{ihi}( {mpar}, {{{mass}}}, {{{pdfs}}}, {{{mrefs}}} ) '''.format(name=name, ilo=ilo, ihi=ihi, mpar=mpar.GetName(), mass=mass.GetName(), pdfs='%s, %s' % (pdflo.GetName(), pdfhi.GetName()), mrefs='%f, %f' % (mlo, mhi))) self._phormorphs.append(phormorph) ## Sample the morph in a 2D histogram in mass and phor. phorhist = phormorph.createHistogram( name + '_phorhist_%dto%d' % (ilo, ihi), mass, roo.Binning('cache'), roo.YVar(phor, roo.Binning(partitions[ilo]))) self._phorhists.append(phorhist) ## End of loop over phor reference bins. self._stitch_phorhists() self._phor_dhist = phor_dhist = ROOT.RooDataHist( name + '_phor_dhist', name + '_phor_dhist', ROOT.RooArgList(mass, phor), self._phorhist) average_index = (len(self._msubs_list) + 1) / 2 msubs = self._msubs_list[average_index] ## self._model = model = ROOT.RooHistPdf( ## name + '_phor_histpdf', name + '_phor_histpdf', ## ROOT.RooArgList(msubs, phor), ROOT.RooArgList(mass, phor), phor_dhist, 2 ## ) ## self._model = model = ROOT.RooPhosphorPdf( ## name + '_phor_histpdf', name + '_phor_histpdf', mass, msubs, phor, phor_dhist, 2 ## ) ## ## Quick hack to make things work. ## self._customizer = customizer = ROOT.RooCustomizer(model, 'msub') ## customizer.replaceArg(mass, msubs) ## model = customizer.build() # ROOT.RooHistPdf.__init__(self, model) ROOT.RooPhosphorPdf.__init__(self, name, title, mass, msubs, phos, phor, phor_dhist, 2) self.SetName(name) self.SetTitle(title)
class PhosphorModel5(ROOT.RooPhosphorPdf): ##-------------------------------------------------------------------------- def __init__(self, name, title, mass, phos, phor, data, workspace, phostarget, phortargets, rho=1.5, mirror=ROOT.RooKeysPdf.NoMirror, mrangetrain=(40, 140), mrangenorm=(50, 130), mrangefit=(60, 120)): '''PhosphorModel4(str name, str title, RooRealVar mass, RooRealVar phos, RooRealVar phor, RooDataSet data, float phostarget, [float] phortargets, float rho=1.5, int mirror=ROOT.RooKeysPdf.NoMirror) name - PDF name title - PDF title mass - mumugamma invariant mass (GeV), observable phos - photon energy scale (%), parameter phor - photon energy resolution (%), paramter data - (mmMass, mmgMass, phoERes = 100*(phoEreco/phoEgen - 1), dataset on which the shapes of reference PDFs are trained. phostarget - target reference value of the photon energy scale at which the model is being trained phortargetss - a list of target photon energy resolution values for the moment morphing rho - passed to trained RooKeysPdfs mirror - passed to trained RooKeysPdfs ''' ## Attach args. self._name = name self._title = title self._mass = mass self._phos = phos self._phor = phor self._data = data self._phostarget = phostarget self._phortargets = phortargets[:] self._rho = rho self._mirror = mirror ## Attach other attributes. self._massrange = (mass.getMin(), mass.getMax()) self._sdata_list = [] self._phostrue_list = [] self._phortrue_list = [] self._dm_dphos_list = [] self._msubs_list = [] self._keys_pdfs = [] self._keys_modes = [] self._keys_effsigmas = [] self._keys_fitresults = [] self._pdfs = [] self._custs = [] # self._pdfrefs = [] self._mrefs = [] self._phormorphs = [] self._phorhists = [] self._workspace = workspace ## self._workspace = ROOT.RooWorkspace(name + '_workspace', ## title + ' workspace') w = self._workspace self.w = w ## Import important args in workspace. # w.Import(ROOT.RooArgSet(mass, phos, phor)) w.Import(mass) w.Import(phos) w.Import(phor) w.Import(self._data) w.factory('ConstVar::{name}_phostarget({value})'.format( name=name, value=self._phostarget)) ## Define the morphing parameter. This an identity with the ## photon resolution for now. mpar = self._mpar = w.factory( 'expr::{name}_mpar("{phor}", {{{phor}}})'.format( ## 'expr::{name}_mpar("2 + sqrt(0.5^2 + 0.05 * {phor}^2)", {{{phor}}})'.format( ## 'expr::{name}_mpar("2.325+sqrt(0.4571 + (0.1608*{phor})^2)", {{{phor}}})'.format( name=name, phor=phor.GetName())) ## Define the formula for dlog(m)/dphos. self._dm_dphos_func = w.factory(''' expr::dm_dphos_func("0.5 * (1 - mmMass^2 / mmgMass^2) * mmgMass", {mmMass, mmgMass}) ''') ## Get the calibrator. self._calibrator = MonteCarloCalibrator(self._data) ## Loop over target reference photon energy resolutions in phortargets. for index, phortarget in enumerate(self._phortargets): ## Store the target photon resolution value. w.factory('ConstVar::{name}_phortarget_{index}({value})'.format( name=name, index=index, value=phortarget)) ## Get the corresponding smeared RooDataSet sdata named ## {name}_sdata_{index} and attach it to self._sdata sdata = self._calibrator.get_smeared_data( self._phostarget, phortarget, name + '_sdata_%d' % index, title + ' sdata %d' % index, ## Get the true scale and resolution with errors. (This can be ## added to the calibrator and snapshots of ## self._calibrator.s and self._calibrator.r stored as ## {name}_sdata_{index}_sr in self._calibrator.w) dofit=True) self._sdata_list.append(sdata) w.Import(sdata) phostrue = ROOT.RooRealVar(self._calibrator.s, name + '_phostrue_%d' % index) phortrue = ROOT.RooRealVar(self._calibrator.r, name + '_phortrue_%d' % index) phostrue.setConstant(True) phortrue.setConstant(True) self._phostrue_list.append(phostrue) self._phortrue_list.append(phortrue) w.Import(phostrue) w.Import(phortrue) ## Calculate the dlogm/dphos {name}_dm_dphos_{index} ## for the smeared dataset. sdata.addColumn(self._dm_dphos_func) dm_dphos = w.factory(''' {name}_dm_dphos_{index}[{mean}, 0, 1] '''.format(name=name, index=index, mean=sdata.mean(sdata.get()['dm_dphos_func']))) dm_dphos.setConstant(True) self._dm_dphos_list.append(dm_dphos) ## Define the mass scaling {name}_msubs{i} introducing ## the dependence on phos. This needs self._calibrator.s and ## dm_dphos. ## msubs = w.factory( ## ''' ## LinearVar::{msubs}( ## {mass}, 1, ## expr::{offset}( ## "- 0.01 * {dm_dphos} * ({phos} - {phostrue})", ## {{ {dm_dphos}, {phos}, {phostrue} }} ## ) ## ) ## '''.format(msubs = name + '_msubs_%d' % index, ## offset = name + '_msubs_offset_%d' % index, ## mass = self._mass.GetName(), ## dm_dphos = dm_dphos.GetName(), ## phos = self._phos.GetName(), ## phostrue = phostrue.GetName()) ## ) ## LinearVar cannot be persisted. msubs = w.factory(''' expr::{msubs}( "{mass} - 0.01 * {dm_dphos} * ({phos} - {phostrue})", {{ {mass}, {dm_dphos}, {phos}, {phostrue} }} ) '''.format(msubs=name + '_msubs_%d' % index, mass=self._mass.GetName(), dm_dphos=dm_dphos.GetName(), phos=self._phos.GetName(), phostrue=phostrue.GetName())) self._msubs_list.append(msubs) ## Build the corresponding parametrized KEYS PDF {name}_kyes_{index} ## with {name}_keys_mode_{index} and ## {name}_keys_effsigma_{index}. keys_mode = w.factory( '{name}_keys_mode_{index}[91.2, 60, 120]'.format(name=name, index=index)) keys_effsigma = w.factory( '{name}_keys_effsigma_{index}[3, 0.1, 60]'.format(name=name, index=index)) mass.setRange(*mrangetrain) keys_pdf = ParametrizedKeysPdf(name + '_keys_pdf_%d' % index, name + '_keys_pdf_%d' % index, mass, keys_mode, keys_effsigma, sdata, rho=self._rho) self._keys_modes.append(keys_mode) self._keys_effsigmas.append(keys_effsigma) self._keys_pdfs.append(keys_pdf) ## Fit the KEYS PDF to the training data and save the result ## {name}_keys_fitresult_{index} and parameter snapshots ## {name}_keys_mctrue_{index}. mass.setRange(*mrangenorm) keys_fitresult = keys_pdf.fitTo(sdata, roo.Range(*mrangefit), roo.Strategy(2), roo.NumCPU(8), roo.Save(True)) self._keys_fitresults.append(keys_fitresult) w.Import(keys_fitresult, name + '_keys_fitresult_%d' % index) w.saveSnapshot( name + '_mctrue_%d' % index, ','.join([ phostrue.GetName(), phortrue.GetName(), keys_mode.GetName(), keys_effsigma.GetName() ])) ## Sample the fitted KEYS PDF to a histogram {name}_hist_{index}. mass.setRange(*mrangetrain) hist = keys_pdf.createHistogram(name + '_hist_%d' % index, mass, roo.Binning('cache')) ## Build a RooDataHist {name}_dhist{index} of the sampled histogram. dhist = ROOT.RooDataHist(name + '_dhist_%d' % index, name + '_dhist_%d' % index, ROOT.RooArgList(mass), hist) w.Import(dhist) ## Build a RooHistPdf {name}_pdf_{index} using the dhist and msubs. ## pdf = w.factory( pdf = w.factory('HistPdf::{name}({{{mass}}}, {dhist}, 1)'.format( name=name + '_pdf_%d' % index, msubs=msubs.GetName(), mass=mass.GetName(), dhist=dhist.GetName())) self._pdfs.append(pdf) mass.setRange(*self._massrange) ## Calculate morphing parameter reference values float mref[index]. phorval = phor.getVal() phor.setVal(phortrue.getVal()) self._mrefs.append(mpar.getVal()) phor.setVal(phorval) ## End of loop over target phortargets ## Make the reference and cache binnings in phor self._check_phor_ranges() partitions = self._partition_binning(self._phor, 'cache', 'reference') ## Loop over phor reference bins and define pairwise RooMomentMorphs. for ilo in range(len(self._mrefs) - 1): ihi = ilo + 1 mlo = self._mrefs[ilo] mhi = self._mrefs[ihi] pdflo = self._pdfs[ilo] pdfhi = self._pdfs[ihi] phormorph = w.factory(''' MomentMorph::{name}_phormorph_{ilo}to{ihi}( {mpar}, {{{mass}}}, {{{pdfs}}}, {{{mrefs}}} ) '''.format(name=name, ilo=ilo, ihi=ihi, mpar=mpar.GetName(), mass=mass.GetName(), pdfs='%s, %s' % (pdflo.GetName(), pdfhi.GetName()), mrefs='%f, %f' % (mlo, mhi))) self._phormorphs.append(phormorph) ## Sample the morph in a 2D histogram in mass and phor. phorhist = phormorph.createHistogram( name + '_phorhist_%dto%d' % (ilo, ihi), mass, roo.Binning('cache'), roo.YVar(phor, roo.Binning(partitions[ilo]))) self._phorhists.append(phorhist) ## End of loop over phor reference bins. self._stitch_phorhists() self._phor_dhist = phor_dhist = ROOT.RooDataHist( name + '_phor_dhist', name + '_phor_dhist', ROOT.RooArgList(mass, phor), self._phorhist) average_index = (len(self._msubs_list) + 1) / 2 msubs = self._msubs_list[average_index] ## self._model = model = ROOT.RooHistPdf( ## name + '_phor_histpdf', name + '_phor_histpdf', ## ROOT.RooArgList(msubs, phor), ROOT.RooArgList(mass, phor), phor_dhist, 2 ## ) ## self._model = model = ROOT.RooPhosphorPdf( ## name + '_phor_histpdf', name + '_phor_histpdf', mass, msubs, phor, phor_dhist, 2 ## ) ## ## Quick hack to make things work. ## self._customizer = customizer = ROOT.RooCustomizer(model, 'msub') ## customizer.replaceArg(mass, msubs) ## model = customizer.build() # ROOT.RooHistPdf.__init__(self, model) ROOT.RooPhosphorPdf.__init__(self, name, title, mass, msubs, phos, phor, phor_dhist, 2) self.SetName(name) self.SetTitle(title) ## End of __init__() ##-------------------------------------------------------------------------- def make_mctrue_graph(self, xname='phor', yname='keys_effsigma'): vardict = { 'phos': self._phostrue_list, 'phor': self._phortrue_list, 'keys_mode': self._keys_modes, 'keys_effsigma': self._keys_effsigmas } x = array.array('d', [v.getVal() for v in vardict[xname]]) y = array.array('d', [v.getVal() for v in vardict[yname]]) ex = array.array('d', [v.getError() for v in vardict[xname]]) ey = array.array('d', [v.getError() for v in vardict[yname]]) graph = ROOT.TGraphErrors(len(vardict[xname]), x, y, ex, ey) return graph ## for xvar in ## End of make_mctrue_graph() ##-------------------------------------------------------------------------- def _partition_binning(self, x, fine, coarse): '''partition_binning(RooAbsArg x, str fine, str coarse) fine .. name of the binning of x to be partitioned coarse .. name of the binning of x to define the partitioning ''' fbins = x.getBinning(fine) cbins = x.getBinning(coarse) ## Create empty lists of bins, one for each group. boundaries = [] for cbin in range(cbins.numBins()): boundaries.append([]) ## Loop over the fine bins for fbin in range(fbins.numBins()): groupindex = cbins.binNumber(fbins.binCenter(fbin)) ## Add the low boundary to the right group. boundaries[groupindex].append(fbins.binLow(fbin)) ## Check if the high boundary has to be added. if fbin == fbins.numBins() - 1: ## This is the last bin. Have to add the high boundary to the ## current group. boundaries[groupindex].append(fbins.binHigh(fbin)) continue if fbin == 0: ## This is the first bin. There is no previous bin, ## so that's it. The low edge of this bin will not ## be addede anywhere else. continue if len(boundaries[groupindex]) == 1: ## This is the first bin in this group. Add the boundary also ## to the previous group. boundaries[groupindex - 1].append(fbins.binLow(fbin)) ## End of loop over boundaries. names = [] for i, iboundaries in enumerate(boundaries): barray = array.array('d', iboundaries) nbins = len(barray) - 1 name = '%s_%dto%d' % (fine, i, i + 1) names.append(name) binning = ROOT.RooBinning(nbins, barray, name) x.setBinning(binning, name) return names ## End of partition_binning(). ##-------------------------------------------------------------------------- def _check_phor_ranges(self): 'Make sure that phor ranges reference and cache are well defined.' if not self._phor.hasBinning('cache'): self._phor.setBins(100, 'cache') boundaries = array.array('d', [x.getVal() for x in self._phortrue_list]) nbins = len(boundaries) - 1 refbins = ROOT.RooBinning(nbins, boundaries, 'reference') self._phor.setBinning(refbins, 'reference') ## End of _check_phor_ranges() ##-------------------------------------------------------------------------- def _stitch_phorhists(self): 'Assemble the phorhists in one histogram with larger phor range.' self._phorhist = ROOT.TH2F(self._name + '_phorhist', self._name + '_phorhist', self._mass.getBinning('cache').numBins(), self._mass.getBinning('cache').array(), self._phor.getBinning('cache').numBins(), self._phor.getBinning('cache').array()) ## Loop over the source histograms. for h in self._phorhists: ## Loop over the x-axis bins. for binx in range(1, h.GetNbinsX() + 1): xcenter = h.GetXaxis().GetBinCenter(binx) ## Loop over the y-axis bins. for biny in range(1, h.GetNbinsY() + 1): ## Set the content of the target histogram for this bin. ycenter = h.GetYaxis().GetBinCenter(biny) targetbin = self._phorhist.FindBin(xcenter, ycenter) self._phorhist.SetBinContent(targetbin, h.GetBinContent(binx, biny))
## Set units and nice titles for x, t, u in zip([mmgMass, mmMass, phoERes, mmgMassPeak, mmgMassWidth, phoScale, phoRes], ['m_{#mu#mu#gamma}', 'm_{#mu#mu}', 'E_{reco}^{#gamma}/E_{gen}^{#gamma} - 1', 'm_{peak}', '#sigma(m_{#mu#mu#gamma})', '#gamma scale', '#gamma resolution'], ['GeV', 'GeV', '%', 'GeV', 'GeV', '%', '%']): x.SetTitle(t) x.setUnit(u) ##------------------------------------------------------------------------------ ## Enlarge the range of the observable to get vanishing tails. range_save = (phoERes.getMin(), phoERes.getMax()) phoERes.setRange(-90, 150) calibrator = MonteCarloCalibrator(data) phoERes.setRange(*range_save) ## Get the smeared data sdata = calibrator.get_smeared_data(targets, targetr) ## Get the parametrized model for the photon energy resolution phoEResPdf = calibrator.phoEResPdf phoScale = calibrator.s phoRes = calibrator.r calibrator.w.loadSnapshot('sr0_mctruth') phoScaleRef = calibrator.s0.getVal() phoResRef = calibrator.r0.getVal() ## Get the parametrized model for the mmg mass mmgMassPdf = ParametrizedKeysPdf('mmgMassPdf', 'mmgMassPdf',
class PhosphorModel4(ROOT.RooMomentMorph): def __init__(self, name, title, mass, phos, phor, data, workspace, phostarget, phortargets, rho=1.5, mirror=ROOT.RooKeysPdf.NoMirror, mrangetrain=(40, 140), mrangenorm=(50, 130), mrangefit=(60, 120)): '''PhosphorModel4(str name, str title, RooRealVar mass, RooRealVar phos, RooRealVar phor, RooDataSet data, float phostarget, [float] phortargets, float rho=1.5, int mirror=ROOT.RooKeysPdf.NoMirror) name - PDF name title - PDF title mass - mumugamma invariant mass (GeV), observable phos - photon energy scale (%), parameter phor - photon energy resolution (%), paramter data - (mmMass, mmgMass, phoERes = 100*(phoEreco/phoEgen - 1), dataset on which the shapes of reference PDFs are trained. phostarget - target reference value of the photon energy scale at which the model is being trained phortargetss - a list of target photon energy resolution values for the moment morphing rho - passed to trained RooKeysPdfs mirror - passed to trained RooKeysPdfs ''' ## Attach args. self._name = name self._title = title self._mass = mass self._phos = phos self._phor = phor self._data = data self._phostarget = phostarget self._phortargets = phortargets[:] self._rho = rho self._mirror = mirror ## Attach other attributes. self._massrange = (mass.getMin(), mass.getMax()) self._sdata_list = [] self._phostrue_list = [] self._phortrue_list = [] self._dlogm_dphos_list = [] self._msubs_list = [] self._keys_pdfs = [] self._keys_modes = [] self._keys_effsigmas = [] self._keys_fitresults = [] self._pdfs = [] self._custs = [] self._pdfrefs = [] self._mrefs = [] self._workspace = workspace ## self._workspace = ROOT.RooWorkspace(name + '_workspace', ## title + ' workspace') w = self._workspace self.w = w ## Import important args in workspace. # w.Import(ROOT.RooArgSet(mass, phos, phor)) w.Import(mass) w.Import(phos) w.Import(phor) w.Import(self._data) w.factory('ConstVar::{name}_phostarget({value})'.format( name=name, value=self._phostarget)) ## Define the morphing parameter. This an identity with the ## photon resolution for now. mpar = self._mpar = w.factory( 'expr::{name}_mpar("{phor}", {{{phor}}})'.format( ## 'expr::{name}_mpar("sqrt(3^2 + {phor}^2)", {{{phor}}})'.format( ## 'expr::{name}_mpar("2.325+sqrt(0.4571 + (0.1608*{phor})^2)", {{{phor}}})'.format( name=name, phor=phor.GetName())) ## Define the formula for dlog(m)/dphos. self._dlogm_dphos_func = w.factory(''' expr::dlogm_dphos_func("0.5 * (1 - mmMass^2 / mmgMass^2) * mmgMass", {mmMass, mmgMass}) ''') ## Get the calibrator. self._calibrator = MonteCarloCalibrator(self._data) ## Loop over target reference photon energy resolutions in phortargets. for index, phortarget in enumerate(self._phortargets): ## Store the target photon resolution value. w.factory('ConstVar::{name}_phortarget_{index}({value})'.format( name=name, index=index, value=phortarget)) ## Get the corresponding smeared RooDataSet sdata named ## {name}_sdata_{index} and attach it to self._sdata sdata = self._calibrator.get_smeared_data( self._phostarget, phortarget, name + '_sdata_%d' % index, title + ' sdata %d' % index, ## Get the true scale and resolution with errors. (This can be ## added to the calibrator and snapshots of ## self._calibrator.s and self._calibrator.r stored as ## {name}_sdata_{index}_sr in self._calibrator.w) dofit=True) self._sdata_list.append(sdata) w.Import(sdata) phostrue = ROOT.RooRealVar(self._calibrator.s, name + '_phostrue_%d' % index) phortrue = ROOT.RooRealVar(self._calibrator.r, name + '_phortrue_%d' % index) phostrue.setConstant(True) phortrue.setConstant(True) self._phostrue_list.append(phostrue) self._phortrue_list.append(phortrue) w.Import(phostrue) w.Import(phortrue) ## Calculate the dlogm/dphos {name}_dlogm_dphos_{index} ## for the smeared dataset. sdata.addColumn(self._dlogm_dphos_func) dlogm_dphos = w.factory(''' {name}_dlogm_dphos_{index}[{mean}, 0, 1] '''.format(name=name, index=index, mean=sdata.mean(sdata.get()['dlogm_dphos_func']))) dlogm_dphos.setConstant(True) self._dlogm_dphos_list.append(dlogm_dphos) ## Define the mass scaling {name}_msubs{i} introducing ## the dependence on phos. This needs self._calibrator.s and ## dlogm_dphos. ## msubs = w.factory( ## ''' ## cexpr::{msubs}( ## "{mass}*(1 - 0.01 * {dlogm_dphos} * ({phos} - {phostrue}))", ## {{ {mass}, {dlogm_dphos}, {phos}, {phostrue} }} ## ) ## '''.format(msubs = name + '_msubs_%d' % index, ## mass = self._mass.GetName(), ## dlogm_dphos = dlogm_dphos.GetName(), ## phos = self._phos.GetName(), ## phostrue = phostrue.GetName()) ## ) ## msubs = w.factory( ## ''' ## LinearVar::{msubs}( ## {mass}, ## expr::{slope}( ## "(1 - 0.01 * {dlogm_dphos} * ({phos} - {phostrue}))", ## {{ {dlogm_dphos}, {phos}, {phostrue} }} ## ), ## 0 ## ) ## '''.format(msubs = name + '_msubs_%d' % index, ## slope = name + '_msubs_slope_%d' % index, ## mass = self._mass.GetName(), ## dlogm_dphos = dlogm_dphos.GetName(), ## phos = self._phos.GetName(), ## phostrue = phostrue.GetName()) ## ) msubs = w.factory(''' LinearVar::{msubs}( {mass}, 1, expr::{offset}( "- 0.01 * {dlogm_dphos} * ({phos} - {phostrue})", {{ {dlogm_dphos}, {phos}, {phostrue} }} ) ) '''.format(msubs=name + '_msubs_%d' % index, offset=name + '_msubs_offset_%d' % index, mass=self._mass.GetName(), dlogm_dphos=dlogm_dphos.GetName(), phos=self._phos.GetName(), phostrue=phostrue.GetName())) self._msubs_list.append(msubs) ## Build the corresponding parametrized KEYS PDF {name}_kyes_{index} ## with {name}_keys_mode_{index} and ## {name}_keys_effsigma_{index}. keys_mode = w.factory( '{name}_keys_mode_{index}[91.2, 60, 120]'.format(name=name, index=index)) keys_effsigma = w.factory( '{name}_keys_effsigma_{index}[3, 0.1, 60]'.format(name=name, index=index)) mass.setRange(*mrangetrain) keys_pdf = ParametrizedKeysPdf(name + '_keys_pdf_%d' % index, name + '_keys_pdf_%d' % index, mass, keys_mode, keys_effsigma, sdata) self._keys_modes.append(keys_mode) self._keys_effsigmas.append(keys_effsigma) self._keys_pdfs.append(keys_pdf) ## Fit the KEYS PDF to the training data and save the result ## {name}_keys_fitresult_{index} and parameter snapshots ## {name}_keys_mctrue_{index}. mass.setRange(*mrangenorm) keys_fitresult = keys_pdf.fitTo(sdata, roo.Range(*mrangefit), roo.Strategy(2), roo.NumCPU(8), roo.Save(True)) self._keys_fitresults.append(keys_fitresult) w.Import(keys_fitresult, name + '_keys_fitresult_%d' % index) w.saveSnapshot( name + '_mctrue_%d' % index, ','.join([ phostrue.GetName(), phortrue.GetName(), keys_mode.GetName(), keys_effsigma.GetName() ])) ## Sample the fitted KEYS PDF to a histogram {name}_hist_{index}. mass.setRange(*mrangetrain) hist = keys_pdf.createHistogram(name + '_hist_%d' % index, mass, roo.Binning(1000)) ## Build a RooDataHist {name}_dhist{index} of the sampled histogram. dhist = ROOT.RooDataHist(name + '_dhist_%d' % index, name + '_dhist_%d' % index, ROOT.RooArgList(mass), hist) w.Import(dhist) ## Build a RooHistPdf {name}_pdf_{index} using the dhist and msubs. ## pdf = w.factory( ## 'HistPdf::{name}({{{msubs}}}, {{{mass}}}, {dhist}, 1)'.format( ## name = name + '_pdf_%d' % index, msubs = msubs.GetName(), ## mass = mass.GetName(), dhist = dhist.GetName() ## ) ## ) pdf = w.factory('HistPdf::{name}({{{mass}}}, {dhist}, 1)'.format( name=name + '_pdf_%d' % index, msubs=msubs.GetName(), mass=mass.GetName(), dhist=dhist.GetName())) self._pdfs.append(pdf) mass.setRange(*self._massrange) ## Supstitute for mass using customizer. cust = ROOT.RooCustomizer(pdf, 'msubs_%d' % index) self._custs.append(cust) cust.replaceArg(mass, msubs) pdfref = cust.build() pdfref.addOwnedComponents(ROOT.RooArgSet(msubs)) pdfref.SetName(name + '_pdfref_%d' % index) pdfref.SetTitle(name + '_pdfref_%d' % index) w.Import(pdfref) self._pdfrefs.append(pdfref) ## Calculate morphing parameter reference values float mref[index]. phorval = phor.getVal() phor.setVal(phortrue.getVal()) self._mrefs.append(mpar.getVal()) phor.setVal(phorval) ## End of loop over target phortargets ## Define the RooMomentMorph model. model = w.factory(''' MomentMorph::{name}({mpar}, {{{mass}}}, {{{pdfs}}}, {{{mrefs}}}) '''.format( name=name, mpar=mpar.GetName(), mass=mass.GetName(), pdfs=','.join([f.GetName() for f in self._pdfs]), # pdfs=','.join([f.GetName() for f in self._pdfrefs]), mrefs=','.join([str(m) for m in self._mrefs]))) ## Quick hack to make things work. cust = ROOT.RooCustomizer(model, 'msub') cust.replaceArg(mass, self._msubs_list[0]) model2 = cust.build() ROOT.RooMomentMorph.__init__(self, model2) self.SetName(name) self.SetTitle(title) ## End of __init__() def make_mctrue_graph(self, xname='phor', yname='keys_effsigma'): vardict = { 'phos': self._phostrue_list, 'phor': self._phortrue_list, 'keys_mode': self._keys_modes, 'keys_effsigma': self._keys_effsigmas } x = array.array('d', [v.getVal() for v in vardict[xname]]) y = array.array('d', [v.getVal() for v in vardict[yname]]) ex = array.array('d', [v.getError() for v in vardict[xname]]) ey = array.array('d', [v.getError() for v in vardict[yname]]) print len(vardict[xname]), x, y, ex, ey graph = ROOT.TGraphErrors(len(vardict[xname]), x, y, ex, ey) return graph