Newer
Older
# Copyright (C) 2002-2024 CERN for the benefit of the ATLAS collaboration
from AnalysisAlgorithmsConfig.ConfigBlock import ConfigBlock
from AsgAnalysisAlgorithms.AsgAnalysisConfig import makeEventCutFlowConfig
class EventSelectionMergerConfig(ConfigBlock):
"""ConfigBlock for merging the output of various selection streams"""
def __init__(self):
super(EventSelectionMergerConfig, self).__init__()
self.addOption('selections', [], type=list,
info="the selection decisions (list of strings) to unify into a "
"final decision (internally: selection_1 || selection_2 || ...). "
"The default is [] (empty list).")
self.addOption('noFilter', False, type=bool,
info="do not apply an event filter. The default is False, i.e. "
"remove events not passing the full list of selection cuts.")
def makeAlgs(self, config):
alg = config.createAlgorithm('CP::SaveFilterAlg', 'EventSelectionMerger')
alg.FilterDescription = 'events passing at least one EventSelection algorithm'
alg.eventDecisionOutputDecoration = 'ignore_anySelection_%SYS%'
alg.selection = '||'.join([sel+',as_char' for sel in self.selections if sel])
alg.noFilter = self.noFilter
alg.selectionName = 'pass_anySelection_%SYS%'
alg.decorationName = 'ntuplepass_anySelection_%SYS%'
class EventSelectionConfig(ConfigBlock):
"""ConfigBlock for interpreting text-based event selections"""
def __init__(self, name=''):
super(EventSelectionConfig, self).__init__()
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
self.addOption('name', name, type=str,
noneAction='error',
info="the name of the event selection, used to uniquely identify "
"the EventSelectionConfig block.")
self.addOption('electrons', "", type=str,
info="the input electron container, with a possible selection, in "
"the format container or container.selection. The default is '' "
"(empty string).")
self.addOption('muons', "", type=str,
info="the input muon container, with a possible selection, in the "
"format container or container.selection. The default is '' "
"(empty string).")
self.addOption('jets', "", type=str,
info="the input jet container, with a possible selection, in the "
"format container or container.selection. The default is '' "
"(empty string).")
self.addOption('largeRjets', "", type=str,
info="the large-R jet container, with a possible selection, in "
"the format container or container.selection. The default is '' "
"(empty string).")
self.addOption('photons', "", type=str,
info="the input photon container, with a possible selection, in "
"the format container or container.selection. The default is '' "
"(empty string).")
self.addOption('taus', "", type=str,
info="the input tau-jet container, with a possible selection, in "
"the format container or container.selection. The default is '' "
"(empty string).")
self.addOption('met', "", type=str,
info="he input MET container. The default is '' (empty string).")
#TODO: add info string
self.addOption('metTerm', "Final", type=str,
info="")
self.addOption('btagDecoration', "", type=str,
info="the b-tagging decoration to use when defining b-jets. "
"The default is '' (empty string).")
self.addOption('preselection', "", type=str,
info="the event-wise selection flag to start this event selection "
"from. The default is '' (empty string).")
self.addOption('selectionCuts', "", type=str,
noneAction='error',
info="a single string listing one selection cut per line.")
self.addOption('noFilter', False, type=bool,
info="do not apply an event filter. The default is False, i.e. "
"remove events not passing the full list of selection cuts.")
self.addOption('debugMode', False, type=bool,
info="whether to create an output branch for every single line "
"of the selection cuts. The default is False (only saves the"
" final decision).")
self.step = 0
self.currentDecoration = ''
self.cutflow = []

