Skip to content

wsfreund/RingerCore

Repository files navigation

Ringer framework: RingerCore package

This package contains a set of base functionalities for the Ringer framework and its packages. The contents are considered implementation details and this documentation is provided for developers that use this package.

Table of Contents

Installation

This package cannot be installed by itself. Consider installing one of the RootCore projects using this package:

  • RingerTuning [recommended]: this project contains only the packages needed for tuning the discriminators;
  • RingerProject: Use this git repository, however, if you want to install all packages related to the Ringer algorithm.

Compile time flags

Currently, it is available the following flag:

  • --with-ringercore-dbg-level: When specified, it will be compiled on debug mode.

Package Organization

The package is organized as a standard RootCore package, namely:

%%bash
echo "Module '$(pwd)' folders are:"
find -L . -type d -maxdepth 1 -not -name ".*"
Module '/afs/cern.ch/user/w/wsfreund/Ringer/xAODRingerOfflinePorting/RingerTPFrameWork/RingerCore' folders are:
./RingerCore
./cmt
./python
./Root

Most of the provided functionalities are written in python. However, a simple adaptation of the Athena framework logging system is also available to be used in C++. Next, we enter in details about what is provided by the package.

Python provided functionalities

The package contains the following python modules:

%%bash
find -L ./python -maxdepth 2 -mindepth 1 -not -name "*.pyc" -a -not -name "__init__.py"
./python/LimitedTypeList.py
./python/argparse.py
./python/Logger.py
./python/util.py
./python/Parser.py
./python/OldLogger.py
./python/npConstants.py
./python/FileIO.py
./python/LoopingBounds.py

The argparse is the python standard argparse module, but it is available to be used on python 2.6 releases. The OldLogger provides compatibility with legacy files. More details about the other modules are given below.

LimitedTypeList

The LimitedTypeList is a class factory that create containers very similar to lists, however being limited to accept objects that inherit of certain types. When it is attempted to add an object of other type, an exception of type NotAllowedType is thrown. Consider the example:

from RingerCore.LimitedTypeList import LimitedTypeList
IntList = LimitedTypeList(
    "IntList", (),
    {'_acceptedTypes' : (int,)})
intList = IntList([1,2,3,4])
print intList
intList += [5,6]
print intList
intList.append(7)
print intList

intList.append(8.)
[1, 2, 3, 4]
[1, 2, 3, 4, 5, 6]
[1, 2, 3, 4, 5, 6, 7]



---------------------------------------------------------------------------

NotAllowedType                            Traceback (most recent call last)

<ipython-input-3-ad80b0d5b22e> in <module>()
     10 print intList
     11 
---> 12 intList.append(8.)


/afs/cern.ch/user/w/wsfreund/Ringer/xAODRingerOfflinePorting/RingerTPFrameWork/RootCoreBin/python/RingerCore/LimitedTypeList.pyc in _LimitedTypeList__append(self, var)
     57   # This is default overload for list append, checking if item is accepted
     58   if not isinstance(var, self._acceptedTypes):
---> 59     raise NotAllowedType( self, var, self._acceptedTypes)
     60   list.append(self,var)
     61 


NotAllowedType: Attempted to add to IntList an object (type=<type 'float'>) which is not an instance from the allowedTypes: (<type 'int'>,)!

In the previous, we used the init method for building the IntList. We repeat previous example, but now using a more readable code:

from RingerCore.LimitedTypeList import LimitedTypeList

class FloatList:
    __metaclass__ = LimitedTypeList
    _acceptedTypes = (float,)
    
floatList = FloatList([1.,2.,3.,4.])
print floatList
floatList.append(5)
print floatList
[1.0, 2.0, 3.0, 4.0]



---------------------------------------------------------------------------

NotAllowedType                            Traceback (most recent call last)

<ipython-input-4-48ceeba11e54> in <module>()
      7 floatList = FloatList([1.,2.,3.,4.])
      8 print floatList
----> 9 floatList.append(5)
     10 print floatList


