5from copy
import deepcopy
6from dataclasses
import dataclass
7from functools
import partial
9from AnalysisAlgorithmsConfig.ConfigBlock
import ConfigBlock
10from AnalysisAlgorithmsConfig.ConfigSequence
import groupBlocks
11from AsgAnalysisAlgorithms.AsgAnalysisConfig
import EventCutFlowBlock
12from AnalysisAlgorithmsConfig.ConfigAccumulator
import DataType, ConfigDeprecationWarning
16 """Raised when an EXPR cut uses a syntactically valid but unimplemented
17 feature (an unknown variable or collection). Subclasses ValueError so the
18 framework's existing tolerance and `except ValueError` still apply."""
22 """Raised when an EXPR cut is internally inconsistent or ill-typed (wrong
23 operand count, an operation undefined for the given object, etc.)."""
26@dataclass(frozen=
True)
29 apply_btag: bool =
False
33@dataclass(frozen=True)
36 max_operands: int |
None
43 """Small recursive-descent parser for EXPR selector expressions."""
52 while pos < len(text):
56 f
"[EventSelectionConfig] EXPR: cannot parse near '{text[pos:]}'")
58 if match.lastgroup !=
"WS":
59 tokens.append((match.lastgroup, match.group()))
60 tokens.append((
"END",
""))
75 f
"[EventSelectionConfig] EXPR: expected {kind}, got '{token[1]}'")
85 return variable, separator, operands, sign, ref_value
88 variable = self.
_expect(
"ID")[1]
92 while self.
_peek()[0]
in (
"COMMA",
"PLUS"):
93 sep =
"," if self.
_advance()[0] ==
"COMMA" else "+"
96 elif sep != separator:
98 "[EventSelectionConfig] EXPR: cannot mix ',' and '+' separators")
101 return variable, separator, operands
106 if self.
_peek()[0] ==
"LB":
108 index = int(self.
_expect(
"NUM")[1])
114 if token[0]
not in (
"LT",
"GT",
"EQ",
"GE",
"LE"):
116 f
"[EventSelectionConfig] EXPR: expected a comparison operator, got '{token[1]}'")
121 if self.
_peek()[0] ==
"MINUS":
124 value = float(self.
_expect(
"NUM")[1])
125 return -value
if negative
else value
130 """ConfigBlock for merging the output of various selection streams"""
133 super(EventSelectionMergerConfig, self).
__init__()
135 self.setBlockName(
'EventSelectionMerger')
136 self.addDependency(
'EventSelection', required=
True)
137 self.addOption(
'noFilter',
False, type=bool,
138 info=
"do not apply an event filter, i.e. setting it to `False` "
139 "removes events not passing the full list of selection cuts.")
142 """Return the instance name for this block"""
150 selections = config.getContainerMeta(
'EventInfo',
'eventSelectionNames',
152 selections = [sel
for sel
in selections
if not sel.startswith(
"pass_SUB")]
154 alg = config.createAlgorithm(
'CP::SaveFilterAlg',
155 'EventSelectionMerger' + selections[0].
split(
"_%SYS%")[0])
156 alg.FilterDescription =
'events passing at least one EventSelection'
157 alg.eventDecisionOutputDecoration =
'ignore_anySelection_%SYS%'
158 alg.selection =
'||'.join([sel +
',as_char' for sel
in selections])
159 alg.noFilter = self.noFilter
160 alg.selectionName =
'pass_anySelection_%SYS%'
161 alg.decorationName =
'ntuplepass_anySelection_%SYS%'
165 """ConfigBlock for interpreting text-based event selections"""
170 "EL_N": (
"electrons",
"NEL",
"electrons"),
171 "MU_N": (
"muons",
"NMU",
"muons"),
172 "JET_N": (
"jets",
"NJET",
"jets"),
173 "PH_N": (
"photons",
"NPH",
"photons"),
174 "TAU_N": (
"taus",
"NTAU",
"tau-jets"),
175 "LJET_N": (
"largeRjets",
"NLJET",
"large-R jets"),
177 _KEYWORD_SPECS =
None
180 super(EventSelectionConfig, self).
__init__()
181 self.setBlockName(
'EventSelection')
182 self.addOption(
'selectionName',
'', type=str,
184 info=
"the name of the event selection, used to uniquely identify "
185 "the `EventSelectionConfig` block.",
186 meta={
'role':
'region'})
187 self.addOption(
'electrons',
"", type=str,
188 info=
"the input electron container, with a possible selection, in "
189 "the format `container` or `container.selection`.",
190 meta={
'role':
'containerRef'})
191 self.addOption(
'muons',
"", type=str,
192 info=
"the input muon container, with a possible selection, in the "
193 "format `container` or `container.selection`.",
194 meta={
'role':
'containerRef'})
195 self.addOption(
'jets',
"", type=str,
196 info=
"the input jet container, with a possible selection, in the "
197 "format `container` or `container.selection`.",
198 meta={
'role':
'containerRef'})
199 self.addOption(
'largeRjets',
"", type=str,
200 info=
"the large-R jet container, with a possible selection, in "
201 "the format `container` or `container.selection`.",
202 meta={
'role':
'containerRef'})
203 self.addOption(
'photons',
"", type=str,
204 info=
"the input photon container, with a possible selection, in "
205 "the format `container` or `container.selection`.",
206 meta={
'role':
'containerRef'})
207 self.addOption(
'taus',
"", type=str,
208 info=
"the input tau-jet container, with a possible selection, in "
209 "the format `container` or `container.selection`.",
210 meta={
'role':
'containerRef'})
211 self.addOption(
'met',
"", type=str,
212 info=
"the input MET container.",
213 meta={
'role':
'containerRef'})
214 self.addOption(
'metTerm',
"Final", type=str,
215 info=
"the MET term to use when computing MET-based quantities.")
216 self.addOption(
'btagDecoration',
"", type=str,
217 info=
"the b-tagging decoration to use when defining b-jets.")
218 self.addOption(
'preselection',
"", type=str,
219 info=
"the event-wise selection flag to start this event selection "
221 meta={
'role':
'region'})
222 self.addOption(
'selectionCuts',
"", type=str,
224 info=
"a single string listing one selection cut per line. "
225 "See [available keywords](https://topcptoolkit.docs.cern.ch/latest/settings/eventselection/#available-keywords).")
226 self.addOption(
'debugMode',
False, type=bool,
227 info=
"whether to create an output branch for every single line "
228 "of the selection cuts. Setting it to `False` only saves the"
230 self.addOption(
'useDressedProperties',
True, type=bool,
231 info=
"whether to use dressed truth electron and truth muon "
232 "kinematics rather than simple 4-vector kinematics.")
239 """Return the instance name for this block"""
243 """Map each keyword to its handler. Dispatch is an exact lookup on the
244 first token, which removes the ordering fragility of token-membership."""
260 "OS": partial(self.
_add_charge, osMode=
True, tag=
"OS"),
261 "SS": partial(self.
_add_charge, osMode=
False, tag=
"SS"),
270 for kw, (attr, tag, _noun)
in self.
_NOBJECT.items():
280 """Machine-readable description of every `selectionCuts` keyword.
283 * `info` (str, required): one-line description of the keyword.
284 * `args` (list): argument descriptors, in token order.
285 * `forms` (list of arg-lists): used instead of `args` for keywords
286 offering several fixed shapes that are not "required + optionals".
287 * `freeText` (True): everything after the keyword is a single
288 expression (EXPR only).
289 * `grammar` (dict): the vocabulary of that expression (EXPR only).
290 * `deprecated` (True): the keyword is deprecated (SAVE only).
292 Argument descriptor fields:
293 * `name` (str), `type` (str), and optionally `optional` (True),
294 `choices` (list), `pattern` (regex str), `signed` (True).
295 * `type` is one of 'str', 'float', 'int', 'sign', 'region', 'flag',
297 - 'sign' is one of `<` `>` `==` `>=` `<=`
298 - 'region' is the `selectionName` of another EventSelection
299 - 'flag' is a literal token, present or absent; the literal
300 is the arg's `name`, matched case-insensitively
301 - 'container' is a container reference, in the format `Name` or
303 * `signed` (True) marks a float that may be negative; every other
304 float goes through `check_float(requirePositive=True)`.
306 Filling rule (implemented by `parseArgs`):
307 1. `flag` arguments are lifted out of the token list first, by
308 case-insensitive match against the arg name.
309 2. Of what remains, required arguments are matched first; optional
310 ones are filled left-to-right with the surplus tokens, skipping an
311 optional whose `pattern` the candidate token does not match.
312 3. With `forms`, the first form whose token count and patterns fit
319 for kw, (_attr, _tag, noun)
in cls.
_NOBJECT.items():
321 'info': f
'Count {noun} above a pT threshold',
323 {
'name':
'sel',
'type':
'str',
'optional':
True},
324 {
'name':
'ptmin',
'type':
'float'},
325 {
'name':
'sign',
'type':
'sign'},
326 {
'name':
'count',
'type':
'int'},
331 'info':
'Count b-tagged jets above the default b-tagging working '
332 'point, or above a custom one given as `tagger:WP`',
334 {
'name':
'sel',
'type':
'str',
'optional':
True,
'pattern':
'^[^:]+$'},
335 {
'name':
'btag',
'type':
'str',
'optional':
True,
'pattern':
'^[^:]+:[^:]+$'},
336 {
'name':
'sign',
'type':
'sign'},
337 {
'name':
'count',
'type':
'int'},
341 'info':
'Count jets ghost-associated to a given particle, e.g. `B`, '
342 'or `B!C` to also veto a second ghost association',
344 {
'name':
'ghost',
'type':
'str',
'pattern':
'^[A-Za-z]+(![A-Za-z]+)?$'},
345 {
'name':
'ptmin',
'type':
'float',
'optional':
True},
346 {
'name':
'sign',
'type':
'sign'},
347 {
'name':
'count',
'type':
'int'},
351 'info':
'Count large-R jets ghost-associated to a given particle, e.g. '
352 '`B`, or `B!C` to also veto a second ghost association',
354 {
'name':
'ghost',
'type':
'str',
'pattern':
'^[A-Za-z]+(![A-Za-z]+)?$'},
355 {
'name':
'ptmin',
'type':
'float',
'optional':
True},
356 {
'name':
'sign',
'type':
'sign'},
357 {
'name':
'count',
'type':
'int'},
361 'info':
'Count large-R jets above a mass threshold',
363 {
'name':
'sel',
'type':
'str',
'optional':
True},
364 {
'name':
'minMass',
'type':
'float'},
365 {
'name':
'sign',
'type':
'sign'},
366 {
'name':
'count',
'type':
'int'},
369 'LJETMASSWINDOW_N': {
370 'info':
'Count large-R jets inside (or, with `veto`, outside) a mass window',
372 {
'name':
'sel',
'type':
'str',
'optional':
True},
373 {
'name':
'lowMass',
'type':
'float'},
374 {
'name':
'highMass',
'type':
'float'},
375 {
'name':
'sign',
'type':
'sign'},
376 {
'name':
'count',
'type':
'int'},
377 {
'name':
'veto',
'type':
'flag',
'optional':
True},
381 'info':
'Count objects of an arbitrary container above a pT threshold',
383 {
'name':
'container',
'type':
'container'},
384 {
'name':
'ptmin',
'type':
'float'},
385 {
'name':
'sign',
'type':
'sign'},
386 {
'name':
'count',
'type':
'int'},
390 'info':
'Count electrons and muons together, above a common or '
391 'per-flavour pT threshold',
394 {
'name':
'ptmin',
'type':
'float'},
395 {
'name':
'sign',
'type':
'sign'},
396 {
'name':
'count',
'type':
'int'},
399 {
'name':
'ptEl',
'type':
'float'},
400 {
'name':
'ptMu',
'type':
'float'},
401 {
'name':
'sign',
'type':
'sign'},
402 {
'name':
'count',
'type':
'int'},
405 {
'name':
'selEl',
'type':
'str'},
406 {
'name':
'selMu',
'type':
'str'},
407 {
'name':
'ptEl',
'type':
'float'},
408 {
'name':
'ptMu',
'type':
'float'},
409 {
'name':
'sign',
'type':
'sign'},
410 {
'name':
'count',
'type':
'int'},
414 'SUM_EL_N_MU_N_TAU_N': {
415 'info':
'Count electrons, muons and tau-jets together, above a common '
416 'or per-flavour pT threshold',
419 {
'name':
'ptmin',
'type':
'float'},
420 {
'name':
'sign',
'type':
'sign'},
421 {
'name':
'count',
'type':
'int'},
424 {
'name':
'ptEl',
'type':
'float'},
425 {
'name':
'ptMu',
'type':
'float'},
426 {
'name':
'ptTau',
'type':
'float'},
427 {
'name':
'sign',
'type':
'sign'},
428 {
'name':
'count',
'type':
'int'},
431 {
'name':
'selEl',
'type':
'str'},
432 {
'name':
'selMu',
'type':
'str'},
433 {
'name':
'selTau',
'type':
'str'},
434 {
'name':
'ptEl',
'type':
'float'},
435 {
'name':
'ptMu',
'type':
'float'},
436 {
'name':
'ptTau',
'type':
'float'},
437 {
'name':
'sign',
'type':
'sign'},
438 {
'name':
'count',
'type':
'int'},
443 'info':
'Cut on the missing transverse energy',
445 {
'name':
'sign',
'type':
'sign'},
446 {
'name':
'refMET',
'type':
'float'},
450 'info':
'Cut on the transverse mass of the leading lepton and MET',
452 {
'name':
'sign',
'type':
'sign'},
453 {
'name':
'refMWT',
'type':
'float'},
457 'info':
'Cut on the sum of the missing transverse energy and the '
460 {
'name':
'sign',
'type':
'sign'},
461 {
'name':
'refMETMWT',
'type':
'float'},
465 'info':
'Cut on the dilepton invariant mass',
467 {
'name':
'sign',
'type':
'sign'},
468 {
'name':
'refMLL',
'type':
'float'},
472 'info':
'Require the dilepton invariant mass inside (or, with `veto`, '
473 'outside) a mass window',
475 {
'name':
'lowMLL',
'type':
'float'},
476 {
'name':
'highMLL',
'type':
'float'},
477 {
'name':
'veto',
'type':
'flag',
'optional':
True},
481 'info':
'Require the opposite-sign same-flavour dilepton invariant mass '
482 'inside (or, with `veto`, outside) a mass window',
484 {
'name':
'lowMll',
'type':
'float'},
485 {
'name':
'highMll',
'type':
'float'},
486 {
'name':
'veto',
'type':
'flag',
'optional':
True},
490 'info':
'Require an opposite-sign lepton pair; without any flag, all '
491 'available lepton flavours are considered',
493 {
'name':
'el',
'type':
'flag',
'optional':
True},
494 {
'name':
'mu',
'type':
'flag',
'optional':
True},
495 {
'name':
'tau',
'type':
'flag',
'optional':
True},
499 'info':
'Require a same-sign lepton pair; without any flag, all '
500 'available lepton flavours are considered',
502 {
'name':
'el',
'type':
'flag',
'optional':
True},
503 {
'name':
'mu',
'type':
'flag',
'optional':
True},
504 {
'name':
'tau',
'type':
'flag',
'optional':
True},
508 'info':
'Import all the cuts of a previously defined event selection',
510 {
'name':
'region',
'type':
'region'},
514 'info':
'Require an existing event-wise decoration to be true',
516 {
'name':
'decoration',
'type':
'str'},
520 'info':
'Require the global trigger matching decision, optionally for '
521 'a given trigger-configuration postfix',
523 {
'name':
'postfix',
'type':
'str',
'optional':
True},
527 'info':
'Cut on the (random) run number',
529 {
'name':
'sign',
'type':
'sign'},
530 {
'name':
'runNumber',
'type':
'int'},
534 'info':
'Cut on an existing EventInfo scalar variable, e.g. a DNN or '
537 {
'name':
'type',
'type':
'str',
'choices': sorted(cls.
_EVENTVAR_TYPES)},
538 {
'name':
'name',
'type':
'str'},
539 {
'name':
'sign',
'type':
'sign'},
540 {
'name':
'value',
'type':
'float',
'signed':
True},
544 'info':
'Cut on a generic object-kinematic expression, e.g. '
545 '`dR(el[0],jet[0]) > 0.4`',
553 'info':
'Deprecated and ignored: the event filter is now emitted '
554 'automatically at the end of every event selection',
564 """Return an independent copy of the keyword specification table."""
569 """Return the set of valid total token counts (leading keyword included)
570 allowed by a keyword specification, or None when the keyword takes free
571 text and no count check applies."""
572 if spec.get(
'freeText'):
574 forms = spec.get(
'forms')
or [spec.get(
'args', [])]
577 min_args = sum(1
for arg
in form
if not arg.get(
'optional'))
579 counts.update(n + 1
for n
in range(min_args, max_args + 1))
583 existing = config.getContainerMeta(
'EventInfo',
'eventSelectionNames', defaultValue=[])
584 config.setContainerMeta(
'EventInfo',
'eventSelectionNames',
585 existing + [f
'pass_{self.selectionName}_%SYS%'], allowOverwrite=
True)
594 if self.selectionCuts
is None:
595 raise ValueError (
"[EventSelectionConfig] You must provide the 'selectionCuts' option to 'EventSelectionConfig': "
596 "a single string where each line represents a different selection cut to apply in order.")
597 for line
in self.selectionCuts.
split(
"\n"):
606 if not text
or text.startswith(
"#"):
609 keyword = text.split()[0]
612 raise ValueError (f
"[EventSelectionConfig] The following selection cut is not recognised! --> {text}")
620 raise ValueError (f
"[EventSelectionConfig] Misconfiguration! Check {keyword} in: {text}")
623 raise ValueError (f
"[EventSelectionConfig] Misconfiguration! Missing input collection for {collection}")
626 """Validate the leading keyword and the number of arguments, the latter
627 derived from the keyword specification table."""
628 if items[0] != keyword:
631 if validCounts
is not None and len(items)
not in validCounts:
635 """Remove flag tokens from `tokens` and return their boolean values."""
636 tokens = list(tokens)
639 if arg.get(
'type') !=
'flag':
641 values[arg[
'name']] =
False
642 for i, token
in enumerate(tokens):
643 if token.lower() == arg[
'name'].lower():
645 values[arg[
'name']] =
True
647 return tokens, values
650 """Match one keyword argument form, returning values or None."""
652 positional = [arg
for arg
in form
if arg.get(
'type') !=
'flag']
653 nRequired = sum(1
for arg
in positional
if not arg.get(
'optional'))
654 if len(tokens) < nRequired
or len(tokens) > len(positional):
657 budget = len(tokens) - nRequired
659 for arg
in positional:
660 if not arg.get(
'optional'):
661 values[arg[
'name']] = tokens[cursor]
664 pattern = arg.get(
'pattern')
665 if budget > 0
and (pattern
is None or re.fullmatch(pattern, tokens[cursor])):
666 values[arg[
'name']] = tokens[cursor]
670 values[arg[
'name']] =
''
671 return values
if cursor == len(tokens)
else None
674 """Turn the already-split token list `items` (leading keyword included)
675 into a dict of argument name -> value, driven by `keywordSpecs`, which
676 is authoritative for the argument grammar."""
679 if spec.get(
'freeText'):
680 return {
'text':
' '.join(items[1:])}
681 for form
in (spec.get(
'forms')
or [spec.get(
'args', [])]):
683 if values
is not None:
690 if not requirePositive
or value >= 0:
693 raise ValueError (f
"[EventSelectionConfig] Misconfiguration! Float {test} is not positive!")
695 raise ValueError (f
"[EventSelectionConfig] Misconfiguration! {test} should be a float, not {type(test)}!")
699 numeric = float(test)
701 except (TypeError, ValueError):
702 raise ValueError (f
"[EventSelectionConfig] Misconfiguration! {test} should be an int, not {type(test)}")
704 raise ValueError (f
"[EventSelectionConfig] Misconfiguration! {test} should be an int, not a float!")
705 if requirePositive
and value < 0:
706 raise ValueError (f
"[EventSelectionConfig] Misconfiguration! Int {test} is not positive!")
710 if not isinstance(test, str):
711 raise ValueError (f
"[EventSelectionConfig] Misconfiguration! {test} should be a string, not a number!")
726 raise KeyError (f
"[EventSelectionConfig] Misconfiguration! {test} should be one of {list(mapping.keys())}")
729 test = test.split(
":")
731 raise ValueError (f
"[EventSelectionConfig] Misconfiguration! {test} should be provided as 'btagger:btagWP'")
737 values = test.split(
"!")
739 "B":
"GhostBHadronsFinalCount",
740 "C":
"GhostCHadronsFinalCount",
741 "T":
"GhostTQuarksFinalCount",
742 "W":
"GhostWBosonsCount",
743 "Z":
"GhostZBosonsCount",
744 "H":
"GhostHBosonsCount",
745 "TAU":
"GhostTausFinalCount"
747 return [ghost_map.get(value.upper(), value)
for value
in values]
757 self.
cutflow.append( decoration )
758 if algorithm
is not None:
759 algorithm.decorationName = f
'{decoration},as_char'
762 config.addOutputVar(
'EventInfo', decoration, decoration.split(
"_%SYS%")[0])
768 config.addSelection(
'EventInfo',
'', decoration)
773 decoration = decoration.split(
"&&")
774 decoration = [sub +
',as_char' if ',as_char' not in sub
else sub
for sub
in decoration]
775 return '&&'.join(decoration)
779 return oldSelection +
"&&" + config.getFullSelection(container, newSelection)
781 return config.getFullSelection(container, newSelection)
788 """Require at least one of the named input options to be configured."""
789 if not any(getattr(self, name)
for name
in names):
793 """Apply the common event-preselection and output decoration."""
798 """Configure reco/truth lepton handles from explicit metadata."""
799 for spec, reco, truth
in leptons:
802 self.
_maybe_dressed(alg, *(spec
for spec, _reco, _truth
in leptons))
805 """Enable dressed kinematics when any of the given electron/muon
806 containers is a truth container. Dressed kinematics only exist for
807 truth electrons and muons, so only those specs should be passed here."""
808 if any(spec
and (
"Particle" in spec
or "Truth" in spec)
for spec
in specs):
809 alg.useDressedProperties = self.useDressedProperties
812 """Assign (name, selection) to the reco or truth handles of `alg`
813 depending on whether `spec` points to a truth container.
814 `reco`/`truth` are (nameAttr, selectionAttr) pairs."""
815 name, sel = config.readNameAndSelection(spec)
816 nameAttr, selAttr = truth
if (
"Particle" in spec
or "Truth" in spec)
else reco
817 setattr(alg, nameAttr, name)
818 setattr(alg, selAttr, sel)
825 """Generic builder for the N-object pT selectors (EL_N, MU_N, JET_N,
826 PH_N, TAU_N, LJET_N): identical except for the source container, which
827 is always required since the cut acts on it."""
829 spec = getattr(self, attr)
833 thisalg = f
'{self.selectionName}_{tag}_{self.step}'
834 alg = config.createAlgorithm(
'CP::NObjectPtSelectorAlg', thisalg)
835 alg.particles, alg.objectSelection = config.readNameAndSelection(spec)
836 if attr
in (
"electrons",
"muons"):
840 config, spec.split(
".")[0], alg.objectSelection,
844 alg.count = self.
check_int(args[
'count'])
849 args = self.
parseArgs(
"IMPORT", text.split())
854 self.
currentDecoration = f
'{self.currentDecoration},as_char&&pass_{region}_%SYS%'
856 imported_cuts = [cut
for cut
in config.getSelectionCutFlow(
'EventInfo',
'')
if cut.startswith(region)]
860 args = self.
parseArgs(
"JET_N_BTAG", text.split())
862 thisalg = f
'{self.selectionName}_NBJET_{self.step}'
863 alg = config.createAlgorithm(
'CP::NObjectPtSelectorAlg', thisalg)
864 particles, selection = config.readNameAndSelection(self.jets)
865 alg.particles = particles
866 alg.objectSelection = f
'{selection}&&{self.btagDecoration},as_char' if selection
else f
'{self.btagDecoration},as_char'
869 customBtag = f
'ftag_select_{btagger}_{btagWP}'
870 alg.objectSelection = f
'{selection}&&{customBtag},as_char' if selection
else f
'{customBtag},as_char'
875 alg.count = self.
check_int(args[
'count'])
879 args = self.
parseArgs(
"SUM_EL_N_MU_N", text.split())
881 thisalg = f
'{self.selectionName}_SUMNELNMU_{self.step}'
882 alg = config.createAlgorithm(
'CP::SumNLeptonPtSelectorAlg', thisalg)
883 alg.electrons, alg.electronSelection = config.readNameAndSelection(self.
electrons)
884 alg.muons, alg.muonSelection = config.readNameAndSelection(self.
muons)
886 if args.get(
'ptmin')
is not None:
891 if args.get(
'selEl'):
894 if args.get(
'selMu'):
900 alg.count = self.
check_int(args[
'count'])
904 args = self.
parseArgs(
"SUM_EL_N_MU_N_TAU_N", text.split())
905 self.
_require_inputs(
"electrons",
"muons",
"taus", message=
"electrons, muons or taus")
906 thisalg = f
'{self.selectionName}_SUMNLEPTONS_{self.step}'
907 alg = config.createAlgorithm(
'CP::SumNLeptonPtSelectorAlg', thisalg)
908 alg.electrons, alg.electronSelection = config.readNameAndSelection(self.
electrons)
909 alg.muons, alg.muonSelection = config.readNameAndSelection(self.
muons)
910 alg.taus, alg.tauSelection = config.readNameAndSelection(self.
taus)
912 if args.get(
'ptmin')
is not None:
918 if args.get(
'selEl'):
921 if args.get(
'selMu'):
924 if args.get(
'selTau'):
931 alg.count = self.
check_int(args[
'count'])
935 args = self.
parseArgs(
"LJETMASS_N", text.split())
936 thisalg = f
'{self.selectionName}_NLJETMASS_{self.step}'
937 alg = config.createAlgorithm(
'CP::NObjectMassSelectorAlg', thisalg)
938 alg.particles, alg.objectSelection = config.readNameAndSelection(self.largeRjets)
941 config, self.largeRjets.
split(
".")[0], alg.objectSelection,
945 alg.count = self.
check_int(args[
'count'])
949 args = self.
parseArgs(
"LJETMASSWINDOW_N", text.split())
950 thisalg = f
'{self.selectionName}_NLJETMASSWINDOW_{self.step}'
951 alg = config.createAlgorithm(
'CP::NLargeRJetMassWindowSelectorAlg', thisalg)
952 alg.ljets, alg.ljetSelection = config.readNameAndSelection(self.largeRjets)
959 alg.count = self.
check_int(args[
'count'])
960 alg.vetoMode = args[
'veto']
964 args = self.
parseArgs(
"JET_N_GHOST", text.split())
965 thisalg = f
'{self.selectionName}_NJETGHOST_{self.step}'
966 alg = config.createAlgorithm(
'CP::JetNGhostSelectorAlg', thisalg)
967 alg.jets, alg.jetSelection = config.readNameAndSelection(self.jets)
969 alg.ghost = ghosts[0]
975 alg.count = self.
check_int(args[
'count'])
979 args = self.
parseArgs(
"LJET_N_GHOST", text.split())
980 thisalg = f
'{self.selectionName}_NLJETGHOST_{self.step}'
981 alg = config.createAlgorithm(
'CP::JetNGhostSelectorAlg', thisalg)
982 alg.jets, alg.jetSelection = config.readNameAndSelection(self.largeRjets)
984 alg.ghost = ghosts[0]
990 alg.count = self.
check_int(args[
'count'])
994 args = self.
parseArgs(
"OBJ_N", text.split())
995 thisalg = f
'{self.selectionName}_NOBJ_{self.step}'
996 alg = config.createAlgorithm(
'CP::NObjectPtSelectorAlg', thisalg)
997 alg.particles, alg.objectSelection = config.readNameAndSelection(self.
check_string(args[
'container']))
1000 alg.count = self.
check_int(args[
'count'])
1004 args = self.
parseArgs(
"MET", text.split())
1006 thisalg = f
'{self.selectionName}_MET_{self.step}'
1007 alg = config.createAlgorithm(
'CP::MissingETSelectorAlg', thisalg)
1008 alg.met = config.readName(self.
met)
1009 alg.metTerm = self.metTerm
1015 args = self.
parseArgs(
"MWT", text.split())
1017 thisalg = f
'{self.selectionName}_MWT_{self.step}'
1018 alg = config.createAlgorithm(
'CP::TransverseMassSelectorAlg', thisalg)
1019 alg.met = config.readName(self.
met)
1020 alg.metTerm = self.metTerm
1021 alg.electrons, alg.electronSelection = config.readNameAndSelection(self.
electrons)
1022 alg.muons, alg.muonSelection = config.readNameAndSelection(self.
muons)
1029 args = self.
parseArgs(
"MET+MWT", text.split())
1032 thisalg = f
'{self.selectionName}_METMWT_{self.step}'
1033 alg = config.createAlgorithm(
'CP::MissingETPlusTransverseMassSelectorAlg', thisalg)
1034 alg.met = config.readName(self.
met)
1035 alg.metTerm = self.metTerm
1036 alg.electrons, alg.electronSelection = config.readNameAndSelection(self.
electrons)
1037 alg.muons, alg.muonSelection = config.readNameAndSelection(self.
muons)
1040 alg.refMETMWT = self.
check_float(args[
'refMETMWT'])
1044 args = self.
parseArgs(
"MLL", text.split())
1046 thisalg = f
'{self.selectionName}_MLL_{self.step}'
1047 alg = config.createAlgorithm(
'CP::DileptonInvariantMassSelectorAlg', thisalg)
1049 (self.
electrons, (
'electrons',
'electronSelection'),
1050 (
'truthElectrons',
'truthElectronSelection')),
1051 (self.
muons, (
'muons',
'muonSelection'),
1052 (
'truthMuons',
'truthMuonSelection')),
1059 args = self.
parseArgs(
"MLLWINDOW", text.split())
1061 thisalg = f
'{self.selectionName}_MLLWINDOW_{self.step}'
1062 alg = config.createAlgorithm(
'CP::DileptonInvariantMassWindowSelectorAlg', thisalg)
1064 (self.
electrons, (
'electrons',
'electronSelection'),
1065 (
'truthElectrons',
'truthElectronSelection')),
1066 (self.
muons, (
'muons',
'muonSelection'),
1067 (
'truthMuons',
'truthMuonSelection')),
1071 alg.vetoMode = args[
'veto']
1075 """Builder shared by OS and SS: same algorithm, opposite charge mode."""
1076 args = self.
parseArgs(tag, text.split())
1078 thisalg = f
'{self.selectionName}_{tag}_{self.step}'
1079 alg = config.createAlgorithm(
'CP::ChargeSelectorAlg', thisalg)
1080 allLeptons =
not (args[
'el']
or args[
'mu']
or args[
'tau'])
1081 if self.
electrons and (allLeptons
or args[
'el']):
1083 (
'electrons',
'electronSelection'),
1084 (
'truthElectrons',
'truthElectronSelection'))
1085 if self.
muons and (allLeptons
or args[
'mu']):
1087 (
'muons',
'muonSelection'),
1088 (
'truthMuons',
'truthMuonSelection'))
1089 if self.
taus and (allLeptons
or args[
'tau']):
1091 (
'taus',
'tauSelection'),
1092 (
'truthTaus',
'truthTauSelection'))
1097 args = self.
parseArgs(
"MLL_OSSF", text.split())
1099 thisalg = f
'{self.selectionName}_MLL_OSSF_{self.step}'
1100 alg = config.createAlgorithm(
'CP::DileptonOSSFInvariantMassWindowSelectorAlg', thisalg)
1102 (self.
electrons, (
'electrons',
'electronSelection'),
1103 (
'truthElectrons',
'truthElectronSelection')),
1104 (self.
muons, (
'muons',
'muonSelection'),
1105 (
'truthMuons',
'truthMuonSelection')),
1109 alg.vetoMode = args[
'veto']
1113 args = self.
parseArgs(
"EVENTFLAG", text.split())
1114 existingDecoration = self.
check_string(args[
'decoration'])
1118 args = self.
parseArgs(
"GLOBALTRIGMATCH", text.split())
1120 self.
setDecorationName(
None, config, f
"globalTriggerMatch{postfix}_%SYS%,as_char")
1123 args = self.
parseArgs(
"RUN_NUMBER", text.split())
1124 thisalg = f
'{self.selectionName}_RUN_NUMBER_{self.step}'
1125 alg = config.createAlgorithm(
'CP::RunNumberSelectorAlg', thisalg)
1127 alg.runNumber = self.
check_int(args[
'runNumber'])
1128 alg.useRandomRunNumber = config.dataType()
is not DataType.Data
1159 _EXPR_TOKEN_RE = re.compile(
r"""
1160 (?P<NUM>\d+\.\d+(?:[eE][+-]?\d+)?|\d+[eE][+-]?\d+|\d+)
1161 | (?P<GE>>=) | (?P<LE><=) | (?P<EQ>==) | (?P<LT><) | (?P<GT>>)
1162 | (?P<ID>[A-Za-z_][A-Za-z0-9_]*)
1163 | (?P<LP>\() | (?P<RP>\)) | (?P<LB>\[) | (?P<RB>\])
1164 | (?P<COMMA>,) | (?P<PLUS>\+) | (?P<MINUS>-)
1178 f
"[EventSelectionConfig] EXPR: variable '{variable}' is not available. "
1179 "Please request it from the EventSelectionAlgorithms developers.")
1181 minN = var_spec.min_operands
1182 maxN = var_spec.max_operands
1183 sep = var_spec.separator
1184 metOk = var_spec.met_ok
1186 if n < minN
or (maxN
is not None and n > maxN):
1187 expected = f
"{minN}" if maxN == minN
else (f
"{minN}+" if maxN
is None else f
"{minN}-{maxN}")
1189 f
"[EventSelectionConfig] EXPR: '{variable}' takes {expected} operand(s), got {n}")
1190 if n > 1
and separator != sep:
1191 want = {
",":
"','",
"+":
"'+'"}.
get(sep, str(sep))
1193 f
"[EventSelectionConfig] EXPR: '{variable}' operands must be separated by {want}")
1194 for coll, index
in operands:
1197 f
"[EventSelectionConfig] EXPR: collection '{coll}' is not available. "
1198 "Please request it from the EventSelectionAlgorithms developers.")
1200 isMET = coll_spec.is_met
1204 f
"[EventSelectionConfig] EXPR: 'met' is not valid for '{variable}'")
1205 if index
is not None:
1207 "[EventSelectionConfig] EXPR: 'met' cannot be indexed")
1208 if separator ==
"+":
1210 "[EventSelectionConfig] EXPR: 'met' cannot be combined in a sum")
1213 f
"[EventSelectionConfig] EXPR: '{coll}' must be indexed, e.g. {coll}[0]")
1216 body = text[len(
"EXPR"):].
strip()
1222 thisalg = f
'{self.selectionName}_EXPR_{self.step}'
1223 alg = config.createAlgorithm(
'CP::ObjectKinematicSelectorAlg', thisalg)
1224 alg.variable = variable
1226 alg.refValue = refValue
1228 operandKinds, collections, selections, indices = [], [], [], []
1230 for coll, index
in operands:
1232 opt = coll_spec.option
1233 applyBtag = coll_spec.apply_btag
1234 isMET = coll_spec.is_met
1235 container = getattr(self, opt)
1239 operandKinds.append(
"MET")
1242 operandKinds.append(
"PARTICLE")
1243 name, selection = config.readNameAndSelection(container)
1247 selection = (f
'{selection}&&{self.btagDecoration},as_char'
1248 if selection
else f
'{self.btagDecoration},as_char')
1249 collections.append(name)
1250 selections.append(selection)
1251 indices.append(index)
1253 alg.operandKinds = operandKinds
1254 alg.collections = collections
1255 alg.selections = selections
1256 alg.indices = indices
1260 alg.met = config.readName(self.
met)
1261 alg.metTerm = self.metTerm
1267 _EVENTVAR_TYPES = {
"float":
"floatVariable",
"int":
"intVariable",
"double":
"doubleVariable"}
1271 args = self.
parseArgs(
"EVENTVAR", text.split())
1274 self.
raise_misconfig(text, f
"value type (one of {sorted(self._EVENTVAR_TYPES)})")
1276 thisalg = f
'{self.selectionName}_EVENTVAR_{self.step}'
1277 alg = config.createAlgorithm(
'CP::EventScalarSelectorAlg', thisalg)
1281 alg.refValue = self.
check_float(args[
'value'], requirePositive=
False)
1290 "[EventSelectionConfig] The 'SAVE' keyword is deprecated: the event "
1291 "filter is now created automatically at the end of each EventSelection "
1292 f
"block. Please remove the 'SAVE' line from selection '{self.selectionName}'.",
1293 category=ConfigDeprecationWarning, stacklevel=2)
1296 """Create the SaveFilterAlg that turns the accumulated event selection
1297 into a named, persisted selection (and ntuple branch). Called once per
1298 block, automatically at the end of makeAlgs."""
1299 thisalg = f
'{self.selectionName}_SAVE'
1300 alg = config.createAlgorithm(
'CP::SaveFilterAlg', thisalg)
1301 alg.FilterDescription = f
'events passing < {self.selectionName} >'
1302 alg.eventDecisionOutputDecoration = f
'ignore_{self.selectionName}_%SYS%'
1305 alg.selectionName = f
'pass_{self.selectionName}_%SYS%,as_char'
1306 alg.decorationName = f
'ntuplepass_{self.selectionName}_%SYS%'
1307 config.addOutputVar(
'EventInfo', f
'ntuplepass_{self.selectionName}_%SYS%', f
'pass_{self.selectionName}')
1313 seq.append(EventCutFlowBlock())
std::vector< std::string > tokenize(std::string_view the_str, std::string_view delimiters)
Splits the string into smaller substrings.
setDecorationName(self, algorithm, config, decoration)
add_SUMNELNMU_selector(self, text, config)
raise_missinginput(self, collection)
extendObjectSelection(self, config, container, oldSelection, newSelection)
_extract_flags(self, form, tokens)
add_RUNNUMBER(self, text, config)
add_MLL_selector(self, text, config)
check_int(self, test, requirePositive=True)
add_METMWT_selector(self, text, config)
add_EVENTVAR_selector(self, text, config)
add_EVENTFLAG(self, text, config)
_add_charge(self, text, config, *, osMode, tag)
add_NLJETGHOST_selector(self, text, config)
add_NOBJ_selector(self, text, config)
add_MWT_selector(self, text, config)
_route_lepton(self, alg, config, spec, reco, truth)
add_SUMNLEPTONS_selector(self, text, config)
_check_args(self, items, keyword)
_require_inputs(self, *names, message=None)
_configure_leptons(self, alg, config, leptons)
add_IMPORT(self, text, config)
_add_nobject(self, text, config, *, attr, tag)
add_GLOBALTRIGMATCH(self, text, config)
parseArgs(self, keyword, items)
raise_misconfig(self, text, keyword)
_expr_parse(self, tokens)
_finish_selector(self, alg, config, name)
add_NBJET_selector(self, text, config)
_expr_validate(self, variable, separator, operands)
checkDecorationName(self, decoration)
add_NLJETMASS_selector(self, text, config)
check_float(self, test, requirePositive=True)
interpret(self, text, cfg)
_match_form(self, form, tokens)
_maybe_dressed(self, alg, *specs)
_expr_tokenize(self, text)
add_MET_selector(self, text, config)
check_btagging(self, test)
add_EXPR_selector(self, text, config)
add_MLLWINDOW_selector(self, text, config)
add_SAVE(self, text, config)
add_NJETGHOST_selector(self, text, config)
add_NLJETMASSWINDOW_selector(self, text, config)
add_MLL_OSSF_selector(self, text, config)
T * get(TKey *tobj)
get a TObject* from a TKey* (why can't a TObject be a TKey?)
std::vector< std::string > split(const std::string &s, const std::string &t=":")
bool match(std::string s1, std::string s2)
match the individual directories of two strings
void handler(int sig)
signal handler