Baptiste Ravina
committed
self.name = name
def makeAlgs(self, config):
# need to re-initialize here to deal with multiple passes
self.step = 0
# initialize the pre-selection
self.currentDecoration = self.preselection
# re-initialize the cutflow
self.cutflow = []
# read the selection cuts
if self.selectionCuts is None:
raise ValueError ("[EventSelectionConfig] You must provide the 'selectionCuts' option to 'EventSelectionConfig': "
"a single string where each line represents a different selection cut to apply in order.")
for line in self.selectionCuts.split("\n"):
self.interpret(line, config)
config.addEventCutFlow(self.name, self.getCutflow())
def interpret(self, text, cfg):
text = text.strip()
if not text:
return
if text.startswith("#"):
return
self.step += 1
if "EL_N" in text.split():
self.add_NEL_selector(text, cfg)
elif "MU_N" in text.split():
self.add_NMU_selector(text, cfg)
elif "SUM_EL_N_MU_N" in text.split():
self.add_SUMNELNMU_selector(text, cfg)
elif "JET_N" in text.split():
self.add_NJET_selector(text, cfg)
elif "JET_N_BTAG" in text.split():
self.add_NBJET_selector(text, cfg)
elif "PH_N" in text.split():
self.add_NPH_selector(text, cfg)
elif "TAU_N" in text.split():
self.add_NTAU_selector(text, cfg)
elif "LJET_N" in text.split():
self.add_NLJET_selector(text, cfg)
elif "MET" in text.split():
self.add_MET_selector(text, cfg)
elif "MWT" in text.split():
self.add_MWT_selector(text, cfg)
elif "MET+MWT" in text.split():
self.add_METMWT_selector(text, cfg)
elif "MLL" in text.split():
self.add_MLL_selector(text, cfg)
elif "MLLWINDOW" in text.split():
self.add_MLLWINDOW_selector(text, cfg)
elif "OS" in text.split():
self.add_OS_selector(text, cfg)
elif "SS" in text.split():
self.add_SS_selector(text, cfg)
elif "MLL_OSSF" in text.split():
self.add_MLL_OSSF_selector(text, cfg)
elif "LJETMASS_N" in text.split():
self.add_NLJETMASS_selector(text, cfg)
elif "LJETMASSWINDOW_N" in text.split():
self.add_NLJETMASSWINDOW_selector(text, cfg)
elif "SAVE" in text.split():
self.add_SAVE(text, cfg)
elif "IMPORT" in text.split():
self.add_IMPORT(text, cfg)

Baptiste Ravina
committed
elif "EVENTFLAG" in text.split():
self.add_EVENTFLAG(text, cfg)
elif "GLOBALTRIGMATCH" in text.split():
self.add_GLOBALTRIGMATCH(text, cfg)
else:
raise ValueError (f"[EventSelectionConfig] The following selection cut is not recognised! --> {text}")
def raise_misconfig(self, text, keyword):
raise ValueError (f"[EventSelectionConfig] Misconfiguration! Check {keyword} in: {text}")

Baptiste Ravina
committed
def raise_missinginput(self, collection):
raise ValueError (f"[EventSelectionConfig] Misconfiguration! Missing input collection for {collection}")
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
def check_float(self, test, requirePositive=True):
try:
value = float(test)
if not requirePositive or value >= 0:
return value
else:
raise ValueError (f"[EventSelectionConfig] Misconfiguration! Float {test} is not positive!")
except ValueError:
raise ValueError (f"[EventSelectionConfig] Misconfiguration! {test} should be a float, not {type(test)}!")
def check_int(self, test, requirePositive=True):
try:
value = int(test)
if value == float(test):
if not requirePositive or value >= 0:
return value
else:
raise ValueError (f"[EventSelectionConfig] Misconfiguration! Int {test} us not positive!")
else:
raise ValueError (f"[EventSelectionConfig] Misconfiguration! {test} should be an int, not a float!")
except ValueError:
raise ValueError (f"[EventSelectionConfig] Misconfiguration! {test} should be an int, not {type(test)}")
def check_string(self, test):
if not isinstance(test, str):
raise ValueError (f"[EventSelectionConfig] Misconfiguration! {test} should be a string, not a number!")
else:
return test
def check_sign(self, test):
mapping = {
"<" : "LT",
">" : "GT",
"==": "EQ",
">=": "GE",
"<=": "LE"
}
try:
return mapping[test]
except KeyError:
raise KeyError (f"[EventSelectionConfig] Misconfiguration! {test} should be one of {list(mapping.keys())}")
def check_btagging(self, test):
test = test.split(":")
if len(test) != 2:
raise ValueError (f"[EventSelectionConfig] Misconfiguration! {test} should be provided as 'btagger:btagWP'")
else:
return test
def getCutflow(self):
return self.cutflow
def setDecorationName(self, algorithm, config, decoration):
self.cutflow.append( decoration )

Baptiste Ravina
committed
if algorithm is not None:
algorithm.decorationName = f'{decoration},as_char'
self.currentDecoration = decoration

Baptiste Ravina
committed
if self.debugMode:
config.addOutputVar('EventInfo', decoration, decoration.split("_%SYS%")[0])
else:
if self.currentDecoration:
self.currentDecoration += '&&' + decoration
else:
self.currentDecoration = decoration
config.addSelection('EventInfo', '', decoration)
return
def checkDecorationName(self, decoration):
if decoration == '':
return decoration
decoration = decoration.split("&&")
decoration = [sub + ',as_char' if ',as_char' not in sub else sub for sub in decoration]
return '&&'.join(decoration)
def add_IMPORT(self, text, config):
# this is used to import a previous selection
items = text.split()
if items[0] != "IMPORT":
self.raise_misconfig(text, "IMPORT")
if len(items) != 2:
self.raise_misconfig(text, "number of arguments")
region = self.check_string(items[1])
if not self.currentDecoration:
self.currentDecoration = f'pass_{region}_%SYS%,as_char'
self.currentDecoration = f'{self.currentDecoration},as_char&&pass_{region}_%SYS%'
# for the cutflow, we need to retrieve all the cuts corresponding to this IMPORT
imported_cuts = [cut for cut in config.getSelectionCutFlow('EventInfo', '') if cut.startswith(region)]
self.cutflow += imported_cuts
return
def add_NEL_selector(self, text, config):
items = text.split()
if items[0] != "EL_N":
self.raise_misconfig(text, "EL_N")
if len(items) != 4 and len(items) != 5:
self.raise_misconfig(text, "number of arguments")

