#!/usr/bin/env python3
##########################################################################
# basf2 (Belle II Analysis Software Framework)                           #
# Author: The Belle II Collaboration                                     #
#                                                                        #
# See git log for contributors and copyright holders.                    #
# This file is licensed under LGPL-3.0, see LICENSE.md.                  #
##########################################################################
""" Skim list building functions for the low multiplicity physics working group """
import math
import modularAnalysis as ma
from skim import BaseSkim, fancy_skim_header
from stdCharged import stdE, stdPi
from stdPhotons import stdPhotons
from variables import variables as vm
_VALIDATION_SAMPLE = "mdst16.root"
[docs]
@fancy_skim_header
class TwoTrackLeptonsForLuminosity(BaseSkim):
    """
    **Physics channel**: :math:`e^{+}e^{-} \\to e^{+}e^{-}` and :math:`e^{+}e^{-} \\to \\mu^{+}\\mu^{-}`
    """
    __authors__ = "Xing-Yu Zhou"
    __description__ = "Skim list for two track lepton (e+e- to e+e- and e+e- to mu+mu-) events for luminosity measurements."
    __contact__ = "Xing-Yu Zhou <xing-yu.zhou@desy.de>"
    __category__ = "physics, low multiplicity"
    TestSampleProcess = "mumu"
    ApplyHLTHadronCut = False
    def __init__(self, prescale=1, **kwargs):
        """
        Parameters:
            prescale (int): the prescale for this skim
            **kwargs: Passed to the constructor of `BaseSkim`
        """
        # Redefine __init__ to allow for additional optional arguments
        super().__init__(**kwargs)
        self.prescale = prescale
[docs]
    def build_lists(self, path):
        # Skim label
        skim_label = 'TwoTrackLeptonsForLuminosity'
        # Skim label for the case of two tracks
        skim_label_2 = 'TwoTrackLeptonsForLuminosity2'
        # Skim label for the case of one track plus one cluster
        skim_label_1 = 'TwoTrackLeptonsForLuminosity1'
        # Tracks from IP
        IP_cut = '[abs(dz) < 5.0] and [abs(dr) < 2.0]'
        # Tracks or clusters of momenta greater than 2 GeV in the CMS frame
        p_cut = '[useCMSFrame(p) > 2.0]'
        # Tracks pointing to or clusters locating in the barrel ECL + 10 degrees
        theta_cut = '[0.561 < theta < 2.247]'
        single_track_cut = IP_cut + ' and ' + p_cut + ' and ' + theta_cut
        single_cluster_cut = p_cut + ' and ' + theta_cut
        # Exactly 2 tracks
        nTracks_cut_2 = '[nCleanedTracks(' + single_track_cut + ') == 2 or nCleanedTracks(' + single_track_cut + ') == 3]'
        # Exactly 1 track
        nTracks_cut_1 = '[nCleanedTracks(' + single_track_cut + ') == 1]'
        # Acollinearity angle in the theta dimension less than 10 degrees in the CMS frame
        # candidates are : vpho -> e+ e- or vpho -> e gamma
        # daughter indices are:    0  1             0 1
        deltaTheta_cut = (
            f'[abs(formula(daughter(0, useCMSFrame(theta)) + daughter(1, useCMSFrame(theta)) - {math.pi})) < 0.17453293]'
        )
        # convert the prescale from trigger convention
        prescale = str(float(1.0 / self.prescale))
        prescale_logic = 'eventRandom <= ' + prescale
        two_track_cut = nTracks_cut_2 + ' and ' + deltaTheta_cut + ' and ' + prescale_logic
        track_cluster_cut = nTracks_cut_1 + ' and ' + deltaTheta_cut + ' and ' + prescale_logic
        # Reconstruct the event candidates with two tracks
        ma.fillParticleList('e+:' + skim_label_2, single_track_cut + ' and ' + nTracks_cut_2, path=path)
        ma.reconstructDecay('vpho:' + skim_label_2 + ' -> e+:' + skim_label_2 + ' e-:' + skim_label_2, two_track_cut, path=path)
        # Reconstruct the event candidates with one track plus one cluster
        ma.fillParticleList('e+:' + skim_label_1, single_track_cut + ' and ' + nTracks_cut_1, path=path)
        ma.fillParticleList('gamma:' + skim_label_1, single_cluster_cut + ' and ' + nTracks_cut_1, path=path)
        ma.reconstructDecay(
            'vpho:' +
            skim_label_1 +
            ' -> e+:' +
            skim_label_1 +
            ' gamma:' +
            skim_label_1,
            track_cluster_cut,
            allowChargeViolation=True,
            path=path)
        ma.copyLists('vpho:' + skim_label, ['vpho:' + skim_label_2, 'vpho:' + skim_label_1], path=path)
        return ['vpho:' + skim_label] 
 