/afs/cern.ch/user/w/wsfreund/Ringer/xAODRingerOfflinePorting/RingerTPFrameWork/RootCoreBin/python/RingerCore/LimitedTypeList.pyc in _LimitedTypeList__append(self, var)
     57   # This is default overload for list append, checking if item is accepted
     58   if not isinstance(var, self._acceptedTypes):
---> 59     raise NotAllowedType( self, var, self._acceptedTypes)
     60   list.append(self,var)
     61 


NotAllowedType: Attempted to add to FloatList an object (type=<type 'int'>) which is not an instance from the allowedTypes: (<type 'float'>,)!

More complex classes can be created, with their own methods. Examples can be found in the TuningTools.PreProc module.

Logger

This module provides logging capabilities that emulates Athena messaging system. The same levels provided by the Athena logging system are available (in decreasing verbosity order: VERBOSE, DEBUG, INFO, WARNING, FATAL).

The logging level is determined via the LoggingLevel "enumeration" class. It is an EnumStringification class, so the values can be easily parsed when obtained from the command line.

Meanwhile the Logger class adds the self._logger property and the level property for its inherited classes. A logger object can also be retrieved via the class method Logger.getModuleLogger, which makes possible the usage of Logger objects outside class scopes. The following example illustrates both usage cases.

The logging system is also compatible with IPython notebook (jupyter), as shown in the next example. Please note, however, that the jupyter messaging system causes miss-synchronization with the print command and the messages captured from the logging module.

from RingerCore.Logger import LoggingLevel, Logger

mainLogger = Logger.getModuleLogger("mainLogger", LoggingLevel.INFO)

mainLogger.info("Starting example printing an INFO message.")
mainLogger.debug("A message ignored by the messaging system.")
mainLogger.warning("Previous message was ignored because logger level is set to INFO.")

mainLogger.level = LoggingLevel.DEBUG

mainLogger.debug("A message not ignored anymore by the messaging system.")
mainLogger.level = LoggingLevel.VERBOSE
mainLogger.verbose("Verbose messages can also be used.")
mainLogger.fatal("As well as fatal messages.")

class MyClass( Logger ):
    def __init__(self, **kw):
        Logger.__init__(self, kw)
    def fcn(self, input_):
        self._verbose("Started executing MyClass.fcn(%d)", input_)
        self._info("Input value was: %d", input_)
        self._verbose("Successfully finished executing MyClass.fcn(%d)", input_)
        
myInst = MyClass()
myInst.fcn(2)
mainLogger.info('Current output level is: %s', myInst.level)
print 'Current output level is: ', myInst.level
myInst.level = LoggingLevel.VERBOSE
mainLogger.info('Changed mainLogger output level to: %s', myInst.level)
print 'Changed mainLogger output level to: ', myInst.level
myInst.fcn(2)
Py.mainLogger                           INFO Starting example printing an INFO message.
Py.mainLogger                        WARNING Previous message was ignored because logger level is set to INFO.
Py.mainLogger                          DEBUG A message not ignored anymore by the messaging system.
Py.mainLogger                        VERBOSE Verbose messages can also be used.
Py.mainLogger                          FATAL As well as fatal messages.
Py.MyClass                              INFO Input value was: 2
Py.mainLogger                           INFO Current output level is: INFO
Py.mainLogger                           INFO Changed mainLogger output level to: VERBOSE
Py.MyClass                           VERBOSE Started executing MyClass.fcn(2)
Py.MyClass                              INFO Input value was: 2
Py.MyClass                           VERBOSE Successfully finished executing MyClass.fcn(2)
Current output level is:  INFO
Changed mainLogger output level to:  VERBOSE

LoopingBounds

The LoopingBounds module provides the following classes:

from RingerCore import LoopingBounds
import inspect
print [name for name, obj in inspect.getmembers(LoopingBounds, inspect.isclass) if obj.__module__ == "RingerCore.LoopingBounds"]
['LoopingBounds', 'MatlabLoopingBounds', 'PythonLoopingBounds', 'SeqLoopingBounds']

The LoopingBounds class is the base class for the other three classes. The MatlabLoopingBounds and SeqLoopingBounds are the same object and represents the list of indexes delimiting a looping sequence in the matlab (e.g. the 0:2:8 matlab notation, exchanging the : by , in the its constructor) and the last one emulates the unix seq command, which results in the same sequence from the matlab.