Baptiste Ravina
committed
if not self.electrons:
self.raise_missinginput("electrons")
thisalg = f'{self.name}_NEL_{self.step}'
alg = config.createAlgorithm('CP::NObjectPtSelectorAlg', thisalg)
alg.particles, alg.objectSelection = config.readNameAndSelection(self.electrons)
alg.eventPreselection = self.checkDecorationName(self.currentDecoration)
if len(items) == 4:
alg.minPt = self.check_float(items[1])
alg.sign = self.check_sign(items[2])
alg.count = self.check_int(items[3])
elif len(items) == 5:
extraSel = self.check_string(items[1])
if alg.objectSelection:
alg.objectSelection += "&&" + config.getFullSelection(self.electrons.split(".")[0], extraSel)
else:
alg.objectSelection = config.getFullSelection(self.electrons.split(".")[0], extraSel)
alg.minPt = self.check_float(items[2])
alg.sign = self.check_sign(items[3])
alg.count = self.check_int(items[4])
self.setDecorationName(alg, config, f'{thisalg}_%SYS%')
return
def add_NMU_selector(self, text, config):
items = text.split()
if items[0] != "MU_N":
self.raise_misconfig(text, "MU_N")
if len(items) != 4 and len(items) != 5:
self.raise_misconfig(text, "number of arguments")

Baptiste Ravina
committed
if not self.muons:
self.raise_missinginput("muons")
thisalg = f'{self.name}_NMU_{self.step}'
alg = config.createAlgorithm('CP::NObjectPtSelectorAlg', thisalg)
alg.particles, alg.objectSelection = config.readNameAndSelection(self.muons)
alg.eventPreselection = self.checkDecorationName(self.currentDecoration)
if len(items) == 4:
alg.minPt = self.check_float(items[1])
alg.sign = self.check_sign(items[2])
alg.count = self.check_int(items[3])
elif len(items) == 5:
extraSel = self.check_string(items[1])
if alg.objectSelection:
alg.objectSelection += "&&" + config.getFullSelection(self.muons.split(".")[0], extraSel)
else:
alg.objectSelection = config.getFullSelection(self.muons.split(".")[0], extraSel)
alg.minPt = self.check_float(items[2])
alg.sign = self.check_sign(items[3])
alg.count = self.check_int(items[4])
self.setDecorationName(alg, config, f'{thisalg}_%SYS%')
return
def add_SUMNELNMU_selector(self, text, config):
items = text.split()
if items[0] != "SUM_EL_N_MU_N":
self.raise_misconfig(text, "SUM_EL_N_MU_N")
if len(items) != 4 and len(items) != 5:
self.raise_misconfig(text, "number of arguments")
if not self.electrons and not self.muons:
self.raise_missinginput("electrons or muons")
thisalg = f'{self.name}_SUMNELNMU_{self.step}'
alg = config.createAlgorithm('CP::SumNElNMuPtSelectorAlg', thisalg)
alg.electrons, alg.electronSelection = config.readNameAndSelection(self.electrons)
alg.muons, alg.muonSelection = config.readNameAndSelection(self.muons)
alg.eventPreselection = self.checkDecorationName(self.currentDecoration)
if len(items) == 4:
alg.minPtEl = self.check_float(items[1])
alg.minPtMu = self.check_float(items[1])
alg.sign = self.check_sign(items[2])
alg.count = self.check_int(items[3])
elif len(items) == 5:
alg.minPtEl = self.check_float(items[1])
alg.minPtMu = self.check_float(items[2])
alg.sign = self.check_sign(items[3])
alg.count = self.check_int(items[4])
self.setDecorationName(alg, config, f'{thisalg}_%SYS%')
return
def add_NJET_selector(self, text, config):
items = text.split()
if items[0] != "JET_N":
self.raise_misconfig(text, "JET_N")
if len(items) != 4 and len(items) != 5:
self.raise_misconfig(text, "number of arguments")