[docs]
@fancy_skim_header
class LowMassTwoTrack(BaseSkim):
    """
    **Physics channel**: :math:`e^{+}e^{-} \\to \\gamma h_{1}^{+}h_{2}^{-} X`
    .. Warning::
        This skim includes the golden mode :math:`e^{+}e^{-} \\to \\gamma \\pi^{+}\\pi^{-}`
    .. Note::
        The :math:`h_{1}^{+}` and :math:`h_{2}^{+}` here mean a positive particle
        and a negative particle that could be either conjugate or non-conjugate. The
        :math:`X` means arbitrary final state particles.
    **Decay Modes**
    1. :math:`e^{+}e^{-} \\to \\gamma \\pi^{+} \\pi^{-} X`,
    2. :math:`e^{+}e^{-} \\to \\gamma K^{+} K^{-} X`,
    3. :math:`e^{+}e^{-} \\to \\gamma K^{+} \\pi^{-} X`,
    4. :math:`e^{+}e^{-} \\to \\gamma p \\overline{p} X`,
    5. :math:`e^{+}e^{-} \\to \\gamma p \\pi^{-} X`,
    6. :math:`e^{+}e^{-} \\to \\gamma p K^{-} X`,
    7. :math:`e^{+}e^{-} \\to \\gamma \\mu^{+} \\mu^{-} X`,
    """
    __authors__ = ["Xing-Yu Zhou", "Guanda Gong"]
    __description__ = "Skim list for low mass events with at least two tracks and one hard photon" \
                      
" in final state."
    __contact__ = "Xing-Yu Zhou <xing-yu.zhou@desy.de>"
    __category__ = "physics, low multiplicity"
    TestSampleProcess = "mumu"
    validation_sample = _VALIDATION_SAMPLE
    ApplyHLTHadronCut = False
[docs]
    def build_lists(self, path):
        label = "LowMassTwoTrack"
        # Momenta of tracks greater than 0.5 GeV in the Lab frame
        pCut = "p > 0.5"
        # Energy of hard ISR gamma greater than 2 GeV in the CMS frame
        ISRECut = "useCMSFrame(E) > 2"
        # Invariant mass of h+h- system less than 3.5 GeV
        hhMassWindow = "daughterInvM(1,2) < 3.5"
        # Event based cut
        # Number of tracks passing the selection criteria, should be greater than or equal to to 2
        nTracksCut = f"nCleanedTracks({pCut}) >= 2"
        # Require at least one hard photon
        nHardISRPhotonCut = f"nCleanedECLClusters({ISRECut}) > 0"
        # Apply event based cuts
        path = self.skim_event_cuts(f"{nTracksCut} and {nHardISRPhotonCut}", path=path)
        # Reconstruct candidates
        ma.fillParticleList(f"pi+:{label}", pCut, path=path)
        ma.fillParticleList(f"K+:{label}", pCut, path=path)
        ma.fillParticleList(f"p+:{label}", pCut, path=path)
        ma.fillParticleList(f"gamma:{label}_ISR", ISRECut, path=path)
        ma.fillParticleList(f"mu+:{label}", pCut, path=path)
        # the mass hypothesis is different for p+, pi+ and K+ lists, so it is good to write them separately.
        ModesAndCuts = [
            (f"vpho:{label}_pipi", f" -> gamma:{label}_ISR pi+:{label} pi-:{label}", hhMassWindow),
            (f"vpho:{label}_KK", f" -> gamma:{label}_ISR K+:{label} K-:{label}", hhMassWindow),
            # Might be useful when one wants to reconstruct ISR K pi and missing other final state particles
            (f"vpho:{label}_Kpi", f" -> gamma:{label}_ISR K+:{label} pi-:{label}", hhMassWindow),
            (f"vpho:{label}_pp", f" -> gamma:{label}_ISR p+:{label} anti-p-:{label}", hhMassWindow),
            # Useful for analyses for processes like ISR Lambda Lambda-bar (Sigma Sigma-bar) , especially when
            # one wants to reconstruct the hard ISR photon and one of the Lambda (Sigma), missing anthoer
            # Lambda (Sigma)
            (f"vpho:{label}_ppi", f" -> gamma:{label}_ISR p+:{label} pi-:{label}", hhMassWindow),
            # Might be useful when one wants to reconstruct ISR p K and missing other final state particles
            (f"vpho:{label}_pK", f" -> gamma:{label}_ISR p+:{label} K-:{label}", hhMassWindow),
            (f"vpho:{label}_mumu", f" -> gamma:{label}_ISR mu+:{label} mu-:{label}", hhMassWindow),
        ]
        ParticleLists = []
        for dmID, (mode, decayString, cut) in enumerate(ModesAndCuts):
            ma.reconstructDecay(mode + decayString, cut, dmID=dmID, path=path)
            ParticleLists.append(mode)
        return ParticleLists 