The PythonLoopingBounds, however, results in different looping indexes, when considering the same bounds used in the Matlab or seq commands, as it emulates the python range function.

Use the module functions transformToMatlabBounds or transformToPythonBounds to transform the looping bounds objects to the respective types without changing the looping sequence.

The main usage of these module is to let user inform the looping index sequence however it prefers, homogenizing code. It can also be used to reduce file sizes when saving and object, as the raw object can be saved with only the arguments provided to the LoopingBounds classes.

Next follows an example showing these behaviors.

from RingerCore.LoopingBounds import *

print 'A matlab list as specified 1:3'
matlab1 = MatlabLoopingBounds(3)
print type(matlab1)
print matlab1.list()

print 'Python range(3)'
python1 = PythonLoopingBounds(3)
print type(python1)
print python1.list()

print 'Transforming python range(3) to MatlabLoopingBounds instance'
matlab_from_python1 = transformToMatlabBounds(python1)
print type(matlab_from_python1)
print matlab_from_python1.list()
A matlab list as specified 1:3
<class 'RingerCore.LoopingBounds.MatlabLoopingBounds'>
[1, 2, 3]
Python range(3)
<class 'RingerCore.LoopingBounds.PythonLoopingBounds'>
[0, 1, 2]
Transforming python range(3) to MatlabLoopingBounds instance
<class 'RingerCore.LoopingBounds.MatlabLoopingBounds'>
[0, 1, 2]
# Work with more complex objects:
print 'Matlab list -4:-2:-8'
matlab2 = MatlabLoopingBounds(-4,-2,-8)
print type(matlab2)
print matlab2.list()

python_from_matlab2 = transformToPythonBounds(matlab2)
print 'Transformed into PythonLoopingBounds instance'
print type(python_from_matlab2)
print python_from_matlab2.list()

python2 = PythonLoopingBounds(-4,-2,-8)
print 'Python range(-4,-2,-8)'
print type(python2)
print python2.list()

matlab_from_python2 = transformToMatlabBounds(python2)
print 'Transformed into MatlabLoopingBounds instance'
print type(matlab_from_python2)
print matlab_from_python2.list()
Matlab list -4:-2:-8
<class 'RingerCore.LoopingBounds.MatlabLoopingBounds'>
[-4, -6, -8]
Transformed into PythonLoopingBounds instance
<class 'RingerCore.LoopingBounds.PythonLoopingBounds'>
[-4, -6, -8]
Python range(-4,-2,-8)
<class 'RingerCore.LoopingBounds.PythonLoopingBounds'>
[-4, -6]
Transformed into MatlabLoopingBounds instance
<class 'RingerCore.LoopingBounds.MatlabLoopingBounds'>
[-4, -6]
print 'It is also possible to retrieve the original entered arguments:'
print python2.getOriginalVec()
print 'Python range(-4,-2,-8) is string represented as: ', str(python2)

print 'The string can also be formated to include some identification flag for the bounds, e.g.:'
print "PythonLoopingBounds(5,10).formattedString('s'):", PythonLoopingBounds(5,10).formattedString('s')
It is also possible to retrieve the original entered arguments:
[-4, -2, -8]
Python range(-4,-2,-8) is string represented as:  l-006.u-004
The string can also be formated to include some identification flag for the bounds, e.g.:
PythonLoopingBounds(5,10).formattedString('s'): sl0005.su0009

Parser

Provides default parsers to be used in order to create python executables. Currently, it provides the following parsers:

  • gridParser: Provides most argument options as the panda executable command prun, but without specifying an input or an output;
  • inGridParser: Extends gridParser to allow user to also specify input dataset;
  • outGridParser: Extends gridParser to allow user to also specify output dataset;
  • ioGridParser: Extends gridParser to allow user to also specify both input and output datasets;
  • loggerParser: Provides logging options to be provided to the LoggingLevel class and to be distributed over the Logger instances.