Baptiste Ravina
committed
if not self.jets:
self.raise_missinginput("jets")
thisalg = f'{self.name}_NJET_{self.step}'
alg = config.createAlgorithm('CP::NObjectPtSelectorAlg', thisalg)
alg.particles, alg.objectSelection = config.readNameAndSelection(self.jets)
alg.eventPreselection = self.checkDecorationName(self.currentDecoration)
if len(items) == 4:
alg.minPt = self.check_float(items[1])
alg.sign = self.check_sign(items[2])
alg.count = self.check_int(items[3])
elif len(items) == 5:
extraSel = self.check_string(items[1])
if alg.objectSelection:
alg.objectSelection += "&&" + config.getFullSelection(self.jets.split(".")[0], extraSel)
else:
alg.objectSelection = config.getFullSelection(self.jets.split(".")[0], extraSel)
alg.minPt = self.check_float(items[2])
alg.sign = self.check_sign(items[3])
alg.count = self.check_int(items[4])
self.setDecorationName(alg, config, f'{thisalg}_%SYS%')
return
def add_NBJET_selector(self, text, config):
items = text.split()
if items[0] != "JET_N_BTAG":
self.raise_misconfig(text, "JET_N_BTAG")
if len(items) != 3 and len(items) != 4:
self.raise_misconfig(text, "number of arguments")

Baptiste Ravina
committed
if not self.jets:
self.raise_missinginput("jets")
thisalg = f'{self.name}_NBJET_{self.step}'
alg = config.createAlgorithm('CP::NObjectPtSelectorAlg', thisalg)
particles, selection = config.readNameAndSelection(self.jets)
alg.particles = particles
alg.objectSelection = f'{selection}&&{self.btagDecoration},as_char'
alg.eventPreselection = self.checkDecorationName(self.currentDecoration)
alg.minPt = 25000.
if len(items) == 3:
alg.sign = self.check_sign(items[1])
alg.count = self.check_int(items[2])
elif len(items) == 4:
btagger, btagWP = self.check_btagging(items[1])
customBtag = f'ftag_select_{btagger}_{btagWP}'
alg.objectSelection = f'{selection}&&{customBtag},as_char'
alg.sign = self.check_sign(items[2])
alg.count = self.check_int(items[3])
self.setDecorationName(alg, config, f'{thisalg}_%SYS%')
return
def add_NPH_selector(self, text, config):
items = text.split()
if items[0] != "PH_N":
self.raise_misconfig(text, "PH_N")
if len(items) != 4 and len(items) != 5:
self.raise_misconfig(text, "number of arguments")

Baptiste Ravina
committed
if not self.photons:
self.raise_missinginput("photons")
thisalg = f'{self.name}_NPH_{self.step}'
alg = config.createAlgorithm('CP::NObjectPtSelectorAlg', thisalg)
alg.particles, alg.objectSelection = config.readNameAndSelection(self.photons)
alg.eventPreselection = self.checkDecorationName(self.currentDecoration)
if len(items) == 4:
alg.minPt = self.check_float(items[1])
alg.sign = self.check_sign(items[2])
alg.count = self.check_int(items[3])
elif len(items) == 5:
extraSel = self.check_string(items[1])
if alg.objectSelection:
alg.objectSelection += "&&" + config.getFullSelection(self.photons.split(".")[0], extraSel)
else:
alg.objectSelection = config.getFullSelection(self.photons.split(".")[0], extraSel)
alg.minPt = self.check_float(items[2])
alg.sign = self.check_sign(items[3])
alg.count = self.check_int(items[4])
self.setDecorationName(alg, config, f'{thisalg}_%SYS%')
return
def add_NTAU_selector(self, text, config):
items = text.split()
if items[0] != "TAU_N":
self.raise_misconfig(text, "TAU_N")
if len(items) != 4 and len(items) != 5:
self.raise_misconfig(text, "number of arguments")

