Example #1
0
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)
Example #2
0
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()
Example #3
0
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)
Example #4
0
    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)
Example #5
0
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()
            )
        )
Example #6
0
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))
Example #7
0
    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)
Example #8
0
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))
Example #9
0
## 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',
Example #10
0
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