Consider, for instance, that it is wanted to create a python module that can be executed by the user and that it can capture the gridParser and loggerParser arguments. This can be done as follows:

try:
  import argparse
except ImportError:
  from RingerCore import argparse

from RingerCore.Parser import loggerParser, gridParser
parser = argparse.ArgumentParser(add_help = False,
                                 description = 'A parser that provides gridParser and loggerParser arguments.',
                                 parents = [gridParser, loggerParser],
                                 conflict_handler = 'resolve')

parser.print_help()
usage: __main__.py [--site [GRID_SITE]] [--excludedSite [GRID_EXCLUDEDSITE]]
                   [--debug] [--nJobs [GRID_NJOBS]]
                   [--excludeFile [GRID_EXCLUDEFILE]] [--disableAutoRetry]
                   [--extFile [GRID_EXTFILE]]
                   [--maxNFilesPerJob [GRID_MAXNFILESPERJOB]]
                   [--cloud [GRID_CLOUD]] [--nGBPerJob [GRID_NGBPERJOB]]
                   [--skipScout] [--memory GRID_MEMORY] [--long]
                   [--useNewCode] [--dry-run] [--allowTaskDuplication]
                   [-itar [InTarBall] | -otar [OutTarBall]]
                   [--output-level {DEBUG,ERROR,FATAL,INFO,VERBOSE,WARNING}]

A parser that provides gridParser and loggerParser arguments.

optional arguments:
  --site [GRID_SITE]    The site location where the job should run.
  --excludedSite [GRID_EXCLUDEDSITE]
                        The excluded site location.
  --debug               Submit GRID job on debug mode.
  --nJobs [GRID_NJOBS]  Number of jobs to submit.
  --excludeFile [GRID_EXCLUDEFILE]
                        Files to exclude from environment copied to grid.
  --disableAutoRetry    Flag to disable auto retrying jobs.
  --extFile [GRID_EXTFILE]
                        External file to add.
  --maxNFilesPerJob [GRID_MAXNFILESPERJOB]
                        Maximum number of files per job.
  --cloud [GRID_CLOUD]  The cloud where to submit the job.
  --nGBPerJob [GRID_NGBPERJOB]
                        Maximum number of GB per job.
  --skipScout           Flag to disable auto retrying jobs.
  --memory GRID_MEMORY  Needed memory to run in MB.
  --long                Submit for long queue.
  --useNewCode          Flag to disable auto retrying jobs.
  --dry-run             Only print grid resulting command, but do not execute
                        it. Used for debugging submission.
  --allowTaskDuplication
                        Flag to disable auto retrying jobs.
  -itar [InTarBall], --inTarBall [InTarBall]
                        The environemnt tarball for posterior usage.
  -otar [OutTarBall], --outTarBall [OutTarBall]
                        The environemnt tarball for posterior usage.
  --output-level {DEBUG,ERROR,FATAL,INFO,VERBOSE,WARNING}
                        The output level for the main logger

With this combination, many different parsers can be easily created by mixing the provided parsers with parsers providing specific job arguments. Available usage examples can be seen on createData.py, createTuningJobFiles.py etc. In some cases, it might be wanted to exclude or modify some of the options available on the default parsers, an example on how to do this can be seen on runGRIDtuning.py.

This module also provides some namespaces that should be used when parsing arguments using the loggerParser and the grid parsers. In the first case, make sure to pass the LoggerNamespace to the parser parse_args method. When using the grid parsers, the GridNamespace should be used. It provides methods for setting the bexec, exec job arguments. The GridNamespace also inherits from the LoggerNamespace, so it can also handle the logger parser arguments.

from RingerCore.Logger import LoggingLevel, Logger
logger = Logger.getModuleLogger("logger", LoggingLevel.INFO)

from RingerCore.Parser import ioGridParser, GridNamespace

args = ioGridParser.parse_args(['--dry-run','--inDS','user.wsfreund.some.inds',
                                '--outDS','user.wsfreund.some.outds','--skipScout',
                                '--outputs','*.root' ], 
                               namespace=GridNamespace('prun'))
args.setExec("""source ./setrootcore.sh;
                {someJob} input_to_the_job
             """.format( someJob = "\$ROOTCOREBIN/user_scripts/SomePackage/some_script.py"))