Baptiste Ravina
committed
if not self.taus:
self.raise_missinginput("taus")
thisalg = f'{self.name}_NTAU_{self.step}'
alg = config.createAlgorithm('CP::NObjectPtSelectorAlg', thisalg)
alg.particles, alg.objectSelection = config.readNameAndSelection(self.taus)
alg.eventPreselection = self.checkDecorationName(self.currentDecoration)
if len(items) == 4:
alg.minPt = self.check_float(items[1])
alg.sign = self.check_sign(items[2])
alg.count = self.check_int(items[3])
elif len(items) == 5:
extraSel = self.check_string(items[1])
if alg.objectSelection:
alg.objectSelection += "&&" + config.getFullSelection(self.taus.split(".")[0], extraSel)
else:
alg.objectSelection = config.getFullSelection(self.taus.split(".")[0], extraSel)
alg.minPt = self.check_float(items[2])
alg.sign = self.check_sign(items[3])
alg.count = self.check_int(items[4])
self.setDecorationName(alg, config, f'{thisalg}_%SYS%')
return
def add_NLJET_selector(self, text, config):
items = text.split()
if items[0] != "LJET_N":
self.raise_misconfig(text, "LJET_N")
if len(items) != 4 and len(items) != 5:
self.raise_misconfig(text, "number of arguments")
thisalg = f'{self.name}_NLJET_{self.step}'
alg = config.createAlgorithm('CP::NObjectPtSelectorAlg', thisalg)
alg.particles, alg.objectSelection = config.readNameAndSelection(self.largeRjets)
alg.eventPreselection = self.checkDecorationName(self.currentDecoration)
if len(items) == 4:
alg.minPt = self.check_float(items[1])
alg.sign = self.check_sign(items[2])
alg.count = self.check_int(items[3])
elif len(items) == 5:
extraSel = self.check_string(items[1])
if alg.objectSelection:
alg.objectSelection += "&&" + config.getFullSelection(self.largeRjets.split(".")[0], extraSel)
else:
alg.objectSelection = config.getFullSelection(self.largeRjets.split(".")[0], extraSel)
alg.minPt = self.check_float(items[2])
alg.sign = self.check_sign(items[3])
alg.count = self.check_int(items[4])
self.setDecorationName(alg, config, f'{thisalg}_%SYS%')
return
def add_NLJETMASS_selector(self, text, config):
items = text.split()
if items[0] != "LJETMASS_N":
self.raise_misconfig(text, "LJETMASS_N")
if len(items) != 4 and len(items) != 5:
self.raise_misconfig(text, "number of arguments")
thisalg = f'{self.name}_NLJETMASS_{self.step}'
alg = config.createAlgorithm('CP::NObjectMassSelectorAlg', thisalg)
alg.particles, alg.objectSelection = config.readNameAndSelection(self.largeRjets)
alg.eventPreselection = self.checkDecorationName(self.currentDecoration)
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
if len(items) == 4:
alg.minMass = self.check_float(items[1])
alg.sign = self.check_sign(items[2])
alg.count = self.check_int(items[3])
elif len(items) == 5:
extraSel = self.check_string(items[1])
if alg.objectSelection:
alg.objectSelection += "&&" + config.getFullSelection(self.largeRjets.split(".")[0], extraSel)
else:
alg.objectSelection = config.getFullSelection(self.largeRjets.split(".")[0], extraSel)
alg.minMass = self.check_float(items[2])
alg.sign = self.check_sign(items[3])
alg.count = self.check_int(items[4])
self.setDecorationName(alg, config, f'{thisalg}_%SYS%')
return
def add_NLJETMASSWINDOW_selector(self, text, config):
items = text.split()
if items[0] != "LJETMASSWINDOW_N":
self.raise_misconfig(text, "LJETMASSWINDOW_N")
if len(items) != 5 and len(items) != 6 and len(items) != 7:
self.raise_misconfig(text, "number of arguments")
thisalg = f'{self.name}_NLJETMASSWINDOW_{self.step}'
alg = config.createAlgorithm('CP::NLargeRJetMassWindowSelectorAlg', thisalg)
alg.ljets, alg.ljetSelection = config.readNameAndSelection(self.largeRjets)
if len(items) == 5 or (len(items) == 6 and "veto" in items):
alg.lowMass = self.check_float(items[1])
alg.highMass = self.check_float(items[2])
alg.sign = self.check_sign(items[3])
alg.count = self.check_int(items[4])
alg.vetoMode = (len(items) == 6 and self.check_string(items[5]) == "veto")
elif (len(items) == 6 and "veto" not in items) or len(items) == 7:
extraSel = self.check_string(items[1])
if alg.ljetSelection:
alg.ljetSelection += "&&" + config.getFullSelection(self.largeRjets.split(".")[0], extraSel)
else:
alg.ljetSelection = config.getFullSelection(self.largeRjets.split(".")[0], extraSel)
alg.lowMass = self.check_float(items[2])
alg.highMass = self.check_float(items[3])
alg.sign = self.check_sign(items[4])
alg.count = self.check_int(items[5])
alg.vetoMode = (len(items) ==7 and self.check_string(items[6]) == "veto")
alg.eventPreselection = self.checkDecorationName(self.currentDecoration)
self.setDecorationName(alg, config, f'{thisalg}_%SYS%')
return
def add_MET_selector(self, text, config):
items = text.split()
if items[0] != "MET":
self.raise_misconfig(text, "MET")
if len(items) != 3:
self.raise_misconfig(text, "number of arguments")