[docs]
    def validation_histograms(self, path):
        from ROOT import Belle2
        vm.addAlias('pip_p_cms', 'daughter(0, useCMSFrame(p))')
        vm.addAlias('pim_p_cms', 'daughter(1, useCMSFrame(p))')
        vm.addAlias('gamma_E_cms', 'daughter(2, useCMSFrame(E))')
        vm.addAlias('pip_theta_lab', f'formula(daughter(0, theta)/{Belle2.Unit.deg})')
        vm.addAlias('pim_theta_lab', f'formula(daughter(1, theta)/{Belle2.Unit.deg})')
        vm.addAlias('gamma_theta_lab', f'formula(daughter(2, theta)/{Belle2.Unit.deg})')
        vm.addAlias('Mpipi', 'daughterInvM(0,1)')
        ma.copyLists('vpho:LowMassTwoTrack', self.SkimLists, path=path)
        variablesHist = [
            ('pip_p_cms', 60, 0, 6),
            ('pim_p_cms', 60, 0, 6),
            ('gamma_E_cms', 60, 0, 6),
            ('pip_theta_lab', 90, 0, 180),
            ('pim_theta_lab', 90, 0, 180),
            ('gamma_theta_lab', 90, 0, 180),
            ('Mpipi', 80, 0., 4.),
            ('M', 60, 6., 12.)
        ]
        # Output the variables to histograms
        ma.variablesToHistogram(
            'vpho:LowMassTwoTrack',
            variablesHist,
            filename=f'{self}_Validation.root',
            path=path) 
 
[docs]
@fancy_skim_header
class SingleTagPseudoScalar(BaseSkim):
    """
    **Physics channel**: :math:`e^{+}e^{-} \\to  e^{\\pm} (e^{\\mp}) \\pi^{0}/\\eta/\\eta^{\\prime}`
    **Decay Modes**
    1. :math:`\\pi^{0}\\to \\gamma \\gamma`,
    2. :math:`\\eta \\to \\gamma\\gamma`,
    3. :math:`\\eta \\to \\pi^{+}\\pi^{-}\\pi^{0}`,
    4. :math:`\\eta \\to \\pi^{+}\\pi^{-}\\gamma`,
    5. :math:`\\eta^{\\prime} \\to \\pi^{+}\\pi^{-}\\eta(\\to \\gamma\\gamma)`,
    6. :math:`\\eta^{\\prime} \\to \\pi^{+}\\pi^{-}\\gamma`
    """
    __authors__ = ["Hisaki Hayashii"]
    __contact__ = "Hisaki Hayashii <hisaki.hayashii@desy.de>"
    __description__ = "A skim script to select events with one high-energy electron and one or more pi0/eta/eta mesons."
    __category__ = "physics, low multiplicity"
    ApplyHLTHadronCut = False
[docs]
    def load_standard_lists(self, path):
        stdE("all", path=path)
        stdPi("all", path=path)
        stdPhotons("all", path=path) 