args.run_cmd()
Py.GridNamespace                        INFO Command:
prun \
     --exec \
       "source ./setrootcore.sh; \
         \$ROOTCOREBIN/user_scripts/SomePackage/some_script.py input_to_the_job;" \
     --skipScout \
     --excludeFile="*.o,*.so,*.a,*.gch" \
     --excludedSite=ANALY_CERN_CLOUD,ANALY_SLAC,ANALY_CERN_SHORT,ANALY_CONNECT_SHORT,ANALY_BNL_SHORT,ANALY_BNL_EC2E1,ANALY_SWT2_CPB \
     --inDS=user.wsfreund.some.inds \
     --outDS=user.wsfreund.some.outds \
     --outputs=*.root \
     --site=AUTO \
     --skipScout \

Consider taking a look at runGRIDtuning.py to observe a more detailed working code using GridNamespace.

npConstants

This class allows to harmonize numpy default flags.

from RingerCore.npConstants import npConstants
import inspect
print(inspect.getdoc(npConstants))
This class is used by dependent packages to harmonize numpy flags. Currently
it can be used obtain armonization in the following information:
  - fortran/c representation;
    o dtype: retrieves floating point string used on numpy
  - dimensions:
    o access: access numpy indexes
    o shape: return shape using npat and nobs
    o odim: retrieves the observations axis index
    o pdim: retrieves the patterns axis index
  - floating point data type:
    o fp_dtype: retrieves floating point dtype used on numpy
  - integer data types:
    o int_dtype: common integer dtype
    o scounter_dtype: short integer dtype to use as counter
    o flag_dtype: the integer to use on flags (usually only -1,0,1 are
    flagged)

Several different numpy configurations can be set and changed accordingly, depending on how data should be represented. E.g.:

from RingerCore.npConstants import npConstants
import numpy as np
npDefault1 = npConstants( useFortran = True,
                          fp_dtype   = np.float64,
                          int_dtype  = np.int64 )
npDefault1
npConstants(fp_dtype=dtype('float64'),int_dtype=dtype('int64'),scounter_dtype=dtype('uint8'),flag_dtype=dtype('int8'),order=F)
from RingerCore.npConstants import npConstants
import numpy as npb
npDefault2 = npConstants( useFortran = False,
                          fp_dtype   = np.float32,
                          int_dtype  = np.int32 )
npDefault2
npConstants(fp_dtype=dtype('float32'),int_dtype=dtype('int32'),scounter_dtype=dtype('uint8'),flag_dtype=dtype('int8'),order=C)
npCurrent = npDefault1

print "Using npDefault1"
zeros = npCurrent.int_zeros(npCurrent.shape(npat=2,nobs=4))
print zeros
print zeros.flags
print zeros.dtype
print "Accessing pattern index 1: ", zeros[npCurrent.access(pidx=1)]
print "Accessing observation index 2: ", zeros[npCurrent.access(oidx=2)]
print "====="
Using npDefault1
[[0 0 0 0]
 [0 0 0 0]]
  C_CONTIGUOUS : False
  F_CONTIGUOUS : True
  OWNDATA : True
  WRITEABLE : True
  ALIGNED : True
  UPDATEIFCOPY : False
int64
Accessing pattern index 1:  [0 0 0 0]
Accessing observation index 2:  [0 0]
=====
npCurrent = npDefault2

print "Using npDefault2"
zeros = npCurrent.int_zeros(npCurrent.shape(npat=2,nobs=4))
print zeros
print zeros.flags
print zeros.dtype
print "Accessing pattern index 1: ", zeros[npCurrent.access(pidx=1)]
print "Accessing observation index 2: ", zeros[npCurrent.access(oidx=2)]
print "====="
Using npDefault2
[[0 0]
 [0 0]
 [0 0]
 [0 0]]
  C_CONTIGUOUS : True
  F_CONTIGUOUS : False
  OWNDATA : True
  WRITEABLE : True
  ALIGNED : True
  UPDATEIFCOPY : False
