219 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):
221 # Historically we have used the identifier
222 # `autoconfigFromFlags`, but in the rest of the code base
223 # `flags` is used. So for now we allow either, and can hopefully
224 # at some point remove the former (21 Aug 25).
225 if autoconfigFromFlags is not None:
226 if flags is not None:
227 raise ValueError("Cannot pass both flags and autoconfigFromFlags arguments")
228 flags = autoconfigFromFlags
229 warnings.warn ('Using autoconfigFromFlags parameter is deprecated, use flags instead', category=deprecationWarningCategory, stacklevel=2)
232 # Historically the user was expected to pass in meta-data
233 # manually, which was a complete underestimate of the amount of
234 # meta-data needed. The current recommendation is to pass in a
235 # configuration flags object instead. The code below will raise
236 # an error if both are done, and if no configuration flags are
237 # passed in, it will try to create a flags object from the
238 # passed in parameters.
239 if self._flags is not None:
240 if dataType is not None:
241 raise ValueError("Cannot pass both dataType and flags/autoconfigFromFlags arguments")
242 if isPhyslite is not None:
243 raise ValueError("Cannot pass both isPhyslite and flags/autoconfigFromFlags arguments")
244 if geometry is not None:
245 raise ValueError("Cannot pass both geometry and flags/autoconfigFromFlags arguments")
247 raise ValueError("Cannot pass both dsid and flags/autoconfigFromFlags arguments")
248 if campaign is not None:
249 raise ValueError("Cannot pass both campaign and flags/autoconfigFromFlags arguments")
250 if runNumber is not None:
251 raise ValueError("Cannot pass both runNumber and flags/autoconfigFromFlags arguments")
253 raise ValueError("Cannot pass both dataYear and flags/autoconfigFromFlags arguments")
255 if self._flags.Input.isMC:
256 if self._flags.Sim.ISF.Simulator.usesFastCaloSim():
257 dataType = DataType.FastSim
259 dataType = DataType.FullSim
261 dataType = DataType.Data
262 isPhyslite = 'StreamDAOD_PHYSLITE' in self._flags.Input.ProcessingTags
263 from TrigDecisionTool.TrigDecisionToolHelpers import (
264 getRun3NavigationContainerFromInput_forAnalysisBase)
265 hltSummary = getRun3NavigationContainerFromInput_forAnalysisBase(self._flags)
267 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)
268 from AthenaConfiguration.AllConfigFlags import initConfigFlags
269 flags = initConfigFlags()
271 raise ValueError ("need to specify dataType if flags are not set")
272 # legacy mappings of string arguments
273 if isinstance(dataType, str):
275 dataType = DataType.FullSim
276 elif dataType == 'afii':
277 dataType = DataType.FastSim
279 dataType = DataType(dataType)
280 if isPhyslite is None:
282 if geometry is not None:
283 # allow possible string argument for `geometry` and convert it to enum
284 geometry = LHCPeriod(geometry)
285 if geometry is LHCPeriod.Run1:
286 raise ValueError (f"invalid Run geometry: {geometry.value}")
287 flags.GeoModel.Run = geometry
289 flags.Input.MCChannelNumber = dsid
290 if campaign is not None:
291 flags.Input.MCCampaign = campaign
293 flags.Input.DataYear = dataYear
294 if runNumber is None:
295 # not sure if we should just use a default run number
296 # here, or just report nothing
298 flags.Input.RunNumbers = [runNumber]
299 hltSummary = 'HLTNav_Summary_DAODSlimmed'
303 # These don't seem to have a direct equivalent in the
304 # configuration flags. For now I'm keeping them (21 Aug 25), but
305 # they might be replaced with something that is more directly in
306 # the configuration flags in the future.
307 self._dataType = dataType
308 self._isPhyslite = isPhyslite
309 self._hltSummary = hltSummary
311 # From here on, we are no longer dealing with flags or
312 # meta-data, but actual internal variables we need to manage the
313 # creation of components.
314 self._algSeq = algSeq
315 self._noSystematics = noSystematics
316 self._noSysSuffix = noSysSuffix
317 self._algPostfix = ''
318 self._defaultHistogramStream = 'ANALYSIS'
319 self._containerConfig = {}
320 self._outputContainers = {}
321 self._algorithms = {}
322 self._currentAlg = None
323 self._selectionNameExpr = re.compile ('[A-Za-z_][A-Za-z_0-9]*')
324 self.setSourceName ('EventInfo', 'EventInfo')
325 self.setContainerMeta ('EventInfo', "nonContainer", True)
326 self._eventcutflow = {}
329 if DualUseConfig.isAthena:
330 from AthenaConfiguration.ComponentAccumulator import ComponentAccumulator
331 self.CA = ComponentAccumulator()
332 if algSeq is not None:
333 self.CA.addSequence(algSeq)
336 raise ValueError ("need to pass algSeq if not using ComponentAccumulator")
338 ConfigAccumulator._instance_counter += 1
339 self._algPrefix = f'seq{self._instance_counter}_'
463 def _createServiceOrTool (self, type, name, isSingleton, kind, create, add) :
464 """shared implementation of createService and createPublicTool"""
466 name = self._algPrefix + name + self._algPostfix
467 if isSingleton and name in ConfigAccumulator._singleton_registry:
468 component = ConfigAccumulator._singleton_registry[name]
469 self._algorithms[name] = component
470 self._currentAlg = component
472 if name in self._algorithms :
473 raise ValueError (f'duplicate {kind}: {name}')
474 component = create (type, name)
475 # Avoid importing AthenaCommon.AppMgr in a CA Athena job
476 # as it modifies Gaudi behaviour
477 if DualUseConfig.isAthena:
480 # We're not, so let's remember this as a "normal" algorithm:
481 self._algSeq += component
482 self._algorithms[name] = component
483 self._currentAlg = component
485 ConfigAccumulator._singleton_registry[name] = component
517 def setSourceName (self, containerName, sourceName,
518 *, originalName = None, isMet = False) :
519 """set the (default) name of the source/original container
521 This is essentially meant to allow using e.g. the muon
522 configuration and the user not having to manually specify that
523 they want to use the Muons/AnalysisMuons container from the
526 In addition it allows to set the original name of the
527 container (which may be different from the source name), which
528 is mostly/exclusively used for jet containers, so that
529 subsequent configurations know which jet container they
532 if containerName not in self._containerConfig :
533 self._containerConfig[containerName] = ContainerConfig (containerName, sourceName, noSysSuffix = self._noSysSuffix, originalName = originalName, isMet = isMet)
588 def renameFinalContainers (self) :
589 """post-process the configured algorithms, tools and services to
590 strip the auto-generated `_STEP<n>` suffix from each container's
591 *final* name in every property value.
593 This is mostly needed in case the user has further downstream
594 algorithms that rely on the exact name of containers in the
595 event store. For anything configured through the
596 `ConfigAccumulator` this doesn't matter, as the names are
597 configured consistently."""
600 for containerConfig in self._containerConfig.values() :
601 if not containerConfig.names :
603 base = containerConfig.name
604 lastName = containerConfig.names[-1]
605 match = re.match (re.escape (base) + r'_STEP\d+', lastName)
607 substitutions.append ((match.group(0), base))
608 # keep ContainerConfig in sync, in case anything reads
609 # currentName() after this pass
610 containerConfig.names[-1] = substituteValue (lastName, [(match.group(0), base)])
611 if not substitutions :
613 for component in self._algorithms.values() :
614 substituteComponentProperties (component, substitutions)
709 def getFullSelection (self, containerName, selectionName,
710 *, skipBase = False, excludeFrom = None) :
712 """get the selection string for the given selection on the given
715 This can handle both individual selections or selection
716 expressions (e.g. `loose||tight`) with the later being
717 properly expanded. Either way the base selection (i.e. the
718 selection without a name) will always be applied on top.
720 containerName --- the container the selection is defined on
721 selectionName --- the name of the selection, or a selection
722 expression based on multiple named selections
723 skipBase --- will avoid the base selection, and should normally
724 not be used by the end-user.
725 excludeFrom --- a set of string names of selection sources to exclude
726 e.g. to exclude OR selections from MET
728 if "." in containerName:
729 raise ValueError (f'invalid containerName argument: {containerName} , it contains a "." '
730 'which is used to indicate container+selection. You should only pass the container.')
731 if containerName not in self._containerConfig :
734 if excludeFrom is None :
736 elif not isinstance(excludeFrom, set) :
737 raise ValueError (f'invalid excludeFrom argument (need set of strings): {excludeFrom}')
739 # Check if this is actually a selection expression,
740 # e.g. `A||B` and if so translate it into a complex expression
741 # for the user. I'm not trying to do any complex syntax
742 # recognition, but instead just produce an expression that the
743 # C++ parser ought to be able to read.
744 if selectionName != '' and \
745 not self._selectionNameExpr.fullmatch (selectionName) :
747 while selectionName != '' :
748 match = self._selectionNameExpr.match (selectionName)
750 result += selectionName[0]
751 selectionName = selectionName[1:]
753 subname = match.group(0)
754 subresult = self.getFullSelection (containerName, subname, skipBase = True, excludeFrom=excludeFrom)
756 result += '(' + subresult + ')'
759 selectionName = selectionName[len(subname):]
760 subresult = self.getFullSelection (containerName, '', excludeFrom=excludeFrom)
762 result = subresult + '&&(' + result + ')'
763 return '(' + result + ')' if result !='' else ''
765 config = self._containerConfig[containerName]
767 hasSelectionName = False
768 for selection in config.selections :
769 if ((selection.name == '' and not skipBase) or selection.name == selectionName) and (selection.comesFrom not in excludeFrom) :
770 decorations += [selection.decoration]
771 if selection.name == selectionName :
772 hasSelectionName = True
773 if not hasSelectionName and selectionName != '' :
774 raise KeyError (f'invalid selection name: {containerName}.{selectionName}')
775 return '&&'.join (decorations)
778 def getSelectionCutFlow (self, containerName, selectionName) :
780 """get the individual selections as a list for producing the cutflow for
781 the given selection on the given container
783 This can only handle individual selections, not selection
784 expressions (e.g. `loose||tight`).
787 if containerName not in self._containerConfig :
790 # Check if this is actually a selection expression,
791 # e.g. `A||B` and if so translate it into a complex expression
792 # for the user. I'm not trying to do any complex syntax
793 # recognition, but instead just produce an expression that the
794 # C++ parser ought to be able to read.
795 if selectionName != '' and \
796 not self._selectionNameExpr.fullmatch (selectionName) :
797 raise ValueError (f'not allowed to do cutflow on selection expression: {selectionName}')
799 config = self._containerConfig[containerName]
801 for selection in config.selections :
802 if (selection.name == '' or selection.name == selectionName) :
803 decorations += [selection.decoration]
859 def addOutputVar (self, containerName, variableName, outputName,
860 *, noSys=False, enabled=True, auxType=None) :
861 """add an output variable for the given container to the output
863 if containerName in self._outputContainers:
864 self.addOutputVar(self.getOutputContainerOrigin(containerName), variableName, outputName, noSys=noSys, enabled=enabled, auxType=auxType)
867 if containerName not in self._containerConfig :
868 raise KeyError (f"container unknown: {containerName}")
869 baseConfig = self._containerConfig[containerName].outputs
870 if outputName in baseConfig :
871 raise KeyError (f"duplicate output variable name: {outputName}")
872 config = OutputConfig (containerName, variableName, noSys=noSys, enabled=enabled, auxType=auxType)
873 baseConfig[outputName] = config