[docs]
    def build_lists(self, path):
        label = "PseudoScalarSkim"
        TrackCuts = "abs(dz) < 2.0 and dr < 0.5 and pt > 0.15"
        ma.fillParticleList(f"e+:{label}", f"{TrackCuts} and E > 1.5 and clusterEoP > 0.7", path=path)
        ma.fillParticleList(f"pi+:{label}", f"{TrackCuts} and [not isInList(e+:{label})]", path=path)
        ma.fillParticleList(f"gamma:{label}", "clusterE > 0.1", path=path)
        pi0MassWindow = "0.04 < InvM < 0.4"
        etaMassWindow = "0.50 < InvM < 0.60"
        etapMassWindow = "0.91 < InvM < 1.10"
        ModesAndCuts = [
            (f"pi0:{label}_loose -> gamma:{label} gamma:{label}", pi0MassWindow),
            (f"eta:gg -> gamma:{label} gamma:{label}", etaMassWindow),
            (f"eta:pipipi0 -> pi+:{label} pi-:{label} pi0:{label}_loose", etaMassWindow),
            (f"eta:pipig -> pi+:{label} pi-:{label} gamma:{label}", etaMassWindow),
            (f"eta':pipieta_gg -> pi+:{label} pi-:{label} eta:gg", etapMassWindow),
            (f"eta':pipig -> pi+:{label} pi-:{label} gamma:{label}", etapMassWindow),
        ]
        for dmID, (mode, cut) in enumerate(ModesAndCuts):
            ma.reconstructDecay(mode, cut, dmID=dmID, path=path)
        ma.cutAndCopyList(f"pi0:{label}_highE_SingleTagPseudoScalar", f"pi0:{label}_loose", "E > 0.5", path=path)
        particles = [
            f"pi0:{label}_highE_SingleTagPseudoScalar",
            "eta:gg",
            "eta:pipipi0",
            "eta:pipig",
            "eta':pipieta_gg",
            "eta':pipig"
        ]
        ModeSum = " + ".join(f"nParticlesInList({particle})" for particle in particles)
        presel = f"nParticlesInList(e+:{label}) == 1 and nParticlesInList(pi+:{label}) <= 2"
        EventCuts = f"{presel} and formula({ModeSum}) >= 1"
        # Although a condition of "mode_sum >= 1" looks like very loose,
        # the reduction rate of this SingleTagPseudoScalar skim is very large, i.e. 1/50,
        # since the requirements, one high-energy electron and <=2 other charged
        # tracks, are quite stringent.
        path = self.skim_event_cuts(EventCuts, path=path)
        return [f"e+:{label}"] 
 
[docs]
@fancy_skim_header
class LowMassOneTrack(BaseSkim):
    """
    **Physics channel**: :math:`e^{+}e^{-} \\to \\gamma \\pi^{+}\\pi^{-}` and :math:`e^{+}e^{-} \\to \\gamma \\mu^{+}\\mu^{-}`
    """
    __authors__ = ["Gaurav Sharma", "Qingyuan Liu"]
    __description__ = "Skim list for low mass events with one track and one hard photon in final state."
    __contact__ = "Gaurav Sharma <gaurav@physics.iitm.ac.in>"
    __category__ = "physics, low multiplicity"
    TestSampleProcess = "mumu"
    ApplyHLTHadronCut = False
[docs]
    def build_lists(self, path):
        label = "LowMassOneTrack"
        # Momenta of tracks greater than 0.3 GeV in the Lab frame
        track_cut = "[p > 0.5] and [clusterEoP < 0.9] and [abs(dz) < 5.0] and [abs(dr) < 2.0] and inCDCAcceptance"
        # Energy of hard ISR gamma greater than 2 GeV in the CMS frame
        isr_cut = "useCMSFrame(E) > 2"
        singleTrack_cut = f"nCleanedTracks({track_cut}) == 1"
        # Require at least one hard photon
        nHardISRPhotonCut = f"nCleanedECLClusters({isr_cut}) > 0"
        # Apply event based cuts
        path = self.skim_event_cuts(f"{singleTrack_cut} and {nHardISRPhotonCut}", path=path)
        # two_track_cut = f"{track_cut} and {nTracksCut}"
        # one_track_cut = f"{track_cut} and {singleTrack_cut}"
        # negative_one_track_cut = f"{track_cut} and {singleTrack_cut} and [charge < 0]"
        track_list = ['pi', 'mu']
        ParticleLists = []
        ma.fillParticleList(f"gamma:isr_{label}", isr_cut, path=path)
        ma.rankByHighest(f"gamma:isr_{label}",
                         "useCMSFrame(E)",
                         outputVariable="highestE_rank",
                         numBest=1,
                         path=path
                         )
        for tracks in track_list:
            ma.fillParticleList(f"{tracks}+:{label}", track_cut, path=path)
            ma.reconstructDecay(f"vpho:g_{tracks}{label} -> gamma:isr_{label} {tracks}+:{label}",
                                cut="",
                                allowChargeViolation=True,
                                path=path,
                                )
            ParticleLists.append(f"vpho:g_{tracks}{label}")
        return ParticleLists