Overview
Teaching: 60 min Exercises: 60 minQuestions
Why is Gaudi used?
Objectives
Learn about the Gaudi framework.
Gaudi is being improved with the following goals in mind:
In order to reach those goals, the following choices should be made when designing Gaudi algorithms:
const
usage: Since C++11, const
and mutable
are defined in terms of data races (see this video).beginEvent
/endEvent
, since there might be multiple events.new
/delete
usage (C++11 goal).get/put<MyData>("")
for safer constructs.One of the standout features of new Gaudi is in the new properties; they can be directly defined in the class definition instead of the implementation and used everywhere in the class, instead of requiring special calls in the constructor and custom management of the data. So the following now creates a property:
Gaudi::Property<int> m_some_int{this, "SomeInt", 0, "Description of some int"};
This defines a member variable of type Gaudi::Property<int>
, and calls the constructor on initialization with several parameters. The first is this
, a pointer to the current class. The second is the property name. The third is the default value. You can also optionally give a description.
Notes on the new syntax
The usage of
this
is a common feature of the new interfaces, giving Gaudi components access to the algorithms that contain them. The location ofthis
is not consistent across the components, however, as you will see withAnyDataHandle
.
The requirement to inherit from DataObject
has been lifted with AnyDataHandle
. This provides a wrapper than can be put into the TES, and can be retrieved from it as well. Assuming you have some object called Anything
, you can wrap it in AnyDataHandleWrapper
and then use the traditional get/put syntax:
auto vec = new AnyDataWrapper<Anything>(Anything{1,2,3,4});
put(vec, "/Event/Anything");
auto base = getIfExists<AnyDataWrapperBase>("/Event/Anything");
const auto i = dynamic_cast<const AnyDataWrapper<Anything>*>(base);
const Anything anything = i->getData();
This, however, is significantly convoluted. A much cleaner way to add an AnyDataHandle
is to construct it as a member of your class, just like a Gaudi::Property
, and then use the .get
and .put
methods.
AnyDataHandle<Anything> m_anything{"/Event/Anything", Gaudi::DataHandle::Writer, this};
m_anything.put(Anything{1,2,3,4});
AnyDataHandle<Anything> m_anything{"/Event/Anything", Gaudi::DataHandle::Reader, this};
const Anything* p_anything = m_anything.get();
Notes on the new syntax
Adding AnyDataHandle as a member variable breaks compatibility with the
using
statement, so you will need to explicitly define the constructor.
This works by replacing inheritance with type erasure.
Rather than provide a series of specialised tools, the new Gaudi::
Functional` provides a general building block that is well defined and multithreading friendly. This standardizes the common pattern of getting data our of the TES, working on it, and putting it back in (in a different location). This reduces the amount of code, makes it more uniform, and encourages ‘best practice’ procedures in coding in the event store. The “annoying details”, or scaffolding, of the design is left to the
framework.
A functional, in general, looks like this example of a transform algorithm to sum two bits of data:
class MySum
: public TransformAlgorithm<OutputData(const Input1&, const Input2&)> {
MySum(const std::string& name, ISvcLocator* pSvc)
: TransformAlgorithm(
name,
pSvc, {
KeyValue("Input1Loc", "Data1"),
KeyValue("Input2Loc", "Data2") },
KeyValue("OutputLoc", "Output/Data")) { }
OutputData operator()(const Input1& in1, const Input2& in2) const override {
return in1 + in2;
}
This example highlights several features of the new design. This starts by inheriting from a templated class, where the template defines the input and output expected. Different functionals may have no input and/or output, and might have multiple inputs or outputs. The constructor takes KeyValue objects that define the data inputs and outputs, with multiple data elements input as a initializer list. The operator()
method is overridden, and is const
to ensure thread safety
and proper design. This takes the inputs and returns an output.
The functionals available in the framework are named by the data they work on (with examples):
Consumer
Rec/RecAlgs
: EventTimeMonitor
, ProcStatusAbortMoni
, TimingTuple
Producer
FilterPredicate
Phys/LoKiHlt
: HDRFilter
, L0Filter
, ODINFilter
Phys/LoKiGen
: MCFilter
Hlt/HltDAQ
: HltRoutingBitsFilter
Rec/LumiAlgs
: FilterFillingScheme
, FilterOnLumiSummary
Transformer
MultiTransformer
MergingTransformer
Calo/CaloPIDs
: InCaloAcceptanceAlg
Tr/TrackUtils
: TrackListMerger
SplittingTransformer
Hlt/HltDAQ
: HltRawBankDecoderBase
ScalarTransformer
Calo/CaloReco
: CaloElectronAlg
, CaloSinglePhotonAlg
A note on stateless algorithms
A stateless algorithm (one that does not store state between events) provides several important benefits:
- Thread safety
- Better scalability
- Leaner code
The downside is that a lot of code needs to be migrated.
The simplest of the functional algorithms, this produces Out1
to OutN
given nothing:
class MyProducer : public Gaudi::Functional::Consumer<int()> {
public:
MyProducer(const std::string& name, ISvcLocator* svcLoc)
: Producer( name, svcLoc,
KeyValue("OutputLocation", {"MyNumber"})) {}
int operator()() const override {
return 314;
}
}
This inherits from the templated Producer, where the template is the signature of the operator()
function. Here, it is a function that produces an int given nothing. When making the constructor, the base class is called with one more argument, the output(s) of the function as KeyValue
, where the first argment is the key (output location), and the second one is a list of locations in the TES that will be produced (here, it is /Event/MyNumber
).
The important part of the class is the operator()
, which produces an int (just 314 in this example) that gets placed on the TES. The Producer is currenlty unused in the LHCb codebase, but is intended for use for IO from files, and for random number generation.
If you want to produce several outputs, you can return a tuple, and give a list of KeyValues, one for each tuple member.
This is a class that takes in TES data. It looks like:
class MyConsumer : public Gaudi::Functional::Consumer<void(const int&)> {
public:
MyConsumer(const std::string& name, ISvcLocator* svcLoc)
: Consumer( name, svcLoc,
KeyValue("InputLocation", {"MyNumber"})) {}
void operator()(const int& input) const override {
info << "Printing input: " << input << endmsg;
}
}
The class is created inheriting the templated class Consumer
, with the signature of the operator()
function as it’s first argument as before.
The constructor should include the KeyValue
input that it will use. The Consumer in the example above takes the int we put in the TES before and prints it to the info log. The value in the TES is obtained by reference.
This blocks algorithms behind it, returns filterPassed
.
Split or merge containers. These take one or more inputs, and produce one or more outputs.
Gaudi::Functional
algorithm. Split into smaller basic algorithms and build a SuperAlgorithm
from the smaller ones if needed.const
; do not cache event dependent data. Interface code, especially for tools, should be const
.beginEvent
, endEvent
incidents.An example
An example with some of these features can be found by running:
svn export https://github.com/lhcb/DevelopKit/trunk/code/GaudiNewAPI GaudiNewAPI cd GaudiNewAPI/GaudiFunctional source build_and_run.sh
This package has two examples,
GaudiFunctional
andGaudiDataTrans
, which use the new system to transfer data in and out of the TES using DataHandles and the functional approach. Feel free to explore and modify these examples. If you come up with a new useful example, please submit it as a PR.
Key Points
Work with Gaudi.