int32
Accessing pattern index 1:  [0 0 0 0]
Accessing observation index 2:  [0 0]
=====

FileIO

This module provides three main methods. The save and load methods which can be used to respectively simplify writing and reading files. They can handle Pickle and numpy file formats, which might be compressed or not. Consider the following example:

from RingerCore.FileIO import save
import numpy as np
help(save)

# Pickle files
someList = [1,2,3,4]
outputFile = save(someList,'someList')
print 'Saved "%s" file.' % outputFile
outputFile = save(someList,'someList',compress=False)
print 'Saved uncompressed file %s.' % outputFile

# Numpy files
array = np.array(someList)
array2 = np.arange(10)
outputFile = save(array,'numpy_file',protocol='save')
print 'Saved "%s" file.' % outputFile
outputFile = save({'array' : array, 'array2' : array2},'numpy_filez',protocol='savez')
print 'Saved "%s" file.' % outputFile
outputFile = save({'array' : array, 'array2' : array2},'numpy_filez_compressed',protocol='savez_compressed')
print 'Saved "%s" file.' % outputFile
Help on function save in module RingerCore.FileIO:

save(o, filename, **kw)
    Save an object to disk.

Saved "someList.pic.gz" file.
Saved uncompressed file someList.pic.
Saved "numpy_file" file.
Saved "numpy_filez" file.
Saved "numpy_filez_compressed" file.
%%bash
ls -lh *.pic *.gz *.npz *.npy
-rw-r--r-- 1 wsfreund zp 112 Mar  6 07:18 numpy_file.npy
-rw-r--r-- 1 wsfreund zp 386 Mar  6 07:18 numpy_filez_compressed.npz
-rw-r--r-- 1 wsfreund zp 484 Mar  6 07:18 numpy_filez.npz
-rw-r--r-- 1 wsfreund zp  16 Mar  6 07:18 someList.pic
-rw-r--r-- 1 wsfreund zp  49 Mar  6 07:18 someList.pic.gz
from RingerCore.FileIO import load
help(load)

someList = load('someList.pic')
print 'someList loaded from someList.pic: ', someList
someList = load('someList.pic.gz')
print 'someList loaded from someList.pic.gz: ', someList
array = load('numpy_file.npy')
print 'array loaded from numpy_file.npy: ', array
f = load('numpy_filez.npz')
print 'array loaded from numpy_filez.npz: ', f['array']
print 'array2 loaded from numpy_filez.npz: ', f['array2']
f = load('numpy_filez_compressed.npz')
print 'array loaded from numpy_filez_compressed.npz: ', f['array']
print 'array2 loaded from numpy_filez_compressed.npz: ', f['array2']
Help on function load in module RingerCore.FileIO:

load(filename, decompress='auto')
    Loads an object from disk

someList loaded from someList.pic:  [1, 2, 3, 4]
someList loaded from someList.pic.gz:  [1, 2, 3, 4]
array loaded from numpy_file.npy:  [1 2 3 4]
array loaded from numpy_filez.npz:  [1 2 3 4]
array2 loaded from numpy_filez.npz:  [0 1 2 3 4 5 6 7 8 9]
array loaded from numpy_filez_compressed.npz:  [1 2 3 4]
array2 loaded from numpy_filez_compressed.npz:  [0 1 2 3 4 5 6 7 8 9]

Finally, the method expandFolders allows recursively expanding system folders to retrieve files within it.

util

This module provide a miscellanea of utilities. Some of those are covered here:

#### EnumStringification

This class allows emulating a C enumeration on Python. It also provides easy transformation from string to int or vice versa through the methods tostring and fromstring. Its retrieve method makes sure that the used string or provided int value are valid within the enumeration and returns the corresponding enumeration integer.

In the next example we show the usage for a case sensitive EnumStringification:

from RingerCore.util import EnumStringification

class SomeEnum( EnumStringification ):
    val1 = 1
    val2 = 2
    
val = SomeEnum.val1
print val
val = SomeEnum.retrieve("val1")
print val
val = SomeEnum.retrieve("val2")
print val

print SomeEnum.tostring(val)

print SomeEnum.fromstring("val1")

