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'
254 if DualUseConfig.isAthena:
255 from AthenaConfiguration.ComponentAccumulator
import ComponentAccumulator
256 self.
CA = ComponentAccumulator()
257 if algSeq
is not None:
258 self.
CA.addSequence(algSeq)
261 raise ValueError (
"need to pass algSeq if not using ComponentAccumulator")
324 def setAlgPostfix (self, postfix : str) :
325 """set the current postfix to be appended to algorithm names
327 Blocks should not call this directly, but rather implement the
328 instanceName method, which will be used to generate the postfix
330 # make sure the postfix matches the expected format ([_a-zA-Z0-9]*)
331 if re.compile ('^[_a-zA-Z0-9]*$').match (postfix) is None :
332 raise ValueError ('invalid algorithm postfix: ' + postfix)
334 self._algPostfix = ''
335 elif postfix[0] != '_' :
336 self._algPostfix = '_' + postfix
338 self._algPostfix = postfix
340 def getAlgorithm (self, name : str):
341 """get the algorithm with the given name
343 Despite the name this will also return services and tools. It is
344 mostly meant for internal use, particularly for the property
346 name = name + self._algPostfix
347 if name not in self._algorithms:
349 return self._algorithms[name]
351 def createAlgorithm (self, type, name, reentrant=False) :
352 """create a new algorithm and register it as the current algorithm"""
353 name = name + self._algPostfix
355 if name in self._algorithms :
356 raise Exception ('duplicate algorithms: ' + name + ' with algPostfix=' + self._algPostfix)
358 alg = DualUseConfig.createReentrantAlgorithm (type, name)
360 alg = DualUseConfig.createAlgorithm (type, name)
362 if DualUseConfig.isAthena:
363 if self._algSeq is not None:
364 self.CA.addEventAlgo(alg,self._algSeq.name)
366 self.CA.addEventAlgo(alg)
370 self._algorithms[name] = alg
371 self._currentAlg = alg
374 if name not in self._algorithms :
375 raise Exception ('unknown algorithm requested: ' + name)
376 self._currentAlg = self._algorithms[name]
377 if self.CA and self._currentAlg != self.CA.getEventAlgo(name) :
378 raise Exception ('change to algorithm object: ' + name)
379 return self._algorithms[name]
382 def createService (self, type, name) :
383 '''create a new service and register it as the "current algorithm"'''
385 if name in self._algorithms :
386 raise Exception ('duplicate service: ' + name)
387 service = DualUseConfig.createService (type, name)
388 # Avoid importing AthenaCommon.AppMgr in a CA Athena job
389 # as it modifies Gaudi behaviour
390 if DualUseConfig.isAthena:
391 self.CA.addService(service)
393 # We're not, so let's remember this as a "normal" algorithm:
394 self._algSeq += service
395 self._algorithms[name] = service
396 self._currentAlg = service
399 if name not in self._algorithms :
400 raise Exception ('unknown service requested: ' + name)
401 self._currentAlg = self._algorithms[name]
402 return self._algorithms[name]
405 def createPublicTool (self, type, name) :
406 '''create a new public tool and register it as the "current algorithm"'''
408 if name in self._algorithms :
409 raise Exception ('duplicate public tool: ' + name)
410 tool = DualUseConfig.createPublicTool (type, name)
411 # Avoid importing AthenaCommon.AppMgr in a CA Athena job
412 # as it modifies Gaudi behaviour
413 if DualUseConfig.isAthena:
414 self.CA.addPublicTool(tool)
416 # We're not, so let's remember this as a "normal" algorithm:
418 self._algorithms[name] = tool
419 self._currentAlg = tool
422 if name not in self._algorithms :
423 raise Exception ('unknown public tool requested: ' + name)
424 self._currentAlg = self._algorithms[name]
425 return self._algorithms[name]
434 def setSourceName (self, containerName, sourceName,
435 *, originalName = None, isMet = False) :
436 """set the (default) name of the source/original container
438 This is essentially meant to allow using e.g. the muon
439 configuration and the user not having to manually specify that
440 they want to use the Muons/AnalysisMuons container from the
443 In addition it allows to set the original name of the
444 container (which may be different from the source name), which
445 is mostly/exclusively used for jet containers, so that
446 subsequent configurations know which jet container they
449 if containerName not in self._containerConfig :
450 self._containerConfig[containerName] = ContainerConfig (containerName, sourceName, noSysSuffix = self._noSysSuffix, originalName = originalName, isMet = isMet)
453 def writeName (self, containerName, *, isMet=None) :
454 """register that the given container will be made and return
456 if containerName not in self._containerConfig :
457 self._containerConfig[containerName] = ContainerConfig (containerName, sourceName = None, noSysSuffix = self._noSysSuffix)
458 if self._containerConfig[containerName].sourceName is not None :
459 raise Exception ("trying to write container configured for input: " + containerName)
460 if self._containerConfig[containerName].index != 0 :
461 raise Exception ("trying to write container twice: " + containerName)
462 self._containerConfig[containerName].index += 1
463 if isMet is not None :
464 self._containerConfig[containerName].isMet = isMet
465 return self._containerConfig[containerName].currentName()
468 def readName (self, containerName) :
469 """get the name of the "current copy" of the given container
471 As extra copies get created during processing this will track
472 the correct name of the current copy. Optionally one can pass
473 in the name of the container before the first copy.
475 if containerName not in self._containerConfig :
476 raise Exception ("no source container for: " + containerName)
477 return self._containerConfig[containerName].currentName()
489 def wantCopy (self, containerName) :
490 """ask whether we want/need a copy of the container
492 This usually only happens if no copy of the container has been
493 made yet and the copy is needed to allow modifications, etc.
495 if containerName not in self._containerConfig :
496 raise Exception ("no source container for: " + containerName)
497 return self._containerConfig[containerName].index == 0
500 def originalName (self, containerName) :
501 """get the "original" name of the given container
503 This is mostly/exclusively used for jet containers, so that
504 subsequent configurations know which jet container they
507 if containerName not in self._containerConfig :
508 raise Exception ("container unknown: " + containerName)
509 result = self._containerConfig[containerName].originalName
511 raise Exception ("no original name for: " + containerName)
514 def getContainerMeta (self, containerName, metaField, defaultValue=None, *, failOnMiss=False) :
515 """get the meta information for the given container
517 This is used to pass down meta-information from the
518 configuration to the algorithms.
520 if containerName not in self._containerConfig :
521 raise Exception ("container unknown: " + containerName)
522 if metaField in self._containerConfig[containerName].meta :
523 return self._containerConfig[containerName].meta[metaField]
525 raise Exception ('unknown meta-field' + metaField + ' on container ' + containerName)
528 def setContainerMeta (self, containerName, metaField, value, *, allowOverwrite=False) :
529 """set the meta information for the given container
531 This is used to pass down meta-information from the
532 configuration to the algorithms.
534 if containerName not in self._containerConfig :
535 raise Exception ("container unknown: " + containerName)
536 if not allowOverwrite and metaField in self._containerConfig[containerName].meta :
537 raise Exception ('duplicate meta-field' + metaField + ' on container ' + containerName)
538 self._containerConfig[containerName].meta[metaField] = value
540 def isMetContainer (self, containerName) :
541 """whether the given container is registered as a MET container
543 This is mostly/exclusively used for determining whether to
544 write out the whole container or just a single MET term.
546 if containerName not in self._containerConfig :
547 raise Exception ("container unknown: " + containerName)
548 return self._containerConfig[containerName].isMet
551 def readNameAndSelection (self, containerName, *, excludeFrom = None) :
552 """get the name of the "current copy" of the given container, and the
555 This is mostly meant for MET and OR for whom the actual object
556 selection is relevant, and which as such allow to pass in the
557 working point as "ObjectName.WorkingPoint".
559 split = containerName.split (".")
561 objectName = split[0]
563 elif len(split) == 2 :
564 objectName = split[0]
565 selectionName = split[1]
567 raise Exception ('invalid object selection name: ' + containerName)
568 return self.readName (objectName), self.getFullSelection (objectName, selectionName, excludeFrom=excludeFrom)
571 def nextPass (self) :
572 """switch to the next configuration pass
574 Configuration happens in two steps, with all the blocks processed
575 twice. This switches from the first to the second pass.
578 raise Exception ("already performed final pass")
579 for name in self._containerConfig :
580 self._containerConfig[name].nextPass ()
582 self._currentAlg = None
583 self._outputContainers = {}
586 def getPreselection (self, containerName, selectionName, *, asList = False) :
588 """get the preselection string for the given selection on the given
591 if selectionName != '' and not self._selectionNameExpr.fullmatch (selectionName) :
592 raise ValueError ('invalid selection name: ' + selectionName)
593 if containerName not in self._containerConfig :
595 config = self._containerConfig[containerName]
597 for selection in config.selections :
598 if (selection.name == '' or selection.name == selectionName) and \
599 selection.preselection :
600 decorations += [selection.decoration]
604 return '&&'.join (decorations)
607 def getFullSelection (self, containerName, selectionName,
608 *, skipBase = False, excludeFrom = None) :
610 """get the selection string for the given selection on the given
613 This can handle both individual selections or selection
614 expressions (e.g. `loose||tight`) with the later being
615 properly expanded. Either way the base selection (i.e. the
616 selection without a name) will always be applied on top.
618 containerName --- the container the selection is defined on
619 selectionName --- the name of the selection, or a selection
620 expression based on multiple named selections
621 skipBase --- will avoid the base selection, and should normally
622 not be used by the end-user.
623 excludeFrom --- a set of string names of selection sources to exclude
624 e.g. to exclude OR selections from MET
626 if "." in containerName:
627 raise ValueError (f'invalid containerName argument: {containerName} , it contains a "." '
628 'which is used to indicate container+selection. You should only pass the container.')
629 if containerName not in self._containerConfig :
632 if excludeFrom is None :
634 elif not isinstance(excludeFrom, set) :
635 raise ValueError ('invalid excludeFrom argument (need set of strings): ' + str(excludeFrom))
637 # Check if this is actually a selection expression,
638 # e.g. `A||B` and if so translate it into a complex expression
639 # for the user. I'm not trying to do any complex syntax
640 # recognition, but instead just produce an expression that the
641 # C++ parser ought to be able to read.
642 if selectionName != '' and \
643 not self._selectionNameExpr.fullmatch (selectionName) :
645 while selectionName != '' :
646 match = self._selectionNameExpr.match (selectionName)
648 result += selectionName[0]
649 selectionName = selectionName[1:]
651 subname = match.group(0)
652 subresult = self.getFullSelection (containerName, subname, skipBase = True, excludeFrom=excludeFrom)
654 result += '(' + subresult + ')'
657 selectionName = selectionName[len(subname):]
658 subresult = self.getFullSelection (containerName, '', excludeFrom=excludeFrom)
660 result = subresult + '&&(' + result + ')'
661 return '(' + result + ')' if result !='' else ''
663 config = self._containerConfig[containerName]
665 hasSelectionName = False
666 for selection in config.selections :
667 if ((selection.name == '' and not skipBase) or selection.name == selectionName) and (selection.comesFrom not in excludeFrom) :
668 decorations += [selection.decoration]
669 if selection.name == selectionName :
670 hasSelectionName = True
671 if not hasSelectionName and selectionName != '' :
672 raise KeyError ('invalid selection name: ' + containerName + '.' + selectionName)
673 return '&&'.join (decorations)
676 def getSelectionCutFlow (self, containerName, selectionName) :
678 """get the individual selections as a list for producing the cutflow for
679 the given selection on the given container
681 This can only handle individual selections, not selection
682 expressions (e.g. `loose||tight`).
685 if containerName not in self._containerConfig :
688 # Check if this is actually a selection expression,
689 # e.g. `A||B` and if so translate it into a complex expression
690 # for the user. I'm not trying to do any complex syntax
691 # recognition, but instead just produce an expression that the
692 # C++ parser ought to be able to read.
693 if selectionName != '' and \
694 not self._selectionNameExpr.fullmatch (selectionName) :
695 raise ValueError ('not allowed to do cutflow on selection expression: ' + selectionName)
697 config = self._containerConfig[containerName]
699 for selection in config.selections :
700 if (selection.name == '' or selection.name == selectionName) :
701 decorations += [selection.decoration]
705 def addEventCutFlow (self, selection, decorations) :
707 """register a new event cutflow, adding it to the dictionary with key 'selection'
708 and value 'decorations', a list of decorated selections
711 if selection in self._eventcutflow.keys():
712 raise ValueError ('the event cutflow dictionary already contains an entry ' + selection)
714 self._eventcutflow[selection] = decorations
763 def addOutputVar (self, containerName, variableName, outputName,
764 *, noSys=False, enabled=True, auxType=None) :
765 """add an output variable for the given container to the output
768 if containerName not in self._containerConfig :
769 raise KeyError ("container unknown: " + containerName)
770 baseConfig = self._containerConfig[containerName].outputs
771 if outputName in baseConfig :
772 raise KeyError ("duplicate output variable name: " + outputName)
773 config = OutputConfig (containerName, variableName, noSys=noSys, enabled=enabled, auxType=auxType)
774 baseConfig[outputName] = config