270 def _keywordSpecs(cls) -> dict:
271 """Machine-readable description of every `selectionCuts` keyword.
272
273 Spec fields:
274 * `info` (str, required): one-line description of the keyword.
275 * `args` (list): argument descriptors, in token order.
276 * `forms` (list of arg-lists): used instead of `args` for keywords
277 offering several fixed shapes that are not "required + optionals".
278 * `freeText` (True): everything after the keyword is a single
279 expression (EXPR only).
280 * `grammar` (dict): the vocabulary of that expression (EXPR only).
281 * `deprecated` (True): the keyword is deprecated (SAVE only).
282
283 Argument descriptor fields:
284 * `name` (str), `type` (str), and optionally `optional` (True),
285 `choices` (list), `pattern` (regex str), `signed` (True).
286 * `type` is one of 'str', 'float', 'int', 'sign', 'region', 'flag',
287 'container', where:
288 - 'sign' is one of `<` `>` `==` `>=` `<=`
289 - 'region' is the `selectionName` of another EventSelection
290 - 'flag' is a literal token, present or absent; the literal
291 is the arg's `name`, matched case-insensitively
292 - 'container' is a container reference, in the format `Name` or
293 `Name.selection`
294 * `signed` (True) marks a float that may be negative; every other
295 float goes through `check_float(requirePositive=True)`.
296
297 Filling rule (implemented by `parseArgs`):
298 1. `flag` arguments are lifted out of the token list first, by
299 case-insensitive match against the arg name.
300 2. Of what remains, required arguments are matched first; optional
301 ones are filled left-to-right with the surplus tokens, skipping an
302 optional whose `pattern` the candidate token does not match.
303 3. With `forms`, the first form whose token count and patterns fit
304 wins.
305 """
306 if cls._KEYWORD_SPECS is not None:
307 return cls._KEYWORD_SPECS
308
309 specs = {}
310 for kw, (_attr, _tag, noun) in cls._NOBJECT.items():
311 specs[kw] = {
312 'info': f'Count {noun} above a pT threshold',
313 'args': [
314 {'name': 'sel', 'type': 'str', 'optional': True},
315 {'name': 'ptmin', 'type': 'float'},
316 {'name': 'sign', 'type': 'sign'},
317 {'name': 'count', 'type': 'int'},
318 ],
319 }
320 specs.update({
321 'JET_N_BTAG': {
322 'info': 'Count b-tagged jets above the default b-tagging working '
323 'point, or above a custom one given as `tagger:WP`',
324 'args': [
325 {'name': 'sel', 'type': 'str', 'optional': True, 'pattern': '^[^:]+$'},
326 {'name': 'btag', 'type': 'str', 'optional': True, 'pattern': '^[^:]+:[^:]+$'},
327 {'name': 'sign', 'type': 'sign'},
328 {'name': 'count', 'type': 'int'},
329 ],
330 },
331 'JET_N_GHOST': {
332 'info': 'Count jets ghost-associated to a given particle, e.g. `B`, '
333 'or `B!C` to also veto a second ghost association',
334 'args': [
335 {'name': 'ghost', 'type': 'str', 'pattern': '^[A-Za-z]+(![A-Za-z]+)?$'},
336 {'name': 'ptmin', 'type': 'float', 'optional': True},
337 {'name': 'sign', 'type': 'sign'},
338 {'name': 'count', 'type': 'int'},
339 ],
340 },
341 'LJET_N_GHOST': {
342 'info': 'Count large-R jets ghost-associated to a given particle, e.g. '
343 '`B`, or `B!C` to also veto a second ghost association',
344 'args': [
345 {'name': 'ghost', 'type': 'str', 'pattern': '^[A-Za-z]+(![A-Za-z]+)?$'},
346 {'name': 'ptmin', 'type': 'float', 'optional': True},
347 {'name': 'sign', 'type': 'sign'},
348 {'name': 'count', 'type': 'int'},
349 ],
350 },
351 'LJETMASS_N': {
352 'info': 'Count large-R jets above a mass threshold',
353 'args': [
354 {'name': 'sel', 'type': 'str', 'optional': True},
355 {'name': 'minMass', 'type': 'float'},
356 {'name': 'sign', 'type': 'sign'},
357 {'name': 'count', 'type': 'int'},
358 ],
359 },
360 'LJETMASSWINDOW_N': {
361 'info': 'Count large-R jets inside (or, with `veto`, outside) a mass window',
362 'args': [
363 {'name': 'sel', 'type': 'str', 'optional': True},
364 {'name': 'lowMass', 'type': 'float'},
365 {'name': 'highMass', 'type': 'float'},
366 {'name': 'sign', 'type': 'sign'},
367 {'name': 'count', 'type': 'int'},
368 {'name': 'veto', 'type': 'flag', 'optional': True},
369 ],
370 },
371 'OBJ_N': {
372 'info': 'Count objects of an arbitrary container above a pT threshold',
373 'args': [
374 {'name': 'container', 'type': 'container'},
375 {'name': 'ptmin', 'type': 'float'},
376 {'name': 'sign', 'type': 'sign'},
377 {'name': 'count', 'type': 'int'},
378 ],
379 },
380 'SUM_EL_N_MU_N': {
381 'info': 'Count electrons and muons together, above a common or '
382 'per-flavour pT threshold',
383 'forms': [
384 [
385 {'name': 'ptmin', 'type': 'float'},
386 {'name': 'sign', 'type': 'sign'},
387 {'name': 'count', 'type': 'int'},
388 ],
389 [
390 {'name': 'ptEl', 'type': 'float'},
391 {'name': 'ptMu', 'type': 'float'},
392 {'name': 'sign', 'type': 'sign'},
393 {'name': 'count', 'type': 'int'},
394 ],
395 [
396 {'name': 'selEl', 'type': 'str'},
397 {'name': 'selMu', 'type': 'str'},
398 {'name': 'ptEl', 'type': 'float'},
399 {'name': 'ptMu', 'type': 'float'},
400 {'name': 'sign', 'type': 'sign'},
401 {'name': 'count', 'type': 'int'},
402 ],
403 ],
404 },
405 'SUM_EL_N_MU_N_TAU_N': {
406 'info': 'Count electrons, muons and tau-jets together, above a common '
407 'or per-flavour pT threshold',
408 'forms': [
409 [
410 {'name': 'ptmin', 'type': 'float'},
411 {'name': 'sign', 'type': 'sign'},
412 {'name': 'count', 'type': 'int'},
413 ],
414 [
415 {'name': 'ptEl', 'type': 'float'},
416 {'name': 'ptMu', 'type': 'float'},
417 {'name': 'ptTau', 'type': 'float'},
418 {'name': 'sign', 'type': 'sign'},
419 {'name': 'count', 'type': 'int'},
420 ],
421 [
422 {'name': 'selEl', 'type': 'str'},
423 {'name': 'selMu', 'type': 'str'},
424 {'name': 'selTau', 'type': 'str'},
425 {'name': 'ptEl', 'type': 'float'},
426 {'name': 'ptMu', 'type': 'float'},
427 {'name': 'ptTau', 'type': 'float'},
428 {'name': 'sign', 'type': 'sign'},
429 {'name': 'count', 'type': 'int'},
430 ],
431 ],
432 },
433 'MET': {
434 'info': 'Cut on the missing transverse energy',
435 'args': [
436 {'name': 'sign', 'type': 'sign'},
437 {'name': 'refMET', 'type': 'float'},
438 ],
439 },
440 'MWT': {
441 'info': 'Cut on the transverse mass of the leading lepton and MET',
442 'args': [
443 {'name': 'sign', 'type': 'sign'},
444 {'name': 'refMWT', 'type': 'float'},
445 ],
446 },
447 'MET+MWT': {
448 'info': 'Cut on the sum of the missing transverse energy and the '
449 'transverse mass',
450 'args': [
451 {'name': 'sign', 'type': 'sign'},
452 {'name': 'refMETMWT', 'type': 'float'},
453 ],
454 },
455 'MLL': {
456 'info': 'Cut on the dilepton invariant mass',
457 'args': [
458 {'name': 'sign', 'type': 'sign'},
459 {'name': 'refMLL', 'type': 'float'},
460 ],
461 },
462 'MLLWINDOW': {
463 'info': 'Require the dilepton invariant mass inside (or, with `veto`, '
464 'outside) a mass window',
465 'args': [
466 {'name': 'lowMLL', 'type': 'float'},
467 {'name': 'highMLL', 'type': 'float'},
468 {'name': 'veto', 'type': 'flag', 'optional': True},
469 ],
470 },
471 'MLL_OSSF': {
472 'info': 'Require the opposite-sign same-flavour dilepton invariant mass '
473 'inside (or, with `veto`, outside) a mass window',
474 'args': [
475 {'name': 'lowMll', 'type': 'float'},
476 {'name': 'highMll', 'type': 'float'},
477 {'name': 'veto', 'type': 'flag', 'optional': True},
478 ],
479 },
480 'OS': {
481 'info': 'Require an opposite-sign lepton pair; without any flag, all '
482 'available lepton flavours are considered',
483 'args': [
484 {'name': 'el', 'type': 'flag', 'optional': True},
485 {'name': 'mu', 'type': 'flag', 'optional': True},
486 {'name': 'tau', 'type': 'flag', 'optional': True},
487 ],
488 },
489 'SS': {
490 'info': 'Require a same-sign lepton pair; without any flag, all '
491 'available lepton flavours are considered',
492 'args': [
493 {'name': 'el', 'type': 'flag', 'optional': True},
494 {'name': 'mu', 'type': 'flag', 'optional': True},
495 {'name': 'tau', 'type': 'flag', 'optional': True},
496 ],
497 },
498 'IMPORT': {
499 'info': 'Import all the cuts of a previously defined event selection',
500 'args': [
501 {'name': 'region', 'type': 'region'},
502 ],
503 },
504 'EVENTFLAG': {
505 'info': 'Require an existing event-wise decoration to be true',
506 'args': [
507 {'name': 'decoration', 'type': 'str'},
508 ],
509 },
510 'GLOBALTRIGMATCH': {
511 'info': 'Require the global trigger matching decision, optionally for '
512 'a given trigger-configuration postfix',
513 'args': [
514 {'name': 'postfix', 'type': 'str', 'optional': True},
515 ],
516 },
517 'RUN_NUMBER': {
518 'info': 'Cut on the (random) run number',
519 'args': [
520 {'name': 'sign', 'type': 'sign'},
521 {'name': 'runNumber', 'type': 'int'},
522 ],
523 },
524 'EVENTVAR': {
525 'info': 'Cut on an existing EventInfo scalar variable, e.g. a DNN or '
526 'BDT discriminant',
527 'args': [
528 {'name': 'type', 'type': 'str', 'choices': sorted(cls._EVENTVAR_TYPES)},
529 {'name': 'name', 'type': 'str'},
530 {'name': 'sign', 'type': 'sign'},
531 {'name': 'value', 'type': 'float', 'signed': True},
532 ],
533 },
534 'EXPR': {
535 'info': 'Cut on a generic object-kinematic expression, e.g. '
536 '`dR(el[0],jet[0]) > 0.4`',
537 'freeText': True,
538 'grammar': {
539 'collections': sorted(cls._EXPR_COLL),
540 'variables': sorted(cls._EXPR_VARS),
541 },
542 },
543 'SAVE': {
544 'info': 'Deprecated and ignored: the event filter is now emitted '
545 'automatically at the end of every event selection',
546 'deprecated': True,
547 'args': [],
548 },
549 })
550 cls._KEYWORD_SPECS = specs
551 return cls._KEYWORD_SPECS
552