val = SomeEnum.retrieve("Val2")
1
1
2
val2
1



---------------------------------------------------------------------------

ValueError                                Traceback (most recent call last)

<ipython-input-20-17288deedc28> in <module>()
     16 print SomeEnum.fromstring("val1")
     17 
---> 18 val = SomeEnum.retrieve("Val2")


/afs/cern.ch/user/w/wsfreund/Ringer/xAODRingerOfflinePorting/RingerTPFrameWork/RootCoreBin/python/RingerCore/util.pyc in retrieve(cls, val)
     79       if val is None:
     80           raise ValueError("String (%s) does not match any of the allowed values %r." % \
---> 81               (oldVal, allowedValues))
     82     else:
     83       if not val in [attr[1] for attr in allowedValues]:


ValueError: String (Val2) does not match any of the allowed values [('val1', 1), ('val2', 2)].

However, in some cases the case sensitive enumeration may not be the desired behavior. In those cases, set the _ignoreCase property to True, as follows:

from RingerCore.util import EnumStringification

class IgnoreCaseEnum( EnumStringification ):
    _ignoreCase = True
    val1 = 1
    vAl2 = 2
    
val = IgnoreCaseEnum.retrieve("Val2")
print val

# It is also possible to convert to the defined string spelling, via
print IgnoreCaseEnum.tostring(IgnoreCaseEnum.fromstring("VAL2"))
2
vAl2

#### retrieve_kw and NotSet

When used in conjunction, they allow to bypass default configuration retrieved on higher level classes and assure that the default configuration of the lower level classes will be used. This is quite important to make sure that only one default value will be available in the python code and that it is not needed to change all references to that property through all python codes. Take a look on python/CreateData.py and python/FilterEvents.py __call__ methods to see an usage example.

checkForUnusedVars

Used to print remaining keywords on dictionaries. Usually used to display warnings of unused keywords:

from RingerCore.util import checkForUnusedVars

def someFcn(**kw):
    arg1 = kw.pop('arg1', None)
    arg2 = kw.pop('arg2', None)
    checkForUnusedVars(kw)

someFcn(arg0=0,arg1=1,arg2=2,arg3=3)
WARNING:Obtained not needed parameter: arg0
WARNING:Obtained not needed parameter: arg3

traverse

It is used to loop over the individual objects upon multiple iterable objects. It is also possible to change the looping object by using the returned parent object if it a mutable object. Consider the following examples:

# Read every object contained in the iterables (list, tuple) 
# This could be considered zero-level "iterable objects" in the iterable list
from RingerCore.util import traverse
a = [[[1,2,3],[2,3],[3,4,5,6]],[[[4,7],[]],[6]],7]
for obj in traverse(a,(list, tuple),0): print obj
# obj is: (holden object, index in parent iterable, parent iterable, level, depth)
(1, 0, [1, 2, 3], 0, 3)
(2, 1, [1, 2, 3], 0, 3)
(3, 2, [1, 2, 3], 0, 3)
(2, 0, [2, 3], 0, 3)
(3, 1, [2, 3], 0, 3)
(3, 0, [3, 4, 5, 6], 0, 3)
(4, 1, [3, 4, 5, 6], 0, 3)
(5, 2, [3, 4, 5, 6], 0, 3)
(6, 3, [3, 4, 5, 6], 0, 3)
(4, 0, [4, 7], 0, 4)
(7, 1, [4, 7], 0, 4)
(6, 0, [6], 0, 3)
(7, 2, [[[1, 2, 3], [2, 3], [3, 4, 5, 6]], [[[4, 7], []], [6]], 7], 0, 1)

Looping over the iterables and change their holden values:

# Read every object contained in the iterables (list, tuple) 
# This could be considered zero-level "iterable objects" in the iterable list
from RingerCore.util import traverse
a = [[[1,2,3],[2,3],[3,4,5,6]],[[[4,7],[]],[6]],7]
for _, idx, parent, _, _ in traverse(a,(list, tuple),0): parent[idx] = "Changed"
a
[[['Changed', 'Changed', 'Changed'],
  ['Changed', 'Changed'],
  ['Changed', 'Changed', 'Changed', 'Changed']],
 [[['Changed', 'Changed'], []], ['Changed']],
 'Changed']