Baptiste Ravina
committed
if not self.met:
self.raise_missinginput("MET")
thisalg = f'{self.name}_MET_{self.step}'
alg = config.createAlgorithm('CP::MissingETSelectorAlg', thisalg)
alg.met = config.readName(self.met)
alg.metTerm = self.metTerm
alg.sign = self.check_sign(items[1])
alg.refMET = self.check_float(items[2])
alg.eventPreselection = self.checkDecorationName(self.currentDecoration)
self.setDecorationName(alg, config, f'{thisalg}_%SYS%')
return
def add_MWT_selector(self, text, config):
items = text.split()
if items[0] != "MWT":
self.raise_misconfig(text, "MWT")
if len(items) != 3:
self.raise_misconfig(text, "number of arguments")

Baptiste Ravina
committed
if not self.electrons and not self.muons:
self.raise_missinginput("electrons or muons")
thisalg = f'{self.name}_MWT_{self.step}'
alg = config.createAlgorithm('CP::TransverseMassSelectorAlg', thisalg)
alg.met = config.readName(self.met)
alg.metTerm = self.metTerm
alg.electrons, alg.electronSelection = config.readNameAndSelection(self.electrons)
alg.muons, alg.muonSelection = config.readNameAndSelection(self.muons)
alg.sign = self.check_sign(items[1])
alg.refMWT = self.check_float(items[2])
alg.eventPreselection = self.checkDecorationName(self.currentDecoration)
self.setDecorationName(alg, config, f'{thisalg}_%SYS%')
return
def add_METMWT_selector(self, text, config):
items = text.split()
if items[0] != "MET+MWT":
self.raise_misconfig(text, "MET+MWT")
if len(items) != 3:
self.raise_misconfig(text, "number of arguments")

Baptiste Ravina
committed
if not self.met:
self.raise_missinginput("MET")
if not self.electrons and not self.muons:
self.raise_missinginput("electrons or muons")
thisalg = f'{self.name}_METMWT_{self.step}'
alg = config.createAlgorithm('CP::MissingETPlusTransverseMassSelectorAlg', thisalg)
alg.met = config.readName(self.met)
alg.metTerm = self.metTerm
alg.electrons, alg.electronSelection = config.readNameAndSelection(self.electrons)
alg.muons, alg.muonSelection = config.readNameAndSelection(self.muons)
alg.sign = self.check_sign(items[1])
alg.refMETMWT = self.check_float(items[2])
alg.eventPreselection = self.checkDecorationName(self.currentDecoration)
self.setDecorationName(alg, config, f'{thisalg}_%SYS%')
return
def add_MLL_selector(self, text, config):
items = text.split()
if items[0] != "MLL":
self.raise_misconfig(text, "MLL")
if len(items) != 3:
self.raise_misconfig(text, "number of arguments")

Baptiste Ravina
committed
if not self.electrons and not self.muons:
self.raise_missinginput("electrons or muons")
thisalg = f'{self.name}_MLL_{self.step}'
alg = config.createAlgorithm('CP::DileptonInvariantMassSelectorAlg', thisalg)

Baptiste Ravina
committed
if self.electrons:
alg.electrons, alg.electronSelection = config.readNameAndSelection(self.electrons)
if self.muons:
alg.muons, alg.muonSelection = config.readNameAndSelection(self.muons)
alg.sign = self.check_sign(items[1])
alg.refMLL = self.check_float(items[2])
alg.eventPreselection = self.checkDecorationName(self.currentDecoration)
self.setDecorationName(alg, config, f'{thisalg}_%SYS%')
return
def add_MLLWINDOW_selector(self, text, config):
items = text.split()
if items[0] != "MLLWINDOW":
self.raise_misconfig(text, "MLLWINDOW")
if len(items) != 3 and len(items) != 4:
self.raise_misconfig(text, "number of arguments")

Baptiste Ravina
committed
if not self.electrons and not self.muons:
self.raise_missinginput("electrons or muons")
thisalg = f'{self.name}_MLLWINDOW_{self.step}'
alg = config.createAlgorithm('CP::DileptonInvariantMassWindowSelectorAlg', thisalg)

Baptiste Ravina
committed
if self.electrons:
alg.electrons, alg.electronSelection = config.readNameAndSelection(self.electrons)
if self.muons:
alg.muons, alg.muonSelection = config.readNameAndSelection(self.muons)
alg.lowMLL = self.check_float(items[1])
alg.highMLL = self.check_float(items[2])
alg.vetoMode = (len(items) == 4 and self.check_string(items[3]) == "veto")
alg.eventPreselection = self.checkDecorationName(self.currentDecoration)
self.setDecorationName(alg, config, f'{thisalg}_%SYS%')
return
def add_OS_selector(self, text, config):
items = text.split()
if len(items) != 1:
self.raise_misconfig(text, "number of arguments")

