ATLAS Offline Software
GenericMonitoringTool.py
Go to the documentation of this file.
1 #
2 # Copyright (C) 2002-2025 CERN for the benefit of the ATLAS collaboration
3 #
4 
5 from AthenaConfiguration.ComponentFactory import CompFactory
6 from AthenaCommon.Logging import logging
7 import json
8 
9 log = logging.getLogger(__name__)
10 
11 
12 class GenericMonitoringTool(CompFactory.GenericMonitoringTool):
13  '''GenericMonitoringTool configurable class'''
14 
15  __slots__ = ('_configFlags', '_convention', '_defaultDuration')
16 
17  def __init__(self, flags, name='GenericMonitoringTool', **kwargs):
18  self._configFlags = flags
19  self._convention = ''
20  self._defaultDuration = kwargs.pop('defaultDuration', None)
21  super().__init__(name, **kwargs)
22 
23  @property
24  def convention(self):
25  return self._convention
26 
27  @convention.setter
28  def convention(self, value):
29  self._convention = value
30 
31  @property
32  def defaultDuration(self):
33  return self._defaultDuration
34 
35  @defaultDuration.setter
36  def defaultDuration(self, value):
37  self._defaultDuration = value
38 
39  def _coreDefine(self, deffunc, *args, **kwargs):
40  if 'convention' in kwargs:
41  # only if someone really knows what they're doing
42  pass
43  else:
44  duration = kwargs.pop('duration', self.defaultDuration)
45  if duration is not None:
46  kwargs['convention'] = self.convention + ':' + duration
47  # if an overall path for tool is specified, can leave path argument empty
48  if getattr(self, 'HistPath', '') != '':
49  kwargs.setdefault('path', '')
50  toadd = deffunc(self._configFlags, *args, **kwargs)
51  if toadd:
52  self.Histograms.append(toadd)
53 
54  def defineHistogram(self, *args, **kwargs):
55  self._coreDefine(defineHistogram, *args, **kwargs)
56 
57  def defineTree(self, *args, **kwargs):
58  self._coreDefine(defineTree, *args, **kwargs)
59 
60 
62  '''Array of configurables of GenericMonitoringTool objects'''
63  def __init__(self, flags, name, dimensions, **kwargs):
64  self.Tools = {}
65  self.Postfixes, self.Accessors = GenericMonitoringArray._postfixes(dimensions)
66  for postfix in self.Postfixes:
67  self.Tools[postfix] = GenericMonitoringTool(flags, name+postfix,**kwargs)
68 
69  def __getitem__(self,index):
70  '''Forward operator[] on class to the list of tools'''
71  return self.toolList()[index]
72 
73  def toolList(self):
74  return list(self.Tools.values())
75 
76  def broadcast(self, member, value):
77  '''Allows one to set attributes of every tool simultaneously
78 
79  Arguments:
80  member -- string which contains the name of the attribute to be set
81  value -- value of the attribute to be set
82  '''
83  for tool in self.toolList():
84  setattr(tool,member,value)
85 
86  def defineHistogram(self, varname, title=None, path=None, pattern=None, **kwargs):
87  '''Propogate defineHistogram to each tool, adding a unique tag.
88 
89  Arguments:
90  pattern -- if specified, list of n-tuples of indices for plots to create
91  '''
92  unAliased = varname.split(';')[0]
93  _, aliasBase = _alias(varname)
94  if aliasBase is None or aliasBase.strip() == '':
95  raise ValueError(f'Unable to define histogram using definition "{varname}" since we cannot determine its name')
96  if pattern is not None:
97  try:
98  iter(pattern)
99  except TypeError:
100  raise ValueError('Argument to GenericMonitoringArray.defineHistogram must be iterable')
101  if not isinstance(pattern, list):
102  pattern = list(pattern)
103  if len(pattern) == 0: # nothing to do
104  return
105  if isinstance(pattern[0], (str, int)):
106  # assume we have list of strings or ints; convert to list of 1-element tuples
107  pattern = [(_2,) for _2 in pattern]
108  for postfix, tool in self.Tools.items():
109  try:
110  accessors = tuple(self.Accessors[postfix])
111  if pattern is not None:
112  if accessors not in pattern:
113  continue
114  # two options for alias formatting,
115  # a) default convention: (var_0, var_1, etc.)
116  # b) custom formatting: 'alias{0}custom'.format(*(0, 1))
117  aliasBaseFormatted = aliasBase.format(*accessors)
118  if aliasBaseFormatted==aliasBase:
119  # if format call did not do anything, use default
120  aliased = unAliased+';'+aliasBase+postfix
121  else:
122  # if format call changed the alias, use custom
123  aliased = unAliased+';'+aliasBaseFormatted
124  if title is not None:
125  kwargs['title'] = title.format(*accessors)
126  if path is not None:
127  kwargs['path'] = path.format(*accessors)
128  except IndexError as e:
129  log.error('In title or path template of histogram {0}, too many positional '\
130  'arguments were requested. Title and path templates were "{1}" and "{2}", '\
131  'while only {3} fillers were given: {4}.'.format(aliasBase, title,\
132  path, len(accessors), accessors))
133  raise e
134 
135  tool.defineHistogram(aliased, **kwargs)
136 
137  @staticmethod
138  def _postfixes(dimensions, previous=''):
139  '''Generates a list of subscripts to add to the name of each tool
140 
141  Arguments:
142  dimensions -- List containing the lengths of each side of the array off tools
143  previous -- Strings appended from the other dimensions of the array
144  '''
145  import collections
146  assert isinstance(dimensions,list) and len(dimensions)>0, \
147  'GenericMonitoringArray must have list of dimensions.'
148  try:
149  if dimensions==[1]:
150  return [''], {'': ['']}
151  except AttributeError:
152  #Evidently not [1]
153  pass
154  postList = []
155  accessorDict = collections.OrderedDict()
156  first = dimensions[0]
157  if isinstance(first,list):
158  iterable = first
159  elif isinstance(first,int):
160  iterable = range(first)
161  else:
162  #Assume GaudiConfig2.semantics._ListHelper
163  iterable = list(first)
164  for i in iterable:
165  if len(dimensions)==1:
166  postList.append(previous+'_'+str(i))
167  accessorDict[previous+'_'+str(i)]=[str(i)]
168  else:
169  postfixes, accessors = GenericMonitoringArray._postfixes(dimensions[1:],previous+'_'+str(i))
170  postList.extend(postfixes)
171  for acckey, accval in accessors.items():
172  accessorDict[acckey] = [str(i)] + accval
173  return postList, accessorDict
174 
175 
176 
185 def _invalidName(flags, name):
186  blocklist = '/\\'
187  if flags.Common.isOnline:
188  blocklist += '=,:.()'
189  return set(name).intersection(blocklist)
190 
191 
192 
199 def _alias(varname):
200  variableAliasSplit = varname.split(';')
201  varList = [v.strip() for v in variableAliasSplit[0].split(',')]
202  if len(variableAliasSplit)==1:
203  return varList, '_vs_'.join(reversed(varList))
204  elif len(variableAliasSplit)==2:
205  return varList, variableAliasSplit[1]
206  else:
207  message = 'Invalid variable or alias for {}. Histogram(s) not defined.'
208  log.warning(message.format(varname))
209  return None, None
210 
211 
212 
218 def _validateOptions(user, default):
219  for key, userVal in user.items():
220  # (1) Check that the requested key exists
221  assert key in default,\
222  f'Unknown option {key} provided. Choices are [{", ".join(default)}].'
223  # (2) Check that the provided type is correct
224  userType = type(userVal)
225  defaultVal = default[key]
226  defaultType = type(defaultVal)
227  if isinstance(userVal, bool) or isinstance(defaultVal, bool):
228  assert isinstance(userVal, bool) and isinstance(defaultVal, bool),\
229  f'{key} provided {userType}, expected bool.'
230  else:
231  assert isinstance(defaultVal, userType),\
232  f'{key} provided {userType}, expected {defaultType}'
233 
234 
235 
238 def _options(opt):
239  # Set the default dictionary of options
240  settings = {
241  'Sumw2': False, # store sum of squares of weights
242  'kLBNHistoryDepth': 0, # length of lumiblock history
243  'kAddBinsDynamically': False, # add new bins if fill is outside axis range
244  'kRebinAxes': False, # increase axis range without adding new bins
245  'kCanRebin': False, # allow all axes to be rebinned
246  'kVec': False, # add content to each bin from each element of a vector
247  'kVecUO': False, # same as above, but use 0th(last) element for underflow(overflow)
248  'kCumulative': False, # fill bin of monitored object's value, and every bin below it
249  'kLive': 0, # plot only the last N lumiblocks on y_vs_LB plots
250  'kAlwaysCreate': False # create the histogram, even if it is empty
251  }
252  if opt is None:
253  # If no options are provided, skip any further checks.
254  pass
255  elif isinstance(opt, dict):
256  # If the user provides a partial dictionary, update the default with user's.
257  _validateOptions(opt, settings) # check validity of user's options
258  settings.update(opt) # update the default dictionary
259  elif isinstance(opt, str) and len(opt)>0:
260  # If the user provides a comma- or space-separated string of options.
261  settings = _options (opt.replace(',',' ').split())
262 
263  elif isinstance(opt,str):
264  # empty string case
265  pass
266  elif isinstance(opt, list):
267  # process each item in list
268  unknown = []
269  for o in opt:
270  kv = o.split('=', maxsplit=1)
271  key = kv[0]
272  if len(kv)==2:
273  value = False if kv[1]=='False' else int(kv[1]) # only bool and int supported
274  else:
275  value = True
276  if key in settings:
277  assert(type(settings[key])==type(value)) # ensure same type as in defaults
278  settings[key] = value
279  else:
280  unknown.append(key)
281 
282  assert len(unknown)==0, f'Unknown option(s) provided: {", ".join(unknown)}.'
283  else:
284  raise ValueError("Unknown opt type")
285  return settings
286 
287 
288 
306 def defineHistogram(flags, varname, type='TH1F', path=None,
307  title=None, weight=None,
308  xbins=100, xmin=0, xmax=1, xlabels=None,
309  ybins=None, ymin=None, ymax=None, ylabels=None,
310  zmin=None, zmax=None, zlabels=None,
311  opt=None, convention=None, cutmask=None,
312  treedef=None, merge=None):
313 
314  # All of these fields default to an empty string
315  stringSettingsKeys = ['xvar', 'yvar', 'zvar', 'type', 'path', 'title', 'weight',
316  'cutMask', 'convention', 'alias', 'treeDef', 'merge']
317  # All of these fileds default to 0
318  numberSettingsKeys = ['xbins', 'xmin', 'xmax', 'ybins', 'ymin', 'ymax', 'zbins',
319  'zmin', 'zmax']
320  # All of these fields default to an empty array
321  arraySettingsKeys = ['allvars', 'xlabels', 'xarray', 'ylabels', 'yarray', 'zlabels']
322  # Initialize a dictionary with all possible fields
323  settings = dict((key, '') for key in stringSettingsKeys)
324  settings.update(dict((key, 0.0) for key in numberSettingsKeys))
325  settings.update(dict((key, []) for key in arraySettingsKeys))
326 
327  # Alias
328  varList, alias = _alias(varname)
329  if alias is None or alias.strip() == '':
330  log.warning(f'Unable to define histogram using definition "{varname}" since we cannot determine its name.')
331  return ''
332 
333  invalid = _invalidName(flags, alias)
334  if invalid:
335  log.warning('%s is not a valid histogram name. Illegal characters: %s',
336  alias, ' '.join(invalid))
337  return ''
338 
339  settings['alias'] = alias
340 
341  # Variable names
342  nVars = len(varList)
343  if nVars>0:
344  settings['xvar'] = varList[0]
345  if nVars>1:
346  settings['yvar'] = varList[1]
347  if nVars>2:
348  settings['zvar'] = varList[2]
349  settings['allvars'] = varList
350 
351  # Type
352  if flags.Common.isOnline and type in ['TTree']:
353  log.warning('Object %s of type %s is not supported for online running and '
354  'will not be added.', varname, type)
355  return ''
356 
357  # Allowed histogram dimensions
358  if type.startswith('TH1'):
359  dims = {1}
360  elif type.startswith('TH2'):
361  dims = {2}
362  elif type=='TProfile':
363  dims = {2}
364  elif type=='TProfile2D':
365  dims = {3}
366  elif type=='TEfficiency':
367  dims = {2, 3}
368 
369  assert type=='TTree' or nVars in dims,\
370  f'Number of monitored variables {varList} for "{path}/{alias}" '\
371  f'does not match histogram dimension for {type}'
372 
373  settings['type'] = type
374 
375  # Path
376  if path is None:
377  path = ''
378  settings['path'] = path
379 
380  # Title
381  if title is None:
382  title = varname
383  settings['title'] = title
384 
385  # Weight
386  if weight is not None:
387  settings['weight'] = weight
388 
389  # Cutmask
390  if cutmask is not None:
391  settings['cutMask'] = cutmask
392 
393  # Output path naming convention
394  if convention is not None:
395  settings['convention'] = convention
396 
397  # Bin counts and ranges
398  # Possible types allowed for bin counts
399  binTypes = (int, list, tuple)
400 
401  # X axis count and range
402  assert isinstance(xbins, binTypes),'xbins argument must be int, list, or tuple'
403  if isinstance(xbins, int): # equal x bin widths
404  settings['xbins'], settings['xarray'] = xbins, []
405  else: # x bin edges are set explicitly
406  settings['xbins'], settings['xarray'] = len(xbins)-1, xbins
407  settings['xmin'] = xmin
408  settings['xmax'] = xmax
409 
410  # Y axis count and range
411  if ybins is not None:
412  assert isinstance(ybins, binTypes),'ybins argument must be int, list, or tuple'
413  if isinstance(ybins, int): # equal y bin widths
414  settings['ybins'], settings['yarray'] = ybins, []
415  else: # y bin edges are set explicitly
416  settings['ybins'], settings['yarray'] = len(ybins)-1, ybins
417  if ymin is not None:
418  settings['ymin'] = ymin
419  if ymax is not None:
420  settings['ymax'] = ymax
421 
422  # Z axis count and range
423  if zmin is not None:
424  settings['zmin'] = zmin
425  if zmax is not None:
426  settings['zmax'] = zmax
427 
428  # Then, parse the [xyz]label arguments
429  if xlabels is not None and len(xlabels)>0:
430  assert isinstance(xlabels, (list, tuple)),'xlabels must be list or tuple'
431  settings['xbins'] = len(xlabels)
432  settings['xlabels'] = xlabels
433  if ylabels is not None and len(ylabels)>0:
434  assert isinstance(ylabels, (list, tuple)),'ylabels must be list or tuple'
435  settings['ybins'] = len(ylabels)
436  settings['ylabels'] = ylabels
437  # if user did not specify ymin and ymax, set it here, as cannot have ymin=ymax=0
438  if ymin is None: settings["ymin"] = 0
439  if ymax is None: settings["ymax"] = settings["ymin"]+1
440  if zlabels is not None and len(zlabels)>0:
441  assert isinstance(zlabels, (list, tuple)),'zlabels must be list or tuple'
442  settings['zlabels'] = zlabels
443 
444  # Tree branches
445  if treedef is not None:
446  assert type=='TTree','cannot define tree branches for a non-TTree object'
447  settings['treeDef'] = treedef
448 
449  # Add all other options
450  settings.update(_options(opt))
451 
452  # some things need merging
453  if ((settings['kAddBinsDynamically'] or settings['kRebinAxes'] or settings['kCanRebin'])
454  and (not flags.Common.isOnline and 'OFFLINE' in settings['convention'])):
455  if merge is None:
456  log.warning(f'Merge method for {alias} is not specified but needs to be "merge" due to histogram definition; overriding for your convenience')
457  merge = 'merge'
458 
459  # merge method
460  if merge is not None:
461  assert type not in ['TEfficiency', 'TTree', 'TGraph'],'only default merge defined for non-histogram objects'
462  settings['merge'] = merge
463 
464  # LB histograms always need to be published online (ADHI-4947)
465  if settings['kLBNHistoryDepth']>0 and flags.Common.isOnline:
466  settings['kAlwaysCreate'] = True
467  log.debug('Setting kAlwaysCreate for lumiblock histogram "%s"', varname)
468 
469  # Check that kLBNHistoryDepth and kLive are both non-negative
470  assert settings['kLBNHistoryDepth']>=0, f'Histogram "{alias}" has invalid kLBNHistoryDepth.'
471  assert settings['kLive']>=0, f'Histogram "{alias}" has invalid kLive.'
472  # kLBNHistoryDepth and kLive options are mutually exclusive. User may not specify both.
473  assert settings['kLBNHistoryDepth']==0 or settings['kLive']==0,\
474  f'Cannot use both kLBNHistoryDepth and kLive for histogram {alias}.'
475  # kLive histograms are only available for Online monitoring.
476  assert settings['kLive']==0 or flags.Common.isOnline,\
477  f'Cannot use kLive with offline histogram {alias}.'
478 
479  return json.dumps(settings)
480 
481 
482 
498 def defineTree(flags, varname, treedef, path=None, title=None,
499  opt='', convention=None,
500  cutmask=None):
501  return defineHistogram(flags, varname, type='TTree', path=path, title=title,
502  treedef=treedef, opt=opt, convention=convention,
503  cutmask=cutmask)
createLinkingScheme.iter
iter
Definition: createLinkingScheme.py:62
GenericMonitoringTool.GenericMonitoringTool.defineTree
def defineTree(self, *args, **kwargs)
Definition: GenericMonitoringTool.py:57
GenericMonitoringTool.GenericMonitoringTool
Definition: GenericMonitoringTool.py:12
vtune_athena.format
format
Definition: vtune_athena.py:14
GenericMonitoringTool.GenericMonitoringArray.__getitem__
def __getitem__(self, index)
Definition: GenericMonitoringTool.py:69
GenericMonitoringTool._invalidName
def _invalidName(flags, name)
Check if name is an allowed histogram/branch name.
Definition: GenericMonitoringTool.py:185
GenericMonitoringTool._options
def _options(opt)
Generate dictionary entries for opt strings.
Definition: GenericMonitoringTool.py:238
GenericMonitoringTool.GenericMonitoringArray.Tools
Tools
Definition: GenericMonitoringTool.py:64
dumpHVPathFromNtuple.append
bool append
Definition: dumpHVPathFromNtuple.py:91
intersection
std::vector< std::string > intersection(std::vector< std::string > &v1, std::vector< std::string > &v2)
Definition: compareFlatTrees.cxx:25
python.CaloAddPedShiftConfig.type
type
Definition: CaloAddPedShiftConfig.py:42
python.Bindings.values
values
Definition: Control/AthenaPython/python/Bindings.py:808
GenericMonitoringTool.GenericMonitoringTool._convention
_convention
Definition: GenericMonitoringTool.py:19
GenericMonitoringTool._alias
def _alias(varname)
Generate an alias for a set of variables.
Definition: GenericMonitoringTool.py:199
GenericMonitoringTool.GenericMonitoringArray
Definition: GenericMonitoringTool.py:61
GenericMonitoringTool.GenericMonitoringTool.defineHistogram
def defineHistogram(self, *args, **kwargs)
Definition: GenericMonitoringTool.py:54
GenericMonitoringTool.GenericMonitoringArray.broadcast
def broadcast(self, member, value)
Definition: GenericMonitoringTool.py:76
plotBeamSpotVxVal.range
range
Definition: plotBeamSpotVxVal.py:194
histSizes.list
def list(name, path='/')
Definition: histSizes.py:38
GenericMonitoringTool.GenericMonitoringArray.__init__
def __init__(self, flags, name, dimensions, **kwargs)
Definition: GenericMonitoringTool.py:63
CxxUtils::set
constexpr std::enable_if_t< is_bitmask_v< E >, E & > set(E &lhs, E rhs)
Convenience function to set bits in a class enum bitmask.
Definition: bitmask.h:232
GenericMonitoringTool.GenericMonitoringArray.defineHistogram
def defineHistogram(self, varname, title=None, path=None, pattern=None, **kwargs)
Definition: GenericMonitoringTool.py:86
TCS::join
std::string join(const std::vector< std::string > &v, const char c=',')
Definition: Trigger/TrigT1/L1Topo/L1TopoCommon/Root/StringUtils.cxx:10
TrigJetMonitorAlgorithm.items
items
Definition: TrigJetMonitorAlgorithm.py:71
GenericMonitoringTool.GenericMonitoringArray._postfixes
def _postfixes(dimensions, previous='')
Definition: GenericMonitoringTool.py:138
GenericMonitoringTool.GenericMonitoringTool.__init__
def __init__(self, flags, name='GenericMonitoringTool', **kwargs)
Definition: GenericMonitoringTool.py:17
python.CaloAddPedShiftConfig.int
int
Definition: CaloAddPedShiftConfig.py:45
GenericMonitoringTool.defineTree
def defineTree(flags, varname, treedef, path=None, title=None, opt='', convention=None, cutmask=None)
Generate tree definition string for the GenericMonitoringTool.Histograms property.
Definition: GenericMonitoringTool.py:498
GenericMonitoringTool.defineHistogram
def defineHistogram(flags, varname, type='TH1F', path=None, title=None, weight=None, xbins=100, xmin=0, xmax=1, xlabels=None, ybins=None, ymin=None, ymax=None, ylabels=None, zmin=None, zmax=None, zlabels=None, opt=None, convention=None, cutmask=None, treedef=None, merge=None)
Generate histogram definition string for the GenericMonitoringTool.Histograms property.
Definition: GenericMonitoringTool.py:306
GenericMonitoringTool._validateOptions
def _validateOptions(user, default)
Validate user inputs for "opt" argument of defineHistogram.
Definition: GenericMonitoringTool.py:218
GenericMonitoringTool.GenericMonitoringTool._configFlags
_configFlags
Definition: GenericMonitoringTool.py:18
str
Definition: BTagTrackIpAccessor.cxx:11
GenericMonitoringTool.GenericMonitoringTool.convention
def convention(self)
Definition: GenericMonitoringTool.py:24
GenericMonitoringTool.GenericMonitoringTool._defaultDuration
_defaultDuration
Definition: GenericMonitoringTool.py:20
GenericMonitoringTool.GenericMonitoringTool.defaultDuration
def defaultDuration(self)
Definition: GenericMonitoringTool.py:32
GenericMonitoringTool.GenericMonitoringTool._coreDefine
def _coreDefine(self, deffunc, *args, **kwargs)
Definition: GenericMonitoringTool.py:39
GenericMonitoringTool.GenericMonitoringArray.toolList
def toolList(self)
Definition: GenericMonitoringTool.py:73
GenericMonitoringTool.GenericMonitoringArray.Accessors
Accessors
Definition: GenericMonitoringTool.py:65
Trk::split
@ split
Definition: LayerMaterialProperties.h:38