144 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):
150 if autoconfigFromFlags
is not None:
151 if flags
is not None:
152 raise ValueError(
"Cannot pass both flags and autoconfigFromFlags arguments")
153 flags = autoconfigFromFlags
154 warnings.warn (
'Using autoconfigFromFlags parameter is deprecated, use flags instead', category=deprecationWarningCategory, stacklevel=2)
164 if self.
_flags is not None:
165 if dataType
is not None:
166 raise ValueError(
"Cannot pass both dataType and flags/autoconfigFromFlags arguments")
167 if isPhyslite
is not None:
168 raise ValueError(
"Cannot pass both isPhyslite and flags/autoconfigFromFlags arguments")
169 if geometry
is not None:
170 raise ValueError(
"Cannot pass both geometry and flags/autoconfigFromFlags arguments")
172 raise ValueError(
"Cannot pass both dsid and flags/autoconfigFromFlags arguments")
173 if campaign
is not None:
174 raise ValueError(
"Cannot pass both campaign and flags/autoconfigFromFlags arguments")
175 if runNumber
is not None:
176 raise ValueError(
"Cannot pass both runNumber and flags/autoconfigFromFlags arguments")
178 raise ValueError(
"Cannot pass both dataYear and flags/autoconfigFromFlags arguments")
180 if self.
_flags.Input.isMC:
181 if self.
_flags.Sim.ISF.Simulator.usesFastCaloSim():
182 dataType = DataType.FastSim
184 dataType = DataType.FullSim
186 dataType = DataType.Data
187 isPhyslite =
'StreamDAOD_PHYSLITE' in self.
_flags.Input.ProcessingTags
188 from TrigDecisionTool.TrigDecisionToolHelpers
import (
189 getRun3NavigationContainerFromInput_forAnalysisBase)
190 hltSummary = getRun3NavigationContainerFromInput_forAnalysisBase(self.
_flags)
192 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)
193 from AthenaConfiguration.AllConfigFlags
import initConfigFlags
194 flags = initConfigFlags()
196 raise ValueError (
"need to specify dataType if flags are not set")
198 if isinstance(dataType, str):
200 dataType = DataType.FullSim
201 elif dataType ==
'afii':
202 dataType = DataType.FastSim
205 if isPhyslite
is None:
207 if geometry
is not None:
209 geometry = LHCPeriod(geometry)
210 if geometry
is LHCPeriod.Run1:
211 raise ValueError (
"invalid Run geometry: %s" % geometry.value)
212 flags.GeoModel.Run = geometry
214 flags.Input.MCChannelNumber = dsid
215 if campaign
is not None:
216 flags.Input.MCCampaign = campaign
218 flags.Input.DataYear = dataYear
219 if runNumber
is None:
223 flags.Input.RunNumbers = [runNumber]
224 hltSummary =
'HLTNav_Summary_DAODSlimmed'
256 if DualUseConfig.useComponentAccumulator:
257 from AthenaConfiguration.ComponentAccumulator
import ComponentAccumulator
258 self.
CA = ComponentAccumulator()
262 self.
CA.addSequence(algSeq)
265 raise ValueError (
"need to pass algSeq if not using ComponentAccumulator")
329 def setAlgPostfix (self, postfix : str) :
330 """set the current postfix to be appended to algorithm names
332 Blocks should not call this directly, but rather implement the
333 instanceName method, which will be used to generate the postfix
335 # make sure the postfix matches the expected format ([_a-zA-Z0-9]*)
336 if re.compile ('^[_a-zA-Z0-9]*$').match (postfix) is None :
337 raise ValueError ('invalid algorithm postfix: ' + postfix)
339 self._algPostfix = ''
340 elif postfix[0] != '_' :
341 self._algPostfix = '_' + postfix
343 self._algPostfix = postfix
345 def getAlgorithm (self, name : str):
346 """get the algorithm with the given name
348 Despite the name this will also return services and tools. It is
349 mostly meant for internal use, particularly for the property
351 name = name + self._algPostfix
352 if name not in self._algorithms:
354 return self._algorithms[name]
356 def createAlgorithm (self, type, name, reentrant=False) :
357 """create a new algorithm and register it as the current algorithm"""
358 name = name + self._algPostfix
360 if name in self._algorithms :
361 raise Exception ('duplicate algorithms: ' + name + ' with algPostfix=' + self._algPostfix)
363 alg = DualUseConfig.createReentrantAlgorithm (type, name)
365 alg = DualUseConfig.createAlgorithm (type, name)
367 if DualUseConfig.useComponentAccumulator:
369 self.CA.addEventAlgo(alg,self._algSeq.name)
371 self.CA.addEventAlgo(alg)
374 self._algorithms[name] = alg
375 self._currentAlg = alg
378 if name not in self._algorithms :
379 raise Exception ('unknown algorithm requested: ' + name)
380 self._currentAlg = self._algorithms[name]
381 if self.CA and self._currentAlg != self.CA.getEventAlgo(name) :
382 raise Exception ('change to algorithm object: ' + name)
383 return self._algorithms[name]
386 def createService (self, type, name) :
387 '''create a new service and register it as the "current algorithm"'''
389 if name in self._algorithms :
390 raise Exception ('duplicate service: ' + name)
391 service = DualUseConfig.createService (type, name)
392 # Avoid importing AthenaCommon.AppMgr in a CA Athena job
393 # as it modifies Gaudi behaviour
394 if DualUseConfig.isAthena:
395 if DualUseConfig.useComponentAccumulator:
396 self.CA.addService(service)
398 # We're not, so let's remember this as a "normal" algorithm:
399 self._algSeq += service
400 self._algorithms[name] = service
401 self._currentAlg = service
404 if name not in self._algorithms :
405 raise Exception ('unknown service requested: ' + name)
406 self._currentAlg = self._algorithms[name]
407 return self._algorithms[name]
410 def createPublicTool (self, type, name) :
411 '''create a new public tool and register it as the "current algorithm"'''
413 if name in self._algorithms :
414 raise Exception ('duplicate public tool: ' + name)
415 tool = DualUseConfig.createPublicTool (type, name)
416 # Avoid importing AthenaCommon.AppMgr in a CA Athena job
417 # as it modifies Gaudi behaviour
418 if DualUseConfig.isAthena:
419 if DualUseConfig.useComponentAccumulator:
420 self.CA.addPublicTool(tool)
422 # We're not, so let's remember this as a "normal" algorithm:
424 self._algorithms[name] = tool
425 self._currentAlg = tool
428 if name not in self._algorithms :
429 raise Exception ('unknown public tool requested: ' + name)
430 self._currentAlg = self._algorithms[name]
431 return self._algorithms[name]
440 def setSourceName (self, containerName, sourceName,
441 *, originalName = None, isMet = False) :
442 """set the (default) name of the source/original container
444 This is essentially meant to allow using e.g. the muon
445 configuration and the user not having to manually specify that
446 they want to use the Muons/AnalysisMuons container from the
449 In addition it allows to set the original name of the
450 container (which may be different from the source name), which
451 is mostly/exclusively used for jet containers, so that
452 subsequent configurations know which jet container they
455 if containerName not in self._containerConfig :
456 self._containerConfig[containerName] = ContainerConfig (containerName, sourceName, noSysSuffix = self._noSysSuffix, originalName = originalName, isMet = isMet)
459 def writeName (self, containerName, *, isMet=None) :
460 """register that the given container will be made and return
462 if containerName not in self._containerConfig :
463 self._containerConfig[containerName] = ContainerConfig (containerName, sourceName = None, noSysSuffix = self._noSysSuffix)
464 if self._containerConfig[containerName].sourceName is not None :
465 raise Exception ("trying to write container configured for input: " + containerName)
466 if self._containerConfig[containerName].index != 0 :
467 raise Exception ("trying to write container twice: " + containerName)
468 self._containerConfig[containerName].index += 1
469 if isMet is not None :
470 self._containerConfig[containerName].isMet = isMet
471 return self._containerConfig[containerName].currentName()
474 def readName (self, containerName) :
475 """get the name of the "current copy" of the given container
477 As extra copies get created during processing this will track
478 the correct name of the current copy. Optionally one can pass
479 in the name of the container before the first copy.
481 if containerName not in self._containerConfig :
482 raise Exception ("no source container for: " + containerName)
483 return self._containerConfig[containerName].currentName()
495 def wantCopy (self, containerName) :
496 """ask whether we want/need a copy of the container
498 This usually only happens if no copy of the container has been
499 made yet and the copy is needed to allow modifications, etc.
501 if containerName not in self._containerConfig :
502 raise Exception ("no source container for: " + containerName)
503 return self._containerConfig[containerName].index == 0
506 def originalName (self, containerName) :
507 """get the "original" name of the given container
509 This is mostly/exclusively used for jet containers, so that
510 subsequent configurations know which jet container they
513 if containerName not in self._containerConfig :
514 raise Exception ("container unknown: " + containerName)
515 result = self._containerConfig[containerName].originalName
517 raise Exception ("no original name for: " + containerName)
520 def getContainerMeta (self, containerName, metaField, defaultValue=None, *, failOnMiss=False) :
521 """get the meta information for the given container
523 This is used to pass down meta-information from the
524 configuration to the algorithms.
526 if containerName not in self._containerConfig :
527 raise Exception ("container unknown: " + containerName)
528 if metaField in self._containerConfig[containerName].meta :
529 return self._containerConfig[containerName].meta[metaField]
531 raise Exception ('unknown meta-field' + metaField + ' on container ' + containerName)
534 def setContainerMeta (self, containerName, metaField, value, *, allowOverwrite=False) :
535 """set the meta information for the given container
537 This is used to pass down meta-information from the
538 configuration to the algorithms.
540 if containerName not in self._containerConfig :
541 raise Exception ("container unknown: " + containerName)
542 if not allowOverwrite and metaField in self._containerConfig[containerName].meta :
543 raise Exception ('duplicate meta-field' + metaField + ' on container ' + containerName)
544 self._containerConfig[containerName].meta[metaField] = value
546 def isMetContainer (self, containerName) :
547 """whether the given container is registered as a MET container
549 This is mostly/exclusively used for determining whether to
550 write out the whole container or just a single MET term.
552 if containerName not in self._containerConfig :
553 raise Exception ("container unknown: " + containerName)
554 return self._containerConfig[containerName].isMet
557 def readNameAndSelection (self, containerName, *, excludeFrom = None) :
558 """get the name of the "current copy" of the given container, and the
561 This is mostly meant for MET and OR for whom the actual object
562 selection is relevant, and which as such allow to pass in the
563 working point as "ObjectName.WorkingPoint".
565 split = containerName.split (".")
567 objectName = split[0]
569 elif len(split) == 2 :
570 objectName = split[0]
571 selectionName = split[1]
573 raise Exception ('invalid object selection name: ' + containerName)
574 return self.readName (objectName), self.getFullSelection (objectName, selectionName, excludeFrom=excludeFrom)
577 def nextPass (self) :
578 """switch to the next configuration pass
580 Configuration happens in two steps, with all the blocks processed
581 twice. This switches from the first to the second pass.
584 raise Exception ("already performed final pass")
585 for name in self._containerConfig :
586 self._containerConfig[name].nextPass ()
588 self._currentAlg = None
589 self._outputContainers = {}
592 def getPreselection (self, containerName, selectionName, *, asList = False) :
594 """get the preselection string for the given selection on the given
597 if selectionName != '' and not self._selectionNameExpr.fullmatch (selectionName) :
598 raise ValueError ('invalid selection name: ' + selectionName)
599 if containerName not in self._containerConfig :
601 config = self._containerConfig[containerName]
603 for selection in config.selections :
604 if (selection.name == '' or selection.name == selectionName) and \
605 selection.preselection :
606 decorations += [selection.decoration]
610 return '&&'.join (decorations)
613 def getFullSelection (self, containerName, selectionName,
614 *, skipBase = False, excludeFrom = None) :
616 """get the selection string for the given selection on the given
619 This can handle both individual selections or selection
620 expressions (e.g. `loose||tight`) with the later being
621 properly expanded. Either way the base selection (i.e. the
622 selection without a name) will always be applied on top.
624 containerName --- the container the selection is defined on
625 selectionName --- the name of the selection, or a selection
626 expression based on multiple named selections
627 skipBase --- will avoid the base selection, and should normally
628 not be used by the end-user.
629 excludeFrom --- a set of string names of selection sources to exclude
630 e.g. to exclude OR selections from MET
632 if "." in containerName:
633 raise ValueError (f'invalid containerName argument: {containerName} , it contains a "." '
634 'which is used to indicate container+selection. You should only pass the container.')
635 if containerName not in self._containerConfig :
638 if excludeFrom is None :
640 elif not isinstance(excludeFrom, set) :
641 raise ValueError ('invalid excludeFrom argument (need set of strings): ' + str(excludeFrom))
643 # Check if this is actually a selection expression,
644 # e.g. `A||B` and if so translate it into a complex expression
645 # for the user. I'm not trying to do any complex syntax
646 # recognition, but instead just produce an expression that the
647 # C++ parser ought to be able to read.
648 if selectionName != '' and \
649 not self._selectionNameExpr.fullmatch (selectionName) :
651 while selectionName != '' :
652 match = self._selectionNameExpr.match (selectionName)
654 result += selectionName[0]
655 selectionName = selectionName[1:]
657 subname = match.group(0)
658 subresult = self.getFullSelection (containerName, subname, skipBase = True, excludeFrom=excludeFrom)
660 result += '(' + subresult + ')'
663 selectionName = selectionName[len(subname):]
664 subresult = self.getFullSelection (containerName, '', excludeFrom=excludeFrom)
666 result = subresult + '&&(' + result + ')'
667 return '(' + result + ')' if result !='' else ''
669 config = self._containerConfig[containerName]
671 hasSelectionName = False
672 for selection in config.selections :
673 if ((selection.name == '' and not skipBase) or selection.name == selectionName) and (selection.comesFrom not in excludeFrom) :
674 decorations += [selection.decoration]
675 if selection.name == selectionName :
676 hasSelectionName = True
677 if not hasSelectionName and selectionName != '' :
678 raise KeyError ('invalid selection name: ' + containerName + '.' + selectionName)
679 return '&&'.join (decorations)
682 def getSelectionCutFlow (self, containerName, selectionName) :
684 """get the individual selections as a list for producing the cutflow for
685 the given selection on the given container
687 This can only handle individual selections, not selection
688 expressions (e.g. `loose||tight`).
691 if containerName not in self._containerConfig :
694 # Check if this is actually a selection expression,
695 # e.g. `A||B` and if so translate it into a complex expression
696 # for the user. I'm not trying to do any complex syntax
697 # recognition, but instead just produce an expression that the
698 # C++ parser ought to be able to read.
699 if selectionName != '' and \
700 not self._selectionNameExpr.fullmatch (selectionName) :
701 raise ValueError ('not allowed to do cutflow on selection expression: ' + selectionName)
703 config = self._containerConfig[containerName]
705 for selection in config.selections :
706 if (selection.name == '' or selection.name == selectionName) :
707 decorations += [selection.decoration]
711 def addEventCutFlow (self, selection, decorations) :
713 """register a new event cutflow, adding it to the dictionary with key 'selection'
714 and value 'decorations', a list of decorated selections
717 if selection in self._eventcutflow.keys():
718 raise ValueError ('the event cutflow dictionary already contains an entry ' + selection)
720 self._eventcutflow[selection] = decorations
769 def addOutputVar (self, containerName, variableName, outputName,
770 *, noSys=False, enabled=True) :
771 """add an output variable for the given container to the output
774 if containerName not in self._containerConfig :
775 raise KeyError ("container unknown: " + containerName)
776 baseConfig = self._containerConfig[containerName].outputs
777 if outputName in baseConfig :
778 raise KeyError ("duplicate output variable name: " + outputName)
779 config = OutputConfig (containerName, variableName, noSys=noSys, enabled=enabled)
780 baseConfig[outputName] = config