Baptiste Ravina
committed
if not self.electrons and not self.muons:
self.raise_missinginput("electrons or muons")
thisalg = f'{self.name}_OS_{self.step}'
alg = config.createAlgorithm('CP::ChargeSelectorAlg', thisalg)

Baptiste Ravina
committed
if self.electrons:
if "Particle" in self.electrons or "Truth" in self.electrons:
alg.truthElectrons, alg.truthElectronSelection = config.readNameAndSelection(self.electrons)
else:
alg.electrons, alg.electronSelection = config.readNameAndSelection(self.electrons)

Baptiste Ravina
committed
if self.muons:
if "Particle" in self.muons or "Truth" in self.muons:
alg.truthMuons, alg.truthMuonSelection = config.readNameAndSelection(self.muons)
else:
alg.muons, alg.muonSelection = config.readNameAndSelection(self.muons)
alg.eventPreselection = self.checkDecorationName(self.currentDecoration)
self.setDecorationName(alg, config, f'{thisalg}_%SYS%')
return
def add_SS_selector(self, text, config):
items = text.split()
if len(items) != 1:
self.raise_misconfig(text, "number of arguments")

Baptiste Ravina
committed
if not self.electrons and not self.muons:
self.raise_missinginput("electrons or muons")
thisalg = f'{self.name}_SS_{self.step}'
alg = config.createAlgorithm('CP::ChargeSelectorAlg', thisalg)

Baptiste Ravina
committed
if self.electrons:
if "Particle" in self.electrons or "Truth" in self.electrons:
alg.truthElectrons, alg.truthElectronSelection = config.readNameAndSelection(self.electrons)
else:
alg.electrons, alg.electronSelection = config.readNameAndSelection(self.electrons)

Baptiste Ravina
committed
if self.muons:
if "Particle" in self.muons or "Truth" in self.muons:
alg.truthMuons, alg.truthMuonSelection = config.readNameAndSelection(self.muons)
else:
alg.muons, alg.muonSelection = config.readNameAndSelection(self.muons)
alg.eventPreselection = self.checkDecorationName(self.currentDecoration)
self.setDecorationName(alg, config, f'{thisalg}_%SYS%')
return
def add_MLL_OSSF_selector(self, text, config):
items = text.split()
if items[0] != "MLL_OSSF":
self.raise_misconfig(text, "MLL_OSSF")
if len(items) != 3 and len(items) != 4:
self.raise_misconfig(text, "number of arguments")
if not self.electrons and not self.muons:
self.raise_missinginput("electrons or muons")
thisalg = f'{self.name}_MLL_OSSF_{self.step}'
alg = config.createAlgorithm('CP::DileptonOSSFInvariantMassWindowSelectorAlg', thisalg)
if self.electrons:
if "Particle" in self.electrons or "Truth" in self.electrons:
alg.truthElectrons, alg.truthElectronSelection = config.readNameAndSelection(self.electrons)
else:
alg.electrons, alg.electronSelection = config.readNameAndSelection(self.electrons)
if "Particle" in self.muons or "Truth" in self.muons:
alg.truthMuons, alg.truthMuonSelection = config.readNameAndSelection(self.muons)
else:
alg.muons, alg.muonSelection = config.readNameAndSelection(self.muons)
alg.lowMll = self.check_float(items[1])
alg.highMll = self.check_float(items[2])
alg.vetoMode = (len(items) == 4 and self.check_string(items[3]) == "veto")
alg.eventPreselection = self.checkDecorationName(self.currentDecoration)
self.setDecorationName(alg, config, f'{thisalg}_%SYS%')
return

Baptiste Ravina
committed
def add_EVENTFLAG(self, text, config):
items = text.split()
if items[0] != "EVENTFLAG":
self.raise_misconfig(text, "EVENTFLAG")
if len(items) != 2:
self.raise_misconfig(text, "number of arguments")
existingDecoration = self.check_string(items[1])
self.setDecorationName(None, config, existingDecoration)
return
def add_GLOBALTRIGMATCH(self, text, config):
items = text.split()
if items[0] != "GLOBALTRIGMATCH":
self.raise_misconfig(text, "GLOBALTRIGMATCH")
if len(items) != 1:
self.raise_misconfig(text, "number of arguments")
self.setDecorationName(None, config, "globalTriggerMatch_dontsave_%SYS%,as_char")

