ATLAS Offline Software
GenericMonitoringTool.py
Go to the documentation of this file.
1 #
2 # Copyright (C) 2002-2024 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  blacklist = '/\\'
187  if flags.Common.isOnline:
188  blacklist += '=,:.()'
189  return set(name).intersection(blacklist)
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  if len(varList)>0:
343  settings['xvar'] = varList[0]
344  if len(varList)>1:
345  settings['yvar'] = varList[1]
346  if len(varList)>2:
347  settings['zvar'] = varList[2]
348  settings['allvars'] = varList
349  nVars = len(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  # Check that the histogram's dimension matches the number of monitored variables
357  # Add TTree to the lists, it can have any number of vars
358  hist2D = ['TH2','TProfile','TEfficiency', 'TTree']
359  hist3D = ['TProfile2D','TEfficiency', 'TTree']
360  if nVars==2:
361  assert any([valid2D in type for valid2D in hist2D]),'Attempting to use two '
362  'monitored variables with a non-2D histogram.'
363  elif nVars==3:
364  assert any([valid3D in type for valid3D in hist3D]),'Attempting to use three '
365  'monitored variables with a non-3D histogram.'
366  settings['type'] = type
367 
368  # Path
369  if path is None:
370  path = ''
371  settings['path'] = path
372 
373  # Title
374  if title is None:
375  title = varname
376  settings['title'] = title
377 
378  # Weight
379  if weight is not None:
380  settings['weight'] = weight
381 
382  # Cutmask
383  if cutmask is not None:
384  settings['cutMask'] = cutmask
385 
386  # Output path naming convention
387  if convention is not None:
388  settings['convention'] = convention
389 
390  # Bin counts and ranges
391  # Possible types allowed for bin counts
392  binTypes = (int, list, tuple)
393 
394  # X axis count and range
395  assert isinstance(xbins, binTypes),'xbins argument must be int, list, or tuple'
396  if isinstance(xbins, int): # equal x bin widths
397  settings['xbins'], settings['xarray'] = xbins, []
398  else: # x bin edges are set explicitly
399  settings['xbins'], settings['xarray'] = len(xbins)-1, xbins
400  settings['xmin'] = xmin
401  settings['xmax'] = xmax
402 
403  # Y axis count and range
404  if ybins is not None:
405  assert isinstance(ybins, binTypes),'ybins argument must be int, list, or tuple'
406  if isinstance(ybins, int): # equal y bin widths
407  settings['ybins'], settings['yarray'] = ybins, []
408  else: # y bin edges are set explicitly
409  settings['ybins'], settings['yarray'] = len(ybins)-1, ybins
410  if ymin is not None:
411  settings['ymin'] = ymin
412  if ymax is not None:
413  settings['ymax'] = ymax
414 
415  # Z axis count and range
416  if zmin is not None:
417  settings['zmin'] = zmin
418  if zmax is not None:
419  settings['zmax'] = zmax
420 
421  # Then, parse the [xyz]label arguments
422  if xlabels is not None and len(xlabels)>0:
423  assert isinstance(xlabels, (list, tuple)),'xlabels must be list or tuple'
424  settings['xbins'] = len(xlabels)
425  settings['xlabels'] = xlabels
426  if ylabels is not None and len(ylabels)>0:
427  assert isinstance(ylabels, (list, tuple)),'ylabels must be list or tuple'
428  settings['ybins'] = len(ylabels)
429  settings['ylabels'] = ylabels
430  # if user did not specify ymin and ymax, set it here, as cannot have ymin=ymax=0
431  if ymin is None: settings["ymin"] = 0
432  if ymax is None: settings["ymax"] = settings["ymin"]+1
433  if zlabels is not None and len(zlabels)>0:
434  assert isinstance(zlabels, (list, tuple)),'zlabels must be list or tuple'
435  settings['zlabels'] = zlabels
436 
437  # Tree branches
438  if treedef is not None:
439  assert type=='TTree','cannot define tree branches for a non-TTree object'
440  settings['treeDef'] = treedef
441 
442  # Add all other options
443  settings.update(_options(opt))
444 
445  # some things need merging
446  if ((settings['kAddBinsDynamically'] or settings['kRebinAxes'] or settings['kCanRebin'])
447  and (not flags.Common.isOnline and 'OFFLINE' in settings['convention'])):
448  if merge is None:
449  log.warning(f'Merge method for {alias} is not specified but needs to be "merge" due to histogram definition; overriding for your convenience')
450  merge = 'merge'
451 
452  # merge method
453  if merge is not None:
454  assert type not in ['TEfficiency', 'TTree', 'TGraph'],'only default merge defined for non-histogram objects'
455  settings['merge'] = merge
456 
457  # LB histograms always need to be published online (ADHI-4947)
458  if settings['kLBNHistoryDepth']>0 and flags.Common.isOnline:
459  settings['kAlwaysCreate'] = True
460  log.debug('Setting kAlwaysCreate for lumiblock histogram "%s"', varname)
461 
462  # Check that kLBNHistoryDepth and kLive are both non-negative
463  assert settings['kLBNHistoryDepth']>=0, f'Histogram "{alias}" has invalid kLBNHistoryDepth.'
464  assert settings['kLive']>=0, f'Histogram "{alias}" has invalid kLive.'
465  # kLBNHistoryDepth and kLive options are mutually exclusive. User may not specify both.
466  assert settings['kLBNHistoryDepth']==0 or settings['kLive']==0,\
467  f'Cannot use both kLBNHistoryDepth and kLive for histogram {alias}.'
468  # kLive histograms are only available for Online monitoring.
469  assert settings['kLive']==0 or flags.Common.isOnline,\
470  f'Cannot use kLive with offline histogram {alias}.'
471 
472  return json.dumps(settings)
473 
474 
475 
491 def defineTree(flags, varname, treedef, path=None, title=None,
492  opt='', convention=None,
493  cutmask=None):
494  return defineHistogram(flags, varname, type='TTree', path=path, title=title,
495  treedef=treedef, opt=opt, convention=convention,
496  cutmask=cutmask)
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
CaloCellPos2Ntuple.int
int
Definition: CaloCellPos2Ntuple.py:24
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.Bindings.values
values
Definition: Control/AthenaPython/python/Bindings.py:805
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:195
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:79
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
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:491
python.CaloScaleNoiseConfig.type
type
Definition: CaloScaleNoiseConfig.py:78
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