Instead looping over all objects in the iterable list, we may want to loop over some iterables keeping some "depth":

# Read every "first level" iterables of type (list, tuple) in the iterable list
from RingerCore.util import traverse
a = [[[1,2,3],[2,3],[3,4,5,6]],[[[4,7],[]],[6]],7]
for obj in traverse(a,(list, tuple),1): print obj
([1, 2, 3], 0, [[1, 2, 3], [2, 3], [3, 4, 5, 6]], 1, 3)
([2, 3], 0, [[1, 2, 3], [2, 3], [3, 4, 5, 6]], 1, 3)
([3, 4, 5, 6], 0, [[1, 2, 3], [2, 3], [3, 4, 5, 6]], 1, 3)
([4, 7], 0, [[4, 7], []], 1, 4)
([6], 0, [[[4, 7], []], [6]], 1, 3)
([[[1, 2, 3], [2, 3], [3, 4, 5, 6]], [[[4, 7], []], [6]], 7], 2, None, 1, 1)
# Read every "second level" iterables of type (list, tuple) in the iterable list
from RingerCore.util import traverse
a = [[[1,2,3],[2,3],[3,4,5,6]],[[[4,7],[]],[6]],7]
for obj in traverse(a,(list, tuple),2): print obj
([[1, 2, 3], [2, 3], [3, 4, 5, 6]], 0, [[[1, 2, 3], [2, 3], [3, 4, 5, 6]], [[[4, 7], []], [6]], 7], 2, 2)
([[4, 7], []], 0, [[[4, 7], []], [6]], 2, 3)
([[[4, 7], []], [6]], 1, [[[1, 2, 3], [2, 3], [3, 4, 5, 6]], [[[4, 7], []], [6]], 7], 2, 2)
# Read every "third level" iterables of type (list, tuple) in the iterable list
from RingerCore.util import traverse
a = [[[1,2,3],[2,3],[3,4,5,6]],[[[4,7],[]],[6]],7]
for obj in traverse(a,(list, tuple),3): print obj
([[[1, 2, 3], [2, 3], [3, 4, 5, 6]], [[[4, 7], []], [6]], 7], 0, None, 3, 1)
# Read every "fourth level" iterables of type (list, tuple) in the iterable list
from RingerCore.util import traverse
a = [[[1,2,3],[2,3],[3,4,5,6]],[[[4,7],[]],[6]],7]
for obj in traverse(a,(list, tuple),4): print obj
([[[1, 2, 3], [2, 3], [3, 4, 5, 6]], [[[4, 7], []], [6]], 7], 1, None, 4, 1)
# Read every "fifth level" iterables of type (list, tuple) in the iterable list
from RingerCore.util import traverse
a = [[[1,2,3],[2,3],[3,4,5,6]],[[[4,7],[]],[6]],7]
for obj in traverse(a,(list, tuple),5): print obj

C++ provided functionalities

The most important C++ functionality provided is available in the RingerCore/MsgStream.h file. It is based on Athena framework's MsgStream, but does not need the Gaudi infrastructure. The name was changed to MsgStreamMirror as it cannot have the same name from the Asg class (otherwise it would generate dictionary conflicts when running PyROOT).

It also provides the macros which emulate the same behavior from Athena:

MSG_DEBUG(msg)
MSG_INFO(msg)
MSG_WARNING(msg)
MSG_ERROR(msg)
MSG_FATAL(msg)

Finally, in order to make those macros available on your classes, make sure them inherit from MsgService, also defined in this file. In case classes inheriting from classes that inherit from MsgService are needed, and it is wanted that the messaging system display the most derived class name, overwrite the IMsgService constructor to use the most inherited class name. E.g.:

/** 
 * Supose MsgService -> A -> B 
 * then:
 **/

class A : public MsgService
{
    A()
      : IMsgService("A"),
        MsgService(MSG::INFO)
      {;}
};

class B : public A
{
    B()
      : IMsgService("B"),
        A()
      {;}
};