ATLAS Offline Software
Loading...
Searching...
No Matches
EventSelectionConfigTestSupport.py
Go to the documentation of this file.
1# Copyright (C) 2002-2026 CERN for the benefit of the ATLAS collaboration
2"""Shared harness for the EventSelectionConfig unit tests.
3
4Each *_unitTest.py script builds a real ConfigSequence containing a single
5EventSelectionConfig block, configures it through a real ConfigAccumulator,
6and inspects the resulting algorithm sequence. No mocking: the accumulator
7instantiates the real CP algorithms, so a Python/C++ property-name mismatch
8is caught here rather than at runtime.
9
10In a full job the object-config blocks register the input containers and their
11selection working points upstream. In isolation we register them here:
12 * input containers -> config.setSourceName(container, container)
13 * object selection WPs -> config.addSelection(container, wp, decoration)
14"""
15
16from AnaAlgorithm.AlgSequence import AlgSequence
17from AnaAlgorithm.DualUseConfig import isAthena
18from AnalysisAlgorithmsConfig.ConfigSequence import ConfigSequence
19from AnalysisAlgorithmsConfig.ConfigAccumulator import ConfigAccumulator, DataType
20from EventSelectionAlgorithms.EventSelectionConfig import (
21 EventSelectionConfig, EventSelectionMergerConfig,
22)
23
24_UNSET = object()
25
26# Dummy object-selection working points referenced by the tests' extra-selection
27# forms. Registered on every input container so getFullSelection resolves them.
28_DUMMY_WPS = ["baseline", "mysel", "goodjet", "centraljet", "topjet",
29 "elsel", "musel", "esel", "msel", "tsel"]
30
31
32def _container(spec):
33 """Strip a trailing '.selection' to get the bare container name."""
34 return spec.split(".")[0]
35
36
37def _new_accumulator(dataType, containers):
38 algSeq = AlgSequence()
39 config = ConfigAccumulator(dataType=dataType, algSeq=algSeq)
40 for cont in {_container(c) for c in containers}:
41 config.setSourceName(cont, cont)
42 for wp in _DUMMY_WPS:
43 # decoration string deliberately embeds the WP name so the
44 # extra-selection tests can assert on it via substring.
45 config.addSelection(cont, wp, f"{wp}_%SYS%")
46 return algSeq, config
47
48
49def run(cuts, *, dataType=DataType.FullSim, containers=None,
50 selectionName="SR", **options):
51 """Configure one EventSelectionConfig block and return its algorithms.
52
53 `containers` maps EventSelectionConfig option names to container specs,
54 e.g. {'electrons': 'AnaElectrons'} or {'jets': 'AnaJets.baseline'}.
55 """
56 containers = containers or {}
57 algSeq, config = _new_accumulator(dataType, containers.values())
58 seq = ConfigSequence()
59 block = EventSelectionConfig()
60 block.setOptionValue("selectionName", selectionName)
61 block.setOptionValue("selectionCuts", cuts)
62 for opt, cname in containers.items():
63 block.setOptionValue(opt, cname)
64 for opt, val in options.items():
65 block.setOptionValue(opt, val)
66 seq.append(block)
67 seq.fullConfigure(config)
68 if isAthena:
69 return config.CA.getEventAlgos()
70 return list(algSeq)
71
72
73def run_merger(region_names, *, dataType=DataType.FullSim, noFilter=False):
74 """Build real regions then the merger, and return the algorithm sequence.
75
76 Each region is a trivial EventSelectionConfig (RUN_NUMBER + SAVE) so that
77 the merger's `EventSelection` dependency is satisfied and `eventSelectionNames`
78 is populated through the normal SAVE path (no manual metadata).
79 """
80 algSeq, config = _new_accumulator(dataType, [])
81 seq = ConfigSequence()
82 for name in region_names:
83 block = EventSelectionConfig()
84 block.setOptionValue("selectionName", name)
85 block.setOptionValue("selectionCuts", "RUN_NUMBER >= 0\nSAVE")
86 seq.append(block)
87 merger = EventSelectionMergerConfig()
88 merger._instance_number = 1 # defensive: ensure the guard runs
89 merger.setOptionValue("noFilter", noFilter)
90 seq.append(merger)
91 seq.fullConfigure(config)
92 if isAthena:
93 return config.CA.getEventAlgos()
94 return list(algSeq)
95
96
97def selectors(algs):
98 """Selector algorithms only (exclude the SAVE filter)."""
99 return [a for a in algs if a.getType() != "CP::SaveFilterAlg"]
100
101
102def named(algs, substr):
103 return [a for a in algs if substr in a.getName()]
104
105
106def prop(alg, name, default=_UNSET):
107 """Read a property set on the algorithm; return `default` if unset."""
108 val = getattr(alg, name, _UNSET)
109 if val is _UNSET:
110 if default is _UNSET:
111 raise AttributeError(f"{alg.getName()} has no property {name!r}")
112 return default
113 return val
run(cuts, *, dataType=DataType.FullSim, containers=None, selectionName="SR", **options)
run_merger(region_names, *, dataType=DataType.FullSim, noFilter=False)