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):
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)
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")
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")
188 raise ValueError(
"Cannot pass both dataYear and flags/autoconfigFromFlags arguments")
190 if self.
_flags.Input.isMC:
191 if self.
_flags.Sim.ISF.Simulator.usesFastCaloSim():
192 dataType = DataType.FastSim
194 dataType = DataType.FullSim
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)
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()
206 raise ValueError (
"need to specify dataType if flags are not set")
208 if isinstance(dataType, str):
210 dataType = DataType.FullSim
211 elif dataType ==
'afii':
212 dataType = DataType.FastSim
215 if isPhyslite
is None:
217 if geometry
is not None:
219 geometry = LHCPeriod(geometry)
220 if geometry
is LHCPeriod.Run1:
221 raise ValueError (
"invalid Run geometry: %s" % geometry.value)
222 flags.GeoModel.Run = geometry
224 flags.Input.MCChannelNumber = dsid
225 if campaign
is not None:
226 flags.Input.MCCampaign = campaign
228 flags.Input.DataYear = dataYear
229 if runNumber
is None:
233 flags.Input.RunNumbers = [runNumber]
234 hltSummary =
'HLTNav_Summary_DAODSlimmed'
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)
271 raise ValueError (
"need to pass algSeq if not using ComponentAccumulator")
345 def setAlgPostfix (self, postfix : str) :
346 """set the current postfix to be appended to algorithm names
348 Blocks should not call this directly, but rather implement the
349 instanceName method, which will be used to generate the postfix
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)
355 self._algPostfix = ''
356 elif postfix[0] != '_' :
357 self._algPostfix = '_' + postfix
359 self._algPostfix = postfix
361 def getAlgorithm (self, name : str):
362 """get the algorithm with the given name
364 Despite the name this will also return services and tools. It is
365 mostly meant for internal use, particularly for the property
367 name = name + self._algPostfix
368 if name not in self._algorithms:
370 return self._algorithms[name]
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
376 if name in self._algorithms :
377 raise Exception ('duplicate algorithms: ' + name + ' with algPostfix=' + self._algPostfix)
379 alg = DualUseConfig.createReentrantAlgorithm (type, name)
381 alg = DualUseConfig.createAlgorithm (type, name)
383 if DualUseConfig.isAthena:
384 if self._algSeq is not None:
385 self.CA.addEventAlgo(alg,self._algSeq.name)
387 self.CA.addEventAlgo(alg)
391 self._algorithms[name] = alg
392 self._currentAlg = alg
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]
403 def createService (self, type, name) :
404 '''create a new service and register it as the "current algorithm"'''
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)
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
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]
426 def createPublicTool (self, type, name) :
427 '''create a new public tool and register it as the "current algorithm"'''
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)
437 # We're not, so let's remember this as a "normal" algorithm:
439 self._algorithms[name] = tool
440 self._currentAlg = tool
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]
464 def setSourceName (self, containerName, sourceName,
465 *, originalName = None, isMet = False) :
466 """set the (default) name of the source/original container
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
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
479 if containerName not in self._containerConfig :
480 self._containerConfig[containerName] = ContainerConfig (containerName, sourceName, noSysSuffix = self._noSysSuffix, originalName = originalName, isMet = isMet)
483 def writeName (self, containerName, *, isMet=None) :
484 """register that the given container will be made and return
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()
498 def readName (self, containerName, *, nominal=False) :
499 """get the name of the "current copy" of the given container
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.
505 if containerName not in self._containerConfig :
506 raise Exception ("no source container for: " + containerName)
507 return self._containerConfig[containerName].currentName(nominal=nominal)
519 def wantCopy (self, containerName) :
520 """ask whether we want/need a copy of the container
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.
525 if containerName not in self._containerConfig :
526 raise Exception ("no source container for: " + containerName)
527 return self._containerConfig[containerName].index == 0
530 def originalName (self, containerName) :
531 """get the "original" name of the given container
533 This is mostly/exclusively used for jet containers, so that
534 subsequent configurations know which jet container they
537 if containerName not in self._containerConfig :
538 raise Exception ("container unknown: " + containerName)
539 result = self._containerConfig[containerName].originalName
541 raise Exception ("no original name for: " + containerName)
544 def getContainerMeta (self, containerName, metaField, defaultValue=None, *, failOnMiss=False) :
545 """get the meta information for the given container
547 This is used to pass down meta-information from the
548 configuration to the algorithms.
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]
555 raise Exception ('unknown meta-field' + metaField + ' on container ' + containerName)
558 def setContainerMeta (self, containerName, metaField, value, *, allowOverwrite=False) :
559 """set the meta information for the given container
561 This is used to pass down meta-information from the
562 configuration to the algorithms.
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
570 def isMetContainer (self, containerName) :
571 """whether the given container is registered as a MET container
573 This is mostly/exclusively used for determining whether to
574 write out the whole container or just a single MET term.
576 if containerName not in self._containerConfig :
577 raise Exception ("container unknown: " + containerName)
578 return self._containerConfig[containerName].isMet
581 def readNameAndSelection (self, containerName, *, excludeFrom = None) :
582 """get the name of the "current copy" of the given container, and the
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".
589 split = containerName.split (".")
591 objectName = split[0]
593 elif len(split) == 2 :
594 objectName = split[0]
595 selectionName = split[1]
597 raise Exception ('invalid object selection name: ' + containerName)
598 return self.readName (objectName), self.getFullSelection (objectName, selectionName, excludeFrom=excludeFrom)
601 def nextPass (self) :
602 """switch to the next configuration pass
604 Configuration happens in two steps, with all the blocks processed
605 twice. This switches from the first to the second pass.
608 raise Exception ("already performed final pass")
609 for name in self._containerConfig :
610 self._containerConfig[name].nextPass ()
612 self._currentAlg = None
613 self._outputContainers = {}
616 def getPreselection (self, containerName, selectionName, *, asList = False) :
618 """get the preselection string for the given selection on the given
621 if selectionName != '' and not self._selectionNameExpr.fullmatch (selectionName) :
622 raise ValueError ('invalid selection name: ' + selectionName)
623 if containerName not in self._containerConfig :
625 config = self._containerConfig[containerName]
627 for selection in config.selections :
628 if (selection.name == '' or selection.name == selectionName) and \
629 selection.preselection :
630 decorations += [selection.decoration]
634 return '&&'.join (decorations)
637 def getFullSelection (self, containerName, selectionName,
638 *, skipBase = False, excludeFrom = None) :
640 """get the selection string for the given selection on the given
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.
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
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 :
662 if excludeFrom is None :
664 elif not isinstance(excludeFrom, set) :
665 raise ValueError ('invalid excludeFrom argument (need set of strings): ' + str(excludeFrom))
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) :
675 while selectionName != '' :
676 match = self._selectionNameExpr.match (selectionName)
678 result += selectionName[0]
679 selectionName = selectionName[1:]
681 subname = match.group(0)
682 subresult = self.getFullSelection (containerName, subname, skipBase = True, excludeFrom=excludeFrom)
684 result += '(' + subresult + ')'
687 selectionName = selectionName[len(subname):]
688 subresult = self.getFullSelection (containerName, '', excludeFrom=excludeFrom)
690 result = subresult + '&&(' + result + ')'
691 return '(' + result + ')' if result !='' else ''
693 config = self._containerConfig[containerName]
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)
706 def getSelectionCutFlow (self, containerName, selectionName) :
708 """get the individual selections as a list for producing the cutflow for
709 the given selection on the given container
711 This can only handle individual selections, not selection
712 expressions (e.g. `loose||tight`).
715 if containerName not in self._containerConfig :
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)
727 config = self._containerConfig[containerName]
729 for selection in config.selections :
730 if (selection.name == '' or selection.name == selectionName) :
731 decorations += [selection.decoration]
735 def addEventCutFlow (self, selection, decorations) :
737 """register a new event cutflow, adding it to the dictionary with key 'selection'
738 and value 'decorations', a list of decorated selections
741 if selection in self._eventcutflow.keys():
742 raise ValueError ('the event cutflow dictionary already contains an entry ' + selection)
744 self._eventcutflow[selection] = decorations
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
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