#!/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 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 = "mdst14.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 = (
'[abs(formula(daughter(0, useCMSFrame(theta)) + daughter(1, useCMSFrame(theta)) - 3.1415927)) < 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):
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', 'formula(daughter(0, theta)*180/3.1415927)')
vm.addAlias('pim_theta_lab', 'formula(daughter(1, theta)*180/3.1415927)')
vm.addAlias('gamma_theta_lab', 'formula(daughter(2, theta)*180/3.1415927)')
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