Baptiste Ravina
committed
return
def add_SAVE(self, text, config):
items = text.split()
if items[0] != "SAVE":
self.raise_misconfig(text, "SAVE")
if len(items) != 1:
self.raise_misconfig(text, "number of arguments")
thisalg = f'{self.name}_SAVE'
alg = config.createAlgorithm('CP::SaveFilterAlg', thisalg)
alg.FilterDescription = f'events passing < {self.name} >'
alg.eventDecisionOutputDecoration = f'ignore_{self.name}_%SYS%'
alg.selection = self.checkDecorationName(self.currentDecoration)
alg.selectionName = f'pass_{self.name}_%SYS%,as_char' # this one is used as a selection
alg.decorationName = f'ntuplepass_{self.name}_%SYS%' # this one is saved to file
config.addOutputVar('EventInfo', f'ntuplepass_{self.name}_%SYS%', f'pass_{self.name}')
return
def makeEventSelectionConfig(seq,
name,
electrons=None, muons=None, jets=None,
photons=None, taus=None, met=None, metTerm=None,
btagDecoration=None, preselection=None,
selectionCuts=None, noFilter=None,
debugMode=None, cutFlowHistograms=None):
"""Create an event selection config block
Keyword arguments:
name -- the name defining this selection
electrons -- the electron container and selection
muons -- the muon container and selection
jets -- the jet container and selection
largeRjets -- the large-R jet container and selection
photons -- the photon container and selection
taus -- the tau-jet container and selection
metTerm -- the MET term to use (e.g. 'Final', 'NonInt')
btagDecoration -- the b-tagging decoration to use when defining b-jets
preselection -- optional event-wise selection flag to start from
selectionCuts -- a string listing one selection cut per line
noFilter -- whether to disable the event filter
debugMode -- enables saving all intermediate decorations
cutFlowHistograms -- whether to toggle event cutflow histograms per systematic
"""
config = EventSelectionConfig(name)
config.setOptionValue ('electrons', electrons)
config.setOptionValue ('muons', muons)
config.setOptionValue ('jets', jets)
config.setOptionValue ('largeRjets', largeRjets)
config.setOptionValue ('photons', photons)
config.setOptionValue ('taus', taus)
config.setOptionValue ('met', met)
config.setOptionValue ('metTerm', metTerm)
config.setOptionValue ('btagDecoration', btagDecoration)
config.setOptionValue ('preselection', preselection)
config.setOptionValue ('selectionCuts', selectionCuts)
config.setOptionValue ('noFilter', noFilter)
config.setOptionValue ('debugMode', debugMode)
seq.append(config)
# add event cutflow algorithm
if cutFlowHistograms:
makeEventCutFlowConfig(seq, 'EventInfo', selectionName='', postfix=name,
customSelections=name)
def makeMultipleEventSelectionConfigs(seq,
electrons=None, muons=None, jets=None,
photons=None, taus=None, met=None, metTerm=None,
btagDecoration=None, preselection=None,
selectionCutsDict=None, noFilter=None,
debugMode=None, cutFlowHistograms=None):
"""Create multiple event selection config blocks
Keyword arguments:
electrons -- the electron container and selection
muons -- the muon container and selection
jets -- the jet container and selection
largeRjets -- the large-R jet container and selection
photons -- the photon container and selection
taus -- the tau-jet container and selection
metTerm -- the MET term to use (e.g. 'Final', 'NonInt')
btagDecoration -- the b-tagging decoration to use when defining b-jets
preselection -- optional event-wise selection flag to start from
selectionCutsDict -- a dictionary with key the name of the selection and value a string listing one selection cut per line
noFilter -- whether to disable the event filter
debugMode -- enables saving all intermediate decorations
cutFlowHistograms -- whether to toggle event cutflow histograms per region and per systematic
"""
# handle the case where a user is only providing one selection
if len(list(selectionCutsDict.keys())) == 1:
name, selectionCuts = list(selectionCutsDict.items())[0]
makeEventSelectionConfig(seq, name, electrons, muons, jets, largeRjets, photons, taus, met, metTerm, btagDecoration, preselection, selectionCuts, noFilter=noFilter, debugMode=debugMode, cutFlowHistograms=cutFlowHistograms)
return
# first, we generate all the individual event selections
# !!! it's important to pass noFilter=True, to avoid applying the individual filters in series
for name, selectionCuts in selectionCutsDict.items():
makeEventSelectionConfig(seq, name, electrons, muons, jets, largeRjets, photons, taus, met, metTerm, btagDecoration, preselection, selectionCuts, noFilter=True, debugMode=debugMode, cutFlowHistograms=cutFlowHistograms)
# now we are ready to collect all the filters and apply their logical OR
# !!! subregions (name starts with "SUB") are not used in the final filtering
config = EventSelectionMergerConfig()
config.setOptionValue ('selections', [f'pass_{name}_%SYS%' for name in selectionCutsDict.keys() if not name.startswith("SUB")])
config.setOptionValue ('noFilter', noFilter)
seq.append(config)