145 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):
151 if autoconfigFromFlags
is not None:
152 if flags
is not None:
153 raise ValueError(
"Cannot pass both flags and autoconfigFromFlags arguments")
154 flags = autoconfigFromFlags
155 warnings.warn (
'Using autoconfigFromFlags parameter is deprecated, use flags instead', category=deprecationWarningCategory, stacklevel=2)
165 if self.
_flags is not None:
166 if dataType
is not None:
167 raise ValueError(
"Cannot pass both dataType and flags/autoconfigFromFlags arguments")
168 if isPhyslite
is not None:
169 raise ValueError(
"Cannot pass both isPhyslite and flags/autoconfigFromFlags arguments")
170 if geometry
is not None:
171 raise ValueError(
"Cannot pass both geometry and flags/autoconfigFromFlags arguments")
173 raise ValueError(
"Cannot pass both dsid and flags/autoconfigFromFlags arguments")
174 if campaign
is not None:
175 raise ValueError(
"Cannot pass both campaign and flags/autoconfigFromFlags arguments")
176 if runNumber
is not None:
177 raise ValueError(
"Cannot pass both runNumber and flags/autoconfigFromFlags arguments")
179 raise ValueError(
"Cannot pass both dataYear and flags/autoconfigFromFlags arguments")
181 if self.
_flags.Input.isMC:
182 if self.
_flags.Sim.ISF.Simulator.usesFastCaloSim():
183 dataType = DataType.FastSim
185 dataType = DataType.FullSim
187 dataType = DataType.Data
188 isPhyslite =
'StreamDAOD_PHYSLITE' in self.
_flags.Input.ProcessingTags
189 from TrigDecisionTool.TrigDecisionToolHelpers
import (
190 getRun3NavigationContainerFromInput_forAnalysisBase)
191 hltSummary = getRun3NavigationContainerFromInput_forAnalysisBase(self.
_flags)
193 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)
194 from AthenaConfiguration.AllConfigFlags
import initConfigFlags
195 flags = initConfigFlags()
197 raise ValueError (
"need to specify dataType if flags are not set")
199 if isinstance(dataType, str):
201 dataType = DataType.FullSim
202 elif dataType ==
'afii':
203 dataType = DataType.FastSim
206 if isPhyslite
is None:
208 if geometry
is not None:
210 geometry = LHCPeriod(geometry)
211 if geometry
is LHCPeriod.Run1:
212 raise ValueError (
"invalid Run geometry: %s" % geometry.value)
213 flags.GeoModel.Run = geometry
215 flags.Input.MCChannelNumber = dsid
216 if campaign
is not None:
217 flags.Input.MCCampaign = campaign
219 flags.Input.DataYear = dataYear
220 if runNumber
is None:
224 flags.Input.RunNumbers = [runNumber]
225 hltSummary =
'HLTNav_Summary_DAODSlimmed'
257 if DualUseConfig.useComponentAccumulator:
258 from AthenaConfiguration.ComponentAccumulator
import ComponentAccumulator
259 self.
CA = ComponentAccumulator()
263 self.
CA.addSequence(algSeq)
266 raise ValueError (
"need to pass algSeq if not using ComponentAccumulator")
330 def setAlgPostfix (self, postfix : str) :
331 """set the current postfix to be appended to algorithm names
333 Blocks should not call this directly, but rather implement the
334 instanceName method, which will be used to generate the postfix
336 # make sure the postfix matches the expected format ([_a-zA-Z0-9]*)
337 if re.compile ('^[_a-zA-Z0-9]*$').match (postfix) is None :
338 raise ValueError ('invalid algorithm postfix: ' + postfix)
340 self._algPostfix = ''
341 elif postfix[0] != '_' :
342 self._algPostfix = '_' + postfix
344 self._algPostfix = postfix
346 def getAlgorithm (self, name : str):
347 """get the algorithm with the given name
349 Despite the name this will also return services and tools. It is
350 mostly meant for internal use, particularly for the property
352 name = name + self._algPostfix
353 if name not in self._algorithms:
355 return self._algorithms[name]
357 def createAlgorithm (self, type, name, reentrant=False) :
358 """create a new algorithm and register it as the current algorithm"""
359 name = name + self._algPostfix
361 if name in self._algorithms :
362 raise Exception ('duplicate algorithms: ' + name + ' with algPostfix=' + self._algPostfix)
364 alg = DualUseConfig.createReentrantAlgorithm (type, name)
366 alg = DualUseConfig.createAlgorithm (type, name)
368 if DualUseConfig.useComponentAccumulator:
370 self.CA.addEventAlgo(alg,self._algSeq.name)
372 self.CA.addEventAlgo(alg)
375 self._algorithms[name] = alg
376 self._currentAlg = alg
379 if name not in self._algorithms :
380 raise Exception ('unknown algorithm requested: ' + name)
381 self._currentAlg = self._algorithms[name]
382 if self.CA and self._currentAlg != self.CA.getEventAlgo(name) :
383 raise Exception ('change to algorithm object: ' + name)
384 return self._algorithms[name]
387 def createService (self, type, name) :
388 '''create a new service and register it as the "current algorithm"'''
390 if name in self._algorithms :
391 raise Exception ('duplicate service: ' + name)
392 service = DualUseConfig.createService (type, name)
393 # Avoid importing AthenaCommon.AppMgr in a CA Athena job
394 # as it modifies Gaudi behaviour
395 if DualUseConfig.isAthena:
396 if DualUseConfig.useComponentAccumulator:
397 self.CA.addService(service)
399 # We're not, so let's remember this as a "normal" algorithm:
400 self._algSeq += service
401 self._algorithms[name] = service
402 self._currentAlg = service
405 if name not in self._algorithms :
406 raise Exception ('unknown service requested: ' + name)
407 self._currentAlg = self._algorithms[name]
408 return self._algorithms[name]
411 def createPublicTool (self, type, name) :
412 '''create a new public tool and register it as the "current algorithm"'''
414 if name in self._algorithms :
415 raise Exception ('duplicate public tool: ' + name)
416 tool = DualUseConfig.createPublicTool (type, name)
417 # Avoid importing AthenaCommon.AppMgr in a CA Athena job
418 # as it modifies Gaudi behaviour
419 if DualUseConfig.isAthena:
420 if DualUseConfig.useComponentAccumulator:
421 self.CA.addPublicTool(tool)
423 # We're not, so let's remember this as a "normal" algorithm:
425 self._algorithms[name] = tool
426 self._currentAlg = tool
429 if name not in self._algorithms :
430 raise Exception ('unknown public tool requested: ' + name)
431 self._currentAlg = self._algorithms[name]
432 return self._algorithms[name]
441 def setSourceName (self, containerName, sourceName,
442 *, originalName = None, isMet = False) :
443 """set the (default) name of the source/original container
445 This is essentially meant to allow using e.g. the muon
446 configuration and the user not having to manually specify that
447 they want to use the Muons/AnalysisMuons container from the
450 In addition it allows to set the original name of the
451 container (which may be different from the source name), which
452 is mostly/exclusively used for jet containers, so that
453 subsequent configurations know which jet container they
456 if containerName not in self._containerConfig :
457 self._containerConfig[containerName] = ContainerConfig (containerName, sourceName, noSysSuffix = self._noSysSuffix, originalName = originalName, isMet = isMet)
460 def writeName (self, containerName, *, isMet=None) :
461 """register that the given container will be made and return
463 if containerName not in self._containerConfig :
464 self._containerConfig[containerName] = ContainerConfig (containerName, sourceName = None, noSysSuffix = self._noSysSuffix)
465 if self._containerConfig[containerName].sourceName is not None :
466 raise Exception ("trying to write container configured for input: " + containerName)
467 if self._containerConfig[containerName].index != 0 :
468 raise Exception ("trying to write container twice: " + containerName)
469 self._containerConfig[containerName].index += 1
470 if isMet is not None :
471 self._containerConfig[containerName].isMet = isMet
472 return self._containerConfig[containerName].currentName()
475 def readName (self, containerName) :
476 """get the name of the "current copy" of the given container
478 As extra copies get created during processing this will track
479 the correct name of the current copy. Optionally one can pass
480 in the name of the container before the first copy.
482 if containerName not in self._containerConfig :
483 raise Exception ("no source container for: " + containerName)
484 return self._containerConfig[containerName].currentName()
496 def wantCopy (self, containerName) :
497 """ask whether we want/need a copy of the container
499 This usually only happens if no copy of the container has been
500 made yet and the copy is needed to allow modifications, etc.
502 if containerName not in self._containerConfig :
503 raise Exception ("no source container for: " + containerName)
504 return self._containerConfig[containerName].index == 0
507 def originalName (self, containerName) :
508 """get the "original" name of the given container
510 This is mostly/exclusively used for jet containers, so that
511 subsequent configurations know which jet container they
514 if containerName not in self._containerConfig :
515 raise Exception ("container unknown: " + containerName)
516 result = self._containerConfig[containerName].originalName
518 raise Exception ("no original name for: " + containerName)
521 def getContainerMeta (self, containerName, metaField, defaultValue=None, *, failOnMiss=False) :
522 """get the meta information for the given container
524 This is used to pass down meta-information from the
525 configuration to the algorithms.
527 if containerName not in self._containerConfig :
528 raise Exception ("container unknown: " + containerName)
529 if metaField in self._containerConfig[containerName].meta :
530 return self._containerConfig[containerName].meta[metaField]
532 raise Exception ('unknown meta-field' + metaField + ' on container ' + containerName)
535 def setContainerMeta (self, containerName, metaField, value, *, allowOverwrite=False) :
536 """set the meta information for the given container
538 This is used to pass down meta-information from the
539 configuration to the algorithms.
541 if containerName not in self._containerConfig :
542 raise Exception ("container unknown: " + containerName)
543 if not allowOverwrite and metaField in self._containerConfig[containerName].meta :
544 raise Exception ('duplicate meta-field' + metaField + ' on container ' + containerName)
545 self._containerConfig[containerName].meta[metaField] = value
547 def isMetContainer (self, containerName) :
548 """whether the given container is registered as a MET container
550 This is mostly/exclusively used for determining whether to
551 write out the whole container or just a single MET term.
553 if containerName not in self._containerConfig :
554 raise Exception ("container unknown: " + containerName)
555 return self._containerConfig[containerName].isMet
558 def readNameAndSelection (self, containerName, *, excludeFrom = None) :
559 """get the name of the "current copy" of the given container, and the
562 This is mostly meant for MET and OR for whom the actual object
563 selection is relevant, and which as such allow to pass in the
564 working point as "ObjectName.WorkingPoint".
566 split = containerName.split (".")
568 objectName = split[0]
570 elif len(split) == 2 :
571 objectName = split[0]
572 selectionName = split[1]
574 raise Exception ('invalid object selection name: ' + containerName)
575 return self.readName (objectName), self.getFullSelection (objectName, selectionName, excludeFrom=excludeFrom)
578 def nextPass (self) :
579 """switch to the next configuration pass
581 Configuration happens in two steps, with all the blocks processed
582 twice. This switches from the first to the second pass.
585 raise Exception ("already performed final pass")
586 for name in self._containerConfig :
587 self._containerConfig[name].nextPass ()
589 self._currentAlg = None
590 self._outputContainers = {}
593 def getPreselection (self, containerName, selectionName, *, asList = False) :
595 """get the preselection string for the given selection on the given
598 if selectionName != '' and not self._selectionNameExpr.fullmatch (selectionName) :
599 raise ValueError ('invalid selection name: ' + selectionName)
600 if containerName not in self._containerConfig :
602 config = self._containerConfig[containerName]
604 for selection in config.selections :
605 if (selection.name == '' or selection.name == selectionName) and \
606 selection.preselection :
607 decorations += [selection.decoration]
611 return '&&'.join (decorations)
614 def getFullSelection (self, containerName, selectionName,
615 *, skipBase = False, excludeFrom = None) :
617 """get the selection string for the given selection on the given
620 This can handle both individual selections or selection
621 expressions (e.g. `loose||tight`) with the later being
622 properly expanded. Either way the base selection (i.e. the
623 selection without a name) will always be applied on top.
625 containerName --- the container the selection is defined on
626 selectionName --- the name of the selection, or a selection
627 expression based on multiple named selections
628 skipBase --- will avoid the base selection, and should normally
629 not be used by the end-user.
630 excludeFrom --- a set of string names of selection sources to exclude
631 e.g. to exclude OR selections from MET
633 if "." in containerName:
634 raise ValueError (f'invalid containerName argument: {containerName} , it contains a "." '
635 'which is used to indicate container+selection. You should only pass the container.')
636 if containerName not in self._containerConfig :
639 if excludeFrom is None :
641 elif not isinstance(excludeFrom, set) :
642 raise ValueError ('invalid excludeFrom argument (need set of strings): ' + str(excludeFrom))
644 # Check if this is actually a selection expression,
645 # e.g. `A||B` and if so translate it into a complex expression
646 # for the user. I'm not trying to do any complex syntax
647 # recognition, but instead just produce an expression that the
648 # C++ parser ought to be able to read.
649 if selectionName != '' and \
650 not self._selectionNameExpr.fullmatch (selectionName) :
652 while selectionName != '' :
653 match = self._selectionNameExpr.match (selectionName)
655 result += selectionName[0]
656 selectionName = selectionName[1:]
658 subname = match.group(0)
659 subresult = self.getFullSelection (containerName, subname, skipBase = True, excludeFrom=excludeFrom)
661 result += '(' + subresult + ')'
664 selectionName = selectionName[len(subname):]
665 subresult = self.getFullSelection (containerName, '', excludeFrom=excludeFrom)
667 result = subresult + '&&(' + result + ')'
668 return '(' + result + ')' if result !='' else ''
670 config = self._containerConfig[containerName]
672 hasSelectionName = False
673 for selection in config.selections :
674 if ((selection.name == '' and not skipBase) or selection.name == selectionName) and (selection.comesFrom not in excludeFrom) :
675 decorations += [selection.decoration]
676 if selection.name == selectionName :
677 hasSelectionName = True
678 if not hasSelectionName and selectionName != '' :
679 raise KeyError ('invalid selection name: ' + containerName + '.' + selectionName)
680 return '&&'.join (decorations)
683 def getSelectionCutFlow (self, containerName, selectionName) :
685 """get the individual selections as a list for producing the cutflow for
686 the given selection on the given container
688 This can only handle individual selections, not selection
689 expressions (e.g. `loose||tight`).
692 if containerName not in self._containerConfig :
695 # Check if this is actually a selection expression,
696 # e.g. `A||B` and if so translate it into a complex expression
697 # for the user. I'm not trying to do any complex syntax
698 # recognition, but instead just produce an expression that the
699 # C++ parser ought to be able to read.
700 if selectionName != '' and \
701 not self._selectionNameExpr.fullmatch (selectionName) :
702 raise ValueError ('not allowed to do cutflow on selection expression: ' + selectionName)
704 config = self._containerConfig[containerName]
706 for selection in config.selections :
707 if (selection.name == '' or selection.name == selectionName) :
708 decorations += [selection.decoration]
712 def addEventCutFlow (self, selection, decorations) :
714 """register a new event cutflow, adding it to the dictionary with key 'selection'
715 and value 'decorations', a list of decorated selections
718 if selection in self._eventcutflow.keys():
719 raise ValueError ('the event cutflow dictionary already contains an entry ' + selection)
721 self._eventcutflow[selection] = decorations
770 def addOutputVar (self, containerName, variableName, outputName,
771 *, noSys=False, enabled=True, auxType=None) :
772 """add an output variable for the given container to the output
775 if containerName not in self._containerConfig :
776 raise KeyError ("container unknown: " + containerName)
777 baseConfig = self._containerConfig[containerName].outputs
778 if outputName in baseConfig :
779 raise KeyError ("duplicate output variable name: " + outputName)
780 config = OutputConfig (containerName, variableName, noSys=noSys, enabled=enabled, auxType=auxType)
781 baseConfig[outputName] = config