Extending the Data Structure
Planning
In order to extend the data structure (RAT::DS), you must decide several things:
What sort of data would you like to record?
If it is an atomic value, like a float or an integer, your task will be easy. If it is several pieces of data, it may make more sense to make a new class to put in the event data structure.
The only types allowed in the data structure are standard C++ atomic types like int and double (and ROOT-specific typedefs to them), STL containers, like vector and map, and classes which follow the RAT::DS style. That is, they are subclasses of TObject which follow the RAT style conventions. Note that C-style arrays and ROOT container classes (e.g. TClonesArray) are not allowed!
What do you want to call it?
Short (but not cryptic) names are preferred, and it helps to make them unique over all branches of the event data structure. This simplifies plotting with TTree::Draw() in ROOT later, so that the branch need not be specified to disambiguate a name.
Where will this data be created and where will it be used?
Data stored in the event structure must be “created” somewhere, either in a generator or a processor. You should decide where the code which computes this information should go. If the information is at all optional, it’s best to put the computation in a separate processor so users who don’t need the data can skip it and save running time.
Also remember that RAT::DS is the interface by which processors “communicate” with each other. Intermediate calculations should not be stored in the data structure unless they would be of interest to an end user. However, any data from one processor that is the input to the calculation of another processor should be stored in the data structure.
Adding Existing Data Types to Objects
If you are adding data of an already existing data type, you can just edit the appropriate header file and recompile. No other action is required, except editing the appropriate processor or generators to actually put some data there!
Creating a New Class for the Data Structure
If you need to create a new class, you need to:
Make the header file
Your new class must subclass TObject and be declared within the namespace RAT::DS. For ease, copy an existing event class and edit it. Do not forget to put the new class name in the !ClassDef macro.
Note that DS class members should not use a prefix like “f” (that is, use “pmtCount” rather than “fPMTCount”), since they are part of the user interface, in a sense. Data members should be private, with the interface defined by “getter” and “setter” methods. The name of the file should match the name of the class.
For example, a class with PMT information might look like this:
/**
* @class RAT::DS::PMT
* @author A. Hamsterton <hamster@urodents.edu>
* @brief Example of PMT information
*
* This is a long description of the class. This whole
* block is here so we can generate Doxygen documentation.
*/
#ifndef __RAT_DS_PMT___
#define __RAT_DS_PMT___
#include <TObject.h>
#include <vector>
namespace RAT {
namespace DS {
class MyClass : public TObject {
public:
MyClass() {}
virtual ~MyClass() {}
/// PMT channel number
int GetPMTID() { return pmtID; }
void SetPMTID(int _pmtID) { pmtID = _pmtID; }
/// Digitized hit time information
int GetADCTime() { return adcTime; }
void SetADCTime(int _adcTime) { adcTime = _adcTime; }
/// Digitized hit charge information
int GetADCCharge() { return adcCharge; }
void SetADCCharge(int _adcCharge) { adcCharge = _adcCharge; }
/// True hit time information
int GetTime() { return time; }
void SetTime(int _time) { time = _time; }
/// True hit charge information
int GetCharge() { return charge; }
void SetCharge(int _charge) { charge = _charge; }
private:
int pmtID; ///< PMT channel number;
int adcTime; ///< Digitized hit time
int adcCharge; ///< Digitized hit charge
float time; ///< True hit time
float charge; ///< True hit charge
ClassDef(PMT, 1)
};
} // namespace DS
} // namespace RAT
#endif // __RAT_DS_PMT__
Make an implementation
Make in implementation in a cc file with the same name as the header, if necessary. Remember that DS classes should not do very much work – they are vessels for data which is computed elsewhere (such as a generator or processor). Never should a DS class itself be computing the data that it stores.
Add to the ROOT dictionary
Edit the LinkDef fragment for the ds module,
src/ds/include/RAT/DS/LinkDef.hh, to ensure your new class is added to the
ROOT dictionary, which is needed for the object to be streamed to disk and for
ROOT macros to work properly. You will need to add a line that looks like:
#pragma link C++ class RAT::DS::PMT + ;
And further down, in the __MAKECINT__ block, there needs to be a line for
any STL container of your class, like:
#pragma link C++ class vector < RAT::DS::PMT>;
Register the header for the dictionary
The dictionary generator also needs to see the header that declares your
class. Make sure the ds module’s CMakeLists.txt (src/ds/CMakeLists.txt)
lists it in its rat_dictionary_headers() call, e.g.:
rat_dictionary_headers(
RAT/DS/PMT.hh
# ...other headers...
)
Each module registers its own headers and LinkDef fragment via
rat_dictionary_headers() and rat_dictionary_linkdef() (see
cmake/RATDictionary.cmake). These accumulate into a single global
dictionary that is generated once in src/CMakeLists.txt, so you no longer
need to touch any central build file.
Recompile
Rebuild with make (or re-run make from your build directory) to
regenerate the dictionary and pick up the new class.
Extending the Data Structure in a Private Experiment
The techniques above are appropriate when the new data belongs in RATPAC
itself and can be shared with everyone. Private experiments, however, often
need to attach experiment-specific data to the top-level event object,
RAT::DS::Root, without modifying the shared class. This is the supported
way to extend the data structure for a private experiment: subclass
RAT::DS::Root in your own code base and register the subclass with
RAT::DS::RootFactory, rather than editing RATPAC’s own data-structure
classes.
RAT::DS::RootFactory acts as the single point where the top-level event
object is constructed. A new RAT::DS::Root only ever originates in a
producer – the object that introduces events into the data stream, such as
Gsim for simulated events or InROOTProducer for events read back from a
ROOT file. These producers obtain the event object from
RAT::DS::RootFactory::Create() instead of calling new DS::Root
directly. (The ROOT I/O classes DSReader, DSWriter, and OutROOTProc
also build their branch buffer through the factory so that the on-disk type
matches, but they do not introduce new events – only producers do.)
By default the factory returns a plain RAT::DS::Root. If you register the
name of a subclass, the factory instead constructs and returns that subclass
everywhere, so your extended object flows through the whole simulation and I/O
chain transparently.
Subclass RAT::DS::Root
In your private code base, create a class that inherits from
RAT::DS::Root and adds the members and accessors you need. It must have a
default (I/O) constructor and a ClassDef, following the same conventions
as any other data-structure class:
namespace MyExp {
namespace DS {
class Root : public RAT::DS::Root {
public:
Root() {}
virtual ~Root() {}
double GetMyValue() const { return myValue; }
void SetMyValue(double v) { myValue = v; }
private:
double myValue; ///< Experiment-specific quantity
ClassDef(MyExp::DS::Root, 1);
};
} // namespace DS
} // namespace MyExp
Because official processors only ever see the RAT::DS::Root interface,
your extra data is invisible to them, but it is written to and read back from
disk with the rest of the event.
Register the subclass in the ROOT dictionary
ROOT needs dictionary information for your subclass so that it can be
constructed by name and streamed to disk. Contribute the header and a LinkDef
fragment from your module’s CMakeLists.txt using the helpers in
cmake/RATDictionary.cmake, exactly as an in-tree module would:
rat_dictionary_headers(MyExp/DS/Root.hh)
rat_dictionary_linkdef(MyExp/DS/LinkDef.hh)
where your LinkDef.hh fragment contains the usual line:
#pragma link C++ class MyExp::DS::Root + ;
Select the subclass at start-up
Tell the factory which class to build by calling:
RAT::DS::RootFactory::SetClassName("MyExp::DS::Root");
This must run once, early, before any event object is created (for example
in your experiment’s main or an initialization hook). From that point on,
every RAT::DS::RootFactory::Create() returns a MyExp::DS::Root.
The factory validates the request at run time: it looks up the class in the
ROOT dictionary, checks that it actually inherits from RAT::DS::Root, and
throws a std::runtime_error if the name is unknown or does not derive from
RAT::DS::Root. If the name is left empty (or set to "RAT::DS::Root"),
the factory simply returns the base object, so RATPAC’s default behavior is
unchanged when no experiment override is registered.
Update Documentation
Don’t forget to update the documentation in $RATROOT/doc to reflect the changes you have made to the data structure! Remember to explain what the data is and what units it is stored in.