ATLAS Offline Software
Loading...
Searching...
No Matches
ConfigAccumulator.py
Go to the documentation of this file.
1# Copyright (C) 2002-2026 CERN for the benefit of the ATLAS collaboration
2
3import AnaAlgorithm.DualUseConfig as DualUseConfig
4from AthenaConfiguration.Enums import LHCPeriod, FlagEnum
5import re
6
7import warnings
8import functools
9
10# warn about deprecations with a FutureWarning instead of a
11# DeprecationWarning, because DeprecatedWarning is not shown by default
12deprecationWarningCategory = FutureWarning
13def deprecated(reason: str = ""):
14 def decorator(func):
15 message = f"{func.__qualname__} is deprecated."
16 if reason:
17 message += " " + reason
18
19 @functools.wraps(func)
20 def wrapper(*args, **kwargs):
21 warnings.warn(
22 message,
23 category=deprecationWarningCategory,
24 stacklevel=2,
25 )
26 return func(*args, **kwargs)
27
28 return wrapper
29 return decorator
30
31class ExpertModeWarning(Warning):
32 """Warning raised when an expert-only configuration option is used."""
33 pass
34# Default filter: error out unless the user overrides
35if not any(f[0] == 'error' and f[2] is ExpertModeWarning for f in warnings.filters):
36 warnings.simplefilter('error', ExpertModeWarning)
37
38class DataType(FlagEnum):
39 """holds the various data types as an enum"""
40 Data = 'data'
41 FullSim = 'fullsim'
42 FastSim = 'fastsim'
43
44
46 """all the data for a given selection that has been registered
47
48 the bits argument is for backward compatibility, does nothing, and will be
49 removed in the future."""
50
51 def __init__ (self, selectionName, decoration,
52 *, bits=0, preselection=None, comesFrom = '',
53 writeToOutput=True) :
54 self.name = selectionName
55 self.decoration = decoration
56 if preselection is not None :
57 self.preselection = preselection
58 else :
59 self.preselection = (selectionName == '')
60 self.comesFrom = comesFrom
61 self.writeToOutput = writeToOutput
62
63
64
66 """all the data for a given variables in the output that has been registered"""
67
68 def __init__ (self, origContainerName, variableName,
69 *, noSys, enabled, auxType) :
70 self.origContainerName = origContainerName
72 self.variableName = variableName
73 self.noSys = noSys
74 self.enabled = enabled
75 self.auxType = auxType
76
77 def __repr__ (self):
78 return f'OutputConfig("{self.outputContainerName}.{self.variableName}" [enabled={self.enabled}])'
79
81 """all the auto-generated meta-configuration data for a single container
82
83 This tracks the naming of all temporary containers, as well as all the
84 selection decorations."""
85
86 def __init__ (self, name, sourceName, *, originalName = None, isMet = False, noSysSuffix) :
87 self.name = name
88 self.sourceName = sourceName
89 self.originalName = originalName
90 self.noSysSuffix = noSysSuffix
91 self.index = 0
92 self.maxIndex = None
93 self.viewIndex = 1
94 self.isMet = isMet
95 self.selections = []
96 self.outputs = {}
97 self.meta = {}
98
99 def currentName (self, *, nominal=False) :
100 if self.index == 0 :
101 if self.sourceName is None :
102 raise Exception ("should not get here, reading container name before created: " + self.name)
103 return self.sourceName
104 if self.maxIndex and self.index == self.maxIndex :
105 result = self.systematicsName(self.name, noSysSuffix=self.noSysSuffix)
106 else :
107 result = self.systematicsName(f"{self.name}_STEP{self.index}", noSysSuffix=self.noSysSuffix)
108 if nominal :
109 result = result.replace("%SYS%", "NOSYS")
110 return result
111
112 @staticmethod
113 def systematicsName (name, *, noSysSuffix) :
114 """map an internal name to a name for systematics data handles
115
116 Right now this just means appending a _%SYS% to the name."""
117 if not noSysSuffix :
118 return name + "_%SYS%"
119 else :
120 return name
121
122 def nextPass (self) :
123 self.maxIndex = self.index
124 self.index = 0
125 self.viewIndex = 1
126 self.selections = []
127 self.outputs = {}
128 self.meta = {}
129
130
131
133 """a class that accumulates a configuration from blocks into an
134 algorithm sequence
135
136 This is used as argument to the ConfigurationBlock methods, which
137 need to be called in the correct order. This class will track all
138 meta-information that needs to be communicated between blocks
139 during configuration, and also add the created algorithms to the
140 sequence.
141
142 Use/access of containers in the event store is handled via
143 references that this class hands out. This happens in a separate
144 step before the algorithms are created, as the naming of
145 containers will depend on where in the chain the container is
146 used.
147
148 All arguments passed to the ConfigAccumulator constructor are used
149 as they are. The only exception is the systematics flag:
150 If not explicitly set the decision to run systematics or not
151 will be taken depending on the CommonServicesConfig setup.
152 """
153
154 def __init__ (self, *, flags=None, algSeq=None, noSysSuffix=False, noSystematics=None, dataType=None, isPhyslite=None, geometry=None, dsid=0, campaign=None, runNumber=None, autoconfigFromFlags=None, dataYear=0):
155
156 # Historically we have used the identifier
157 # `autoconfigFromFlags`, but in the rest of the code base
158 # `flags` is used. So for now we allow either, and can hopefully
159 # at some point remove the former (21 Aug 25).
160 if autoconfigFromFlags is not None:
161 if flags is not None:
162 raise ValueError("Cannot pass both flags and autoconfigFromFlags arguments")
163 flags = autoconfigFromFlags
164 warnings.warn ('Using autoconfigFromFlags parameter is deprecated, use flags instead', category=deprecationWarningCategory, stacklevel=2)
165 self._flags = flags
166
167 # Historically the user was expected to pass in meta-data
168 # manually, which was a complete underestimate of the amount of
169 # meta-data needed. The current recommendation is to pass in a
170 # configuration flags object instead. The code below will raise
171 # an error if both are done, and if no configuration flags are
172 # passed in, it will try to create a flags object from the
173 # passed in parameters.
174 if self._flags is not None:
175 if dataType is not None:
176 raise ValueError("Cannot pass both dataType and flags/autoconfigFromFlags arguments")
177 if isPhyslite is not None:
178 raise ValueError("Cannot pass both isPhyslite and flags/autoconfigFromFlags arguments")
179 if geometry is not None:
180 raise ValueError("Cannot pass both geometry and flags/autoconfigFromFlags arguments")
181 if dsid != 0:
182 raise ValueError("Cannot pass both dsid and flags/autoconfigFromFlags arguments")
183 if campaign is not None:
184 raise ValueError("Cannot pass both campaign and flags/autoconfigFromFlags arguments")
185 if runNumber is not None:
186 raise ValueError("Cannot pass both runNumber and flags/autoconfigFromFlags arguments")
187 if dataYear != 0:
188 raise ValueError("Cannot pass both dataYear and flags/autoconfigFromFlags arguments")
189
190 if self._flags.Input.isMC:
191 if self._flags.Sim.ISF.Simulator.usesFastCaloSim():
192 dataType = DataType.FastSim
193 else:
194 dataType = DataType.FullSim
195 else:
196 dataType = DataType.Data
197 isPhyslite = 'StreamDAOD_PHYSLITE' in self._flags.Input.ProcessingTags
198 from TrigDecisionTool.TrigDecisionToolHelpers import (
199 getRun3NavigationContainerFromInput_forAnalysisBase)
200 hltSummary = getRun3NavigationContainerFromInput_forAnalysisBase(self._flags)
201 else:
202 warnings.warn ('it is deprecated to configure meta-data for analysis configuration manually, please read the configuration flags via the meta-data reader', category=deprecationWarningCategory, stacklevel=2)
203 from AthenaConfiguration.AllConfigFlags import initConfigFlags
204 flags = initConfigFlags()
205 if dataType is None:
206 raise ValueError ("need to specify dataType if flags are not set")
207 # legacy mappings of string arguments
208 if isinstance(dataType, str):
209 if dataType == 'mc':
210 dataType = DataType.FullSim
211 elif dataType == 'afii':
212 dataType = DataType.FastSim
213 else:
214 dataType = DataType(dataType)
215 if isPhyslite is None:
216 isPhyslite = False
217 if geometry is not None:
218 # allow possible string argument for `geometry` and convert it to enum
219 geometry = LHCPeriod(geometry)
220 if geometry is LHCPeriod.Run1:
221 raise ValueError ("invalid Run geometry: %s" % geometry.value)
222 flags.GeoModel.Run = geometry
223 if dsid != 0:
224 flags.Input.MCChannelNumber = dsid
225 if campaign is not None:
226 flags.Input.MCCampaign = campaign
227 if dataYear != 0:
228 flags.Input.DataYear = dataYear
229 if runNumber is None:
230 # not sure if we should just use a default run number
231 # here, or just report nothing
232 runNumber = 284500
233 flags.Input.RunNumbers = [runNumber]
234 hltSummary = 'HLTNav_Summary_DAODSlimmed'
235 flags.lock()
236 self._flags = flags
237
238 # These don't seem to have a direct equivalent in the
239 # configuration flags. For now I'm keeping them (21 Aug 25), but
240 # they might be replaced with something that is more directly in
241 # the configuration flags in the future.
242 self._dataType = dataType
243 self._isPhyslite = isPhyslite
244 self._hltSummary = hltSummary
245
246 # From here on, we are no longer dealing with flags or
247 # meta-data, but actual internal variables we need to manage the
248 # creation of components.
249 self._algSeq = algSeq
250 self._noSystematics = noSystematics
251 self._noSysSuffix = noSysSuffix
252 self._algPostfix = ''
253 self._defaultHistogramStream = 'ANALYSIS'
256 self._pass = 0
257 self._algorithms = {}
258 self._currentAlg = None
259 self._selectionNameExpr = re.compile ('[A-Za-z_][A-Za-z_0-9]+')
260 self.setSourceName ('EventInfo', 'EventInfo')
262 self.CA = None
263
264 if DualUseConfig.isAthena:
265 from AthenaConfiguration.ComponentAccumulator import ComponentAccumulator
266 self.CA = ComponentAccumulator()
267 if algSeq is not None:
268 self.CA.addSequence(algSeq)
269 else:
270 if algSeq is None :
271 raise ValueError ("need to pass algSeq if not using ComponentAccumulator")
272
273 def noSystematics (self) :
274 """noSystematics flag used by CommonServices block"""
275 return self._noSystematics
276
277 @property
278 def flags (self) :
279 """Athena configuration flags"""
280 return self._flags
281
282 @deprecated("use the flags property instead")
283 def autoconfigFlags (self) :
284 """Athena configuration flags
285
286 This is a backward compatibility version of the flags property,
287 which is preferred."""
288 return self._flags
289
290 def dataType (self) :
291 """the data type we run on (data, fullsim, fastsim)"""
292 return self._dataType
293
294 def isPhyslite (self) :
295 """whether we run on PHYSLITE"""
296 return self._isPhyslite
297
298 def geometry (self) :
299 """the LHC Run period we run on"""
300 return self._flags.GeoModel.Run
301
302 def dsid(self) :
303 """the mcChannelNumber or DSID of the sample we run on"""
304 return self._flags.Input.MCChannelNumber
305
306 def campaign(self) :
307 """the MC campaign we run on"""
308 return self._flags.Input.MCCampaign
309
310 def runNumber(self) :
311 """the MC runNumber"""
312 return int(self._flags.Input.RunNumbers[0])
313
314 def dataYear(self) :
315 """for data, the corresponding year; for MC, zero"""
316 return self._flags.Input.DataYear
317
318 def generatorInfo(self) :
319 """the dictionary of MC generators and their versions for the sample we run on"""
320 return self._flags.Input.GeneratorsInfo
321
322 def hltSummary(self) :
323 """the HLTSummary configuration to be used for the trigger decision tool"""
324 return self._hltSummary
325
326 def defaultHistogramStream(self):
327 """the default histogram stream to be used for output histograms"""
328 return self._defaultHistogramStream
329
330 def setDefaultHistogramStream(self, streamName: str):
331 """set the default histogram stream to be used for output histograms
332
333 As an advanced option this is not directly exposed by the constructor,
334 but can be set by the user if needed before configuring the job."""
335 self._defaultHistogramStream = streamName
336
337 def algPostfix (self) :
338 """the current postfix to be appended to algorithm names
339
340 Blocks should not call this directly, but rather implement the
341 instanceName method, which will be used to generate the postfix
342 automatically."""
343 return self._algPostfix
344
345 def setAlgPostfix (self, postfix : str) :
346 """set the current postfix to be appended to algorithm names
347
348 Blocks should not call this directly, but rather implement the
349 instanceName method, which will be used to generate the postfix
350 automatically."""
351 # make sure the postfix matches the expected format ([_a-zA-Z0-9]*)
352 if re.compile ('^[_a-zA-Z0-9]*$').match (postfix) is None :
353 raise ValueError ('invalid algorithm postfix: ' + postfix)
354 if postfix == '' :
355 self._algPostfix = ''
356 elif postfix[0] != '_' :
357 self._algPostfix = '_' + postfix
358 else :
359 self._algPostfix = postfix
360
361 def getAlgorithm (self, name : str):
362 """get the algorithm with the given name
363
364 Despite the name this will also return services and tools. It is
365 mostly meant for internal use, particularly for the property
366 overrides."""
367 name = name + self._algPostfix
368 if name not in self._algorithms:
369 return None
370 return self._algorithms[name]
371
372 def createAlgorithm (self, type, name, reentrant=False) :
373 """create a new algorithm and register it as the current algorithm"""
374 name = name + self._algPostfix
375 if self._pass == 0 :
376 if name in self._algorithms :
377 raise Exception ('duplicate algorithms: ' + name + ' with algPostfix=' + self._algPostfix)
378 if reentrant:
379 alg = DualUseConfig.createReentrantAlgorithm (type, name)
380 else:
381 alg = DualUseConfig.createAlgorithm (type, name)
382
383 if DualUseConfig.isAthena:
384 if self._algSeq is not None:
385 self.CA.addEventAlgo(alg,self._algSeq.name)
386 else :
387 self.CA.addEventAlgo(alg)
388 else:
389 self._algSeq += alg
390
391 self._algorithms[name] = alg
392 self._currentAlg = alg
393 return alg
394 else :
395 if name not in self._algorithms :
396 raise Exception ('unknown algorithm requested: ' + name)
397 self._currentAlg = self._algorithms[name]
398 if self.CA and self._currentAlg != self.CA.getEventAlgo(name) :
399 raise Exception ('change to algorithm object: ' + name)
400 return self._algorithms[name]
401
402
403 def createService (self, type, name) :
404 '''create a new service and register it as the "current algorithm"'''
405 if self._pass == 0 :
406 if name in self._algorithms :
407 raise Exception ('duplicate service: ' + name)
408 service = DualUseConfig.createService (type, name)
409 # Avoid importing AthenaCommon.AppMgr in a CA Athena job
410 # as it modifies Gaudi behaviour
411 if DualUseConfig.isAthena:
412 self.CA.addService(service)
413 else:
414 # We're not, so let's remember this as a "normal" algorithm:
415 self._algSeq += service
416 self._algorithms[name] = service
417 self._currentAlg = service
418 return service
419 else :
420 if name not in self._algorithms :
421 raise Exception ('unknown service requested: ' + name)
422 self._currentAlg = self._algorithms[name]
423 return self._algorithms[name]
424
425
426 def createPublicTool (self, type, name) :
427 '''create a new public tool and register it as the "current algorithm"'''
428 if self._pass == 0 :
429 if name in self._algorithms :
430 raise Exception ('duplicate public tool: ' + name)
431 tool = DualUseConfig.createPublicTool (type, name)
432 # Avoid importing AthenaCommon.AppMgr in a CA Athena job
433 # as it modifies Gaudi behaviour
434 if DualUseConfig.isAthena:
435 self.CA.addPublicTool(tool)
436 else:
437 # We're not, so let's remember this as a "normal" algorithm:
438 self._algSeq += tool
439 self._algorithms[name] = tool
440 self._currentAlg = tool
441 return tool
442 else :
443 if name not in self._algorithms :
444 raise Exception ('unknown public tool requested: ' + name)
445 self._currentAlg = self._algorithms[name]
446 return self._algorithms[name]
447
448
449 def addPrivateTool (self, propertyName, toolType) :
450 """add a private tool to the current algorithm"""
451 if self._pass == 0 :
452 DualUseConfig.addPrivateTool (self._currentAlg, propertyName, toolType)
453
454 def setExtraInputs (self, inputs) :
455 """set extra input dependencies for the current algorithm"""
456 if DualUseConfig.isAthena:
457 self._currentAlg.ExtraInputs = inputs
458
459 def setExtraOutputs (self, outputs) :
460 """set extra output dependencies for the current algorithm"""
461 if DualUseConfig.isAthena:
462 self._currentAlg.ExtraOutputs = outputs
463
464 def setSourceName (self, containerName, sourceName,
465 *, originalName = None, isMet = False) :
466 """set the (default) name of the source/original container
467
468 This is essentially meant to allow using e.g. the muon
469 configuration and the user not having to manually specify that
470 they want to use the Muons/AnalysisMuons container from the
471 input file.
472
473 In addition it allows to set the original name of the
474 container (which may be different from the source name), which
475 is mostly/exclusively used for jet containers, so that
476 subsequent configurations know which jet container they
477 operate on.
478 """
479 if containerName not in self._containerConfig :
480 self._containerConfig[containerName] = ContainerConfig (containerName, sourceName, noSysSuffix = self._noSysSuffix, originalName = originalName, isMet = isMet)
481
482
483 def writeName (self, containerName, *, isMet=None) :
484 """register that the given container will be made and return
485 its name"""
486 if containerName not in self._containerConfig :
487 self._containerConfig[containerName] = ContainerConfig (containerName, sourceName = None, noSysSuffix = self._noSysSuffix)
488 if self._containerConfig[containerName].sourceName is not None :
489 raise Exception ("trying to write container configured for input: " + containerName)
490 if self._containerConfig[containerName].index != 0 :
491 raise Exception ("trying to write container twice: " + containerName)
492 self._containerConfig[containerName].index += 1
493 if isMet is not None :
494 self._containerConfig[containerName].isMet = isMet
495 return self._containerConfig[containerName].currentName()
496
497
498 def readName (self, containerName, *, nominal=False) :
499 """get the name of the "current copy" of the given container
500
501 As extra copies get created during processing this will track
502 the correct name of the current copy. Optionally one can pass
503 in the name of the container before the first copy.
504 """
505 if containerName not in self._containerConfig :
506 raise Exception ("no source container for: " + containerName)
507 return self._containerConfig[containerName].currentName(nominal=nominal)
508
509
510 def copyName (self, containerName) :
511 """register that a copy of the container will be made and return
512 its name"""
513 if containerName not in self._containerConfig :
514 raise Exception ("unknown container: " + containerName)
515 self._containerConfig[containerName].index += 1
516 return self._containerConfig[containerName].currentName()
517
518
519 def wantCopy (self, containerName) :
520 """ask whether we want/need a copy of the container
521
522 This usually only happens if no copy of the container has been
523 made yet and the copy is needed to allow modifications, etc.
524 """
525 if containerName not in self._containerConfig :
526 raise Exception ("no source container for: " + containerName)
527 return self._containerConfig[containerName].index == 0
528
529
530 def originalName (self, containerName) :
531 """get the "original" name of the given container
532
533 This is mostly/exclusively used for jet containers, so that
534 subsequent configurations know which jet container they
535 operate on.
536 """
537 if containerName not in self._containerConfig :
538 raise Exception ("container unknown: " + containerName)
539 result = self._containerConfig[containerName].originalName
540 if result is None :
541 raise Exception ("no original name for: " + containerName)
542 return result
543
544 def getContainerMeta (self, containerName, metaField, defaultValue=None, *, failOnMiss=False) :
545 """get the meta information for the given container
546
547 This is used to pass down meta-information from the
548 configuration to the algorithms.
549 """
550 if containerName not in self._containerConfig :
551 raise Exception ("container unknown: " + containerName)
552 if metaField in self._containerConfig[containerName].meta :
553 return self._containerConfig[containerName].meta[metaField]
554 if failOnMiss :
555 raise Exception ('unknown meta-field' + metaField + ' on container ' + containerName)
556 return defaultValue
557
558 def setContainerMeta (self, containerName, metaField, value, *, allowOverwrite=False) :
559 """set the meta information for the given container
560
561 This is used to pass down meta-information from the
562 configuration to the algorithms.
563 """
564 if containerName not in self._containerConfig :
565 raise Exception ("container unknown: " + containerName)
566 if not allowOverwrite and metaField in self._containerConfig[containerName].meta :
567 raise Exception ('duplicate meta-field' + metaField + ' on container ' + containerName)
568 self._containerConfig[containerName].meta[metaField] = value
569
570 def isMetContainer (self, containerName) :
571 """whether the given container is registered as a MET container
572
573 This is mostly/exclusively used for determining whether to
574 write out the whole container or just a single MET term.
575 """
576 if containerName not in self._containerConfig :
577 raise Exception ("container unknown: " + containerName)
578 return self._containerConfig[containerName].isMet
579
580
581 def readNameAndSelection (self, containerName, *, excludeFrom = None) :
582 """get the name of the "current copy" of the given container, and the
583 selection string
584
585 This is mostly meant for MET and OR for whom the actual object
586 selection is relevant, and which as such allow to pass in the
587 working point as "ObjectName.WorkingPoint".
588 """
589 split = containerName.split (".")
590 if len(split) == 1 :
591 objectName = split[0]
592 selectionName = ''
593 elif len(split) == 2 :
594 objectName = split[0]
595 selectionName = split[1]
596 else :
597 raise Exception ('invalid object selection name: ' + containerName)
598 return self.readName (objectName), self.getFullSelection (objectName, selectionName, excludeFrom=excludeFrom)
599
600
601 def nextPass (self) :
602 """switch to the next configuration pass
603
604 Configuration happens in two steps, with all the blocks processed
605 twice. This switches from the first to the second pass.
606 """
607 if self._pass != 0 :
608 raise Exception ("already performed final pass")
609 for name in self._containerConfig :
610 self._containerConfig[name].nextPass ()
611 self._pass = 1
612 self._currentAlg = None
613 self._outputContainers = {}
614
615
616 def getPreselection (self, containerName, selectionName, *, asList = False) :
617
618 """get the preselection string for the given selection on the given
619 container
620 """
621 if selectionName != '' and not self._selectionNameExpr.fullmatch (selectionName) :
622 raise ValueError ('invalid selection name: ' + selectionName)
623 if containerName not in self._containerConfig :
624 return ""
625 config = self._containerConfig[containerName]
626 decorations = []
627 for selection in config.selections :
628 if (selection.name == '' or selection.name == selectionName) and \
629 selection.preselection :
630 decorations += [selection.decoration]
631 if asList :
632 return decorations
633 else :
634 return '&&'.join (decorations)
635
636
637 def getFullSelection (self, containerName, selectionName,
638 *, skipBase = False, excludeFrom = None) :
639
640 """get the selection string for the given selection on the given
641 container
642
643 This can handle both individual selections or selection
644 expressions (e.g. `loose||tight`) with the later being
645 properly expanded. Either way the base selection (i.e. the
646 selection without a name) will always be applied on top.
647
648 containerName --- the container the selection is defined on
649 selectionName --- the name of the selection, or a selection
650 expression based on multiple named selections
651 skipBase --- will avoid the base selection, and should normally
652 not be used by the end-user.
653 excludeFrom --- a set of string names of selection sources to exclude
654 e.g. to exclude OR selections from MET
655 """
656 if "." in containerName:
657 raise ValueError (f'invalid containerName argument: {containerName} , it contains a "." '
658 'which is used to indicate container+selection. You should only pass the container.')
659 if containerName not in self._containerConfig :
660 return ""
661
662 if excludeFrom is None :
663 excludeFrom = set()
664 elif not isinstance(excludeFrom, set) :
665 raise ValueError ('invalid excludeFrom argument (need set of strings): ' + str(excludeFrom))
666
667 # Check if this is actually a selection expression,
668 # e.g. `A||B` and if so translate it into a complex expression
669 # for the user. I'm not trying to do any complex syntax
670 # recognition, but instead just produce an expression that the
671 # C++ parser ought to be able to read.
672 if selectionName != '' and \
673 not self._selectionNameExpr.fullmatch (selectionName) :
674 result = ''
675 while selectionName != '' :
676 match = self._selectionNameExpr.match (selectionName)
677 if not match :
678 result += selectionName[0]
679 selectionName = selectionName[1:]
680 else :
681 subname = match.group(0)
682 subresult = self.getFullSelection (containerName, subname, skipBase = True, excludeFrom=excludeFrom)
683 if subresult != '' :
684 result += '(' + subresult + ')'
685 else :
686 result += 'true'
687 selectionName = selectionName[len(subname):]
688 subresult = self.getFullSelection (containerName, '', excludeFrom=excludeFrom)
689 if subresult != '' :
690 result = subresult + '&&(' + result + ')'
691 return '(' + result + ')' if result !='' else ''
692
693 config = self._containerConfig[containerName]
694 decorations = []
695 hasSelectionName = False
696 for selection in config.selections :
697 if ((selection.name == '' and not skipBase) or selection.name == selectionName) and (selection.comesFrom not in excludeFrom) :
698 decorations += [selection.decoration]
699 if selection.name == selectionName :
700 hasSelectionName = True
701 if not hasSelectionName and selectionName != '' :
702 raise KeyError ('invalid selection name: ' + containerName + '.' + selectionName)
703 return '&&'.join (decorations)
704
705
706 def getSelectionCutFlow (self, containerName, selectionName) :
707
708 """get the individual selections as a list for producing the cutflow for
709 the given selection on the given container
710
711 This can only handle individual selections, not selection
712 expressions (e.g. `loose||tight`).
713
714 """
715 if containerName not in self._containerConfig :
716 return []
717
718 # Check if this is actually a selection expression,
719 # e.g. `A||B` and if so translate it into a complex expression
720 # for the user. I'm not trying to do any complex syntax
721 # recognition, but instead just produce an expression that the
722 # C++ parser ought to be able to read.
723 if selectionName != '' and \
724 not self._selectionNameExpr.fullmatch (selectionName) :
725 raise ValueError ('not allowed to do cutflow on selection expression: ' + selectionName)
726
727 config = self._containerConfig[containerName]
728 decorations = []
729 for selection in config.selections :
730 if (selection.name == '' or selection.name == selectionName) :
731 decorations += [selection.decoration]
732 return decorations
733
734
735 def addEventCutFlow (self, selection, decorations) :
736
737 """register a new event cutflow, adding it to the dictionary with key 'selection'
738 and value 'decorations', a list of decorated selections
739 """
740 if self._pass == 0:
741 if selection in self._eventcutflow.keys():
742 raise ValueError ('the event cutflow dictionary already contains an entry ' + selection)
743 else:
744 self._eventcutflow[selection] = decorations
745
746
747 def getEventCutFlow (self, selection) :
748
749 """get the list of decorated selections for an event cutflow, corresponding to
750 key 'selection'
751 """
752 return self._eventcutflow[selection]
753
754
755 def addSelection (self, containerName, selectionName, decoration,
756 **kwargs) :
757 """add another selection decoration to the selection of the given
758 name for the given container"""
759 if selectionName != '' and not self._selectionNameExpr.fullmatch (selectionName) :
760 raise ValueError ('invalid selection name: ' + selectionName)
761 if containerName not in self._containerConfig :
762 self._containerConfig[containerName] = ContainerConfig (containerName, containerName, noSysSuffix=self._noSysSuffix)
763 config = self._containerConfig[containerName]
764 selection = SelectionConfig (selectionName, decoration, **kwargs)
765 config.selections.append (selection)
766
767
768 def addOutputContainer (self, containerName, outputContainerName) :
769 """register a copy of a container used in outputs"""
770 if containerName not in self._containerConfig :
771 raise KeyError ("container unknown: " + containerName)
772 if outputContainerName in self._outputContainers :
773 raise KeyError ("duplicate output container name: " + outputContainerName)
774 self._outputContainers[outputContainerName] = containerName
775
776
777 def checkOutputContainer (self, containerName) :
778 """check whether a given container has been registered in outputs"""
779 return containerName in self._outputContainers.values()
780
781
782 def getOutputContainerOrigin (self, outputContainerName) :
783 """Get the name of the actual container, for which an output is registered"""
784 try:
785 return self._outputContainers[outputContainerName]
786 except KeyError:
787 try:
788 return self._containerConfig[outputContainerName].name
789 except KeyError:
790 raise KeyError ("output container unknown: " + outputContainerName)
791
792
793 def addOutputVar (self, containerName, variableName, outputName,
794 *, noSys=False, enabled=True, auxType=None) :
795 """add an output variable for the given container to the output
796 """
797
798 if containerName not in self._containerConfig :
799 raise KeyError ("container unknown: " + containerName)
800 baseConfig = self._containerConfig[containerName].outputs
801 if outputName in baseConfig :
802 raise KeyError ("duplicate output variable name: " + outputName)
803 config = OutputConfig (containerName, variableName, noSys=noSys, enabled=enabled, auxType=auxType)
804 baseConfig[outputName] = config
805
806
807 def getOutputVars (self, containerName) :
808 """get the output variables for the given container"""
809 if containerName in self._outputContainers :
810 containerName = self._outputContainers[containerName]
811 if containerName not in self._containerConfig :
812 raise KeyError ("unknown container for output: " + containerName)
813 return self._containerConfig[containerName].outputs
814
815
816 def getSelectionNames (self, containerName, excludeFrom = None) :
817 """Retrieve set of unique selections defined for a given container"""
818 if containerName not in self._containerConfig :
819 return []
820 if excludeFrom is None:
821 excludeFrom = set()
822 elif not isinstance(excludeFrom, set) :
823 raise ValueError ('invalid excludeFrom argument (need set of strings): ' + str(excludeFrom))
824
825 config = self._containerConfig[containerName]
826 # because cuts are registered individually, selection names can repeat themselves
827 # but we are interested in unique names only
828 selectionNames = set()
829 for selection in config.selections:
830 if selection.comesFrom in excludeFrom:
831 continue
832 # skip flags which should be disabled in output
833 if selection.writeToOutput:
834 selectionNames.add(selection.name)
835 return selectionNames
setSourceName(self, containerName, sourceName, *, originalName=None, isMet=False)
__init__(self, *, flags=None, algSeq=None, noSysSuffix=False, noSystematics=None, dataType=None, isPhyslite=None, geometry=None, dsid=0, campaign=None, runNumber=None, autoconfigFromFlags=None, dataYear=0)
__init__(self, name, sourceName, *, originalName=None, isMet=False, noSysSuffix)
__init__(self, origContainerName, variableName, *, noSys, enabled, auxType)
__init__(self, selectionName, decoration, *, bits=0, preselection=None, comesFrom='', writeToOutput=True)