Pixie16 Analysis Software Suite
Analysis code for processing of .ldf files
Primer

In this section I will attempt to provide a rough description of the aspects of C++ that are being used in the analysis code that someone who has never seen C++ before may not recognize. Described here are

  • classes
  • pointers
  • vectors
  • and maps

For more information on any of the topics mentioned here you are referred to the fantastic resource cplusplus.com. This webpage has many thorough descriptions of the ideas presented here, as well as, a complete method listing for the STL templates discussed.

Classes

The C++ code is structured around classes. Classes are very similar to structures in C, and are objects into which data is placed or actions are performed. Functions that are contained within classes are called "methods". For example, the class RawEvent is the basic object into which all event data will be placed in the analysis. Another example is the class GeProcessor, which performs much more complex manipulations of data that is taken using Ge detectors. In the analysis, all classes consist of two different files: a '.hpp' file, and a '.cpp' file. The '.hpp' file defines the class, its data members, and the methods that allow a user to manipulate the data members. The ".cpp" file contains the code necessary for the class to do what you want. The following code shows an example for the class called ChanEvent that contains the information for each channel that triggered. Below, we list the content of ChanEvent in its entirety.

#ifndef __CHANEVENT_HPP
#define __CHANEVENT_HPP
#include <vector>
#include "pixie16app_defs.h"
#include "Identifier.hpp"
#include "Globals.hpp"
#include "Trace.hpp"
class ChanEvent {
public:
void SetEnergy(double a) {energy = a;}
void SetCalEnergy(double a) {calEnergy = a;}
void SetTime(double a) {time = a;}
void SetCorrectedTime(double a) { correctedTime = a;}
void SetCalTime(double a) {calTime = a;}
void SetHighResTime(double a) {highResTime =a;}
bool GetCfdSourceBit() const {
return(cfdTrigSource);
}
bool CfdForceTrig() const {
return(cfdForceTrig);
}
double GetEnergy() const {
return energy;
}
double GetCalEnergy() const {
return calEnergy;
}
double GetCorrectedTime() const {
return correctedTime;
}
double GetTime() const {
return time;
}
double GetCalTime() const {
return calTime;
}
double GetHighResTime() const {
return highResTime;
}
double GetEventTime() const {
return eventTime;
}
const Trace& GetTrace() const {
return trace;
}
return trace;
}
unsigned long GetTrigTime() const {
return trigTime;
}
unsigned long GetEventTimeLo() const {
return eventTimeLo;
}
unsigned long GetEventTimeHi() const {
return eventTimeHi;
}
unsigned long GetRunTime0() const {
return runTime0;
}
unsigned long GetRunTime1() const {
return runTime1;
}
unsigned long GetRunTime2() const {
return runTime2;
}
bool IsPileup() const {
return pileupBit;
}
bool IsSaturated() const {
return saturatedBit;
}
const Identifier& GetChanID() const;
int GetID() const;
unsigned long GetQdcValue(int i) const;
void ZeroVar();
private:
double energy;
double calEnergy;
double calTime;
double correctedTime;
double highResTime;
static const int numQdcs = 8;
double time;
double eventTime;
int modNum;
int chanNum;
bool pileupBit;
bool saturatedBit;
bool cfdForceTrig;
void ZeroNums(void);
friend int ReadBuffDataA(pixie::word_t *, unsigned long *, std::vector<ChanEvent *> &);
friend int ReadBuffDataD(pixie::word_t *, unsigned long *, std::vector<ChanEvent *> &);
friend int ReadBuffDataF(pixie::word_t *, unsigned long *, std::vector<ChanEvent *> &);
};
bool CompareCorrectedTime(const ChanEvent *a, const ChanEvent *b);
bool CompareTime(const ChanEvent *a, const ChanEvent *b);
#endif

In the ChanEvent example, all the data members are defined as private and methods that act on the data members are public. This means that only the methods belonging to this class such as GetEnergy() can access the private data members. This prevents the variables from being inadvertently altered by a different portion of the analysis code. I've tried to keep all data members private but haven't in all cases just for simplicity (for example see the RawEvent class in the RawEvent.hpp file).

The arguments that must be passed to a method are defined between the parenthesis after the method's name. Thus, the method SetEnergy() must be provided a double-precision numerical value in order to work properly and would look like the following:

chanEvent.SetEnergy(3.1416);

The other important aspect of the methods are their return values, which is defined before the method name. For example, the method GetEnergy() returns the value contained in the variable energy which is a double. Therefore to correctly retrieve the value you need to use a double.

double tempEnergy = chanEvent.GetEnergy();

Arguments to methods and results thereof are not limited to basic data types (int, double, char, ...) but can include other objects as well. In the method, GetChanID() method an Identifier object is returned. When using any of the methods make sure you know what type of argument is required.

Methods that are defined in the ".hpp" file of the class are said to be defined "inline". Most of the methods in ChanEvent are defined inline. One exception is ZeroVar() which is defined in the corresponding '.cpp' file; a portion of which is shown below:

\snippet ChanEvent.cpp Zero Channel

The only exception to this *.hpp and *.cpp pattern is PixieStd.cpp No class is defined for PixieStd because the main function hissub_ is called from the FORTRAN scan program. A class could probably be constructed but is not strictly necessary in this case.

Pointers

Pointers are both an incredibly powerful feature of C++ and the easiest way to screw up the code. The two types of pointers in C++ are a pointer by value and a pointer by reference. A pointer by value is declared by an '*' in the C++ code and points to the value contained in a variable. A pointer by reference is declared by an '&' in the C++ code and points to the memory location where a variable is stored. An example usage is:

ChanEvent eventList[100];
// ...
ChanEvent *chan;
chan = &eventList[1];

In this section of code a pointer by value 'chan' is created that that will point to a ChanEvent object. The second line puts the memory location of eventList[1] (a ChanEvent object) into the pointer chan. In this way it is possible to perform actions on chan and have it affect the values contained in the variable eventList[1].

Functions also rely heavily on pointers. By default when a C++ function is passed a variable a local copy is created inside the function. This means that if a value is passed to a function in this manner the function can use the value and alter it but after the function is finished the local copy of the value disappears, and the original is not affected. In general, it is far more useful to have a function act on and permanently alter the original value. This is where pointers are useful. A pointer by reference or value can be passed to a function and actions on the pointer will affect the original data. The passing of pointers is also much less computationally intensive speeding up the data analysis. For example, a function declared to receive a pointer by reference would look like:

Where a reference to the variable passed to the function is received.

Vector

The vector class is part of the STL (standard template library) that is used in the analysis. A vector is essentially a dynamically expandable array. A vector variable is defined as

vector<int> intVec;

where intVec is declared as a vector that contains integer values. The two common functions that are used to act on vectors include the push_back and clear functions.

intVec.push_back(2);
intVec.push_back(5);
intVec.clear();

The push_back function inserts a value at the end of the vector. In this example the first element of the vector intVec will have a value of 2 and the second element is 5. The clear() function removes all entries in a vector and is extensively used in the code to zero the various vectors that are used. The values of a vector can be retrieved in two different ways. First, you can ask for a specific element using the same syntax as an array, thus

cout << intVec[1] << endl;

will print out a value of 5.

Warning
C++ index numbering starts at 0 not at 1 as in FORTRAN. Let me repeat since this causes many problems if forgotten. C++ index numbering starts at 0 not at 1 as in FORTRAN.

The second way to retrieve a value from a vector is through an iterator. This is a pointer that can be pointed any location in the array to retrieve the value at that location. The following section of code would loop over the intVec from beginning to end and print the value of the element. Note the incrementing of the iterator iv using iv++ to go to the next element. Using iterators is, by far, the safest way to access the contents of the vector because an out of bound failure is much more descriptive than simply "Segmentation Fault".

vector<int>::iterator iv;
for(vector<int>::iterator it = intVec.end(); it != intVec.begin(); it++) {
cout << *it << endl;
}

Map

The map is the other STL feature used in the code. The map allows an association between an unique key and a value. It is defined as

map<string,int> stringIntMap;

In this example the key is a string and the associated value is an int. To create a map you have to make pairs of the keys and values as in the following example.

stringIntMap.insert(make_pair("dssd",2));
stringIntMap.insert(make_pair("ge",15));

In this example the key "dssd" is associated with an integer of 2 and the key "ge" is associated with an integer of 15. The associated values can be retrieved either by a direct access using the key or using of the find function.

int temp;
temp = stringIntMap["dssd"];
int temp1;
temp1 = stringIntMap.find("ge");

where temp and temp1 will return 2 and 15 respectively. While associating strings with integers is not very useful, a map of strings associated with objects is a powerful method to associate an unknown number of detector types with data objects as is done in the pixie16 analysis.

Warning
I (SNL) am not a C++ expert! It is possible that in the analysis code things are done inefficiently. Also, the descriptions I have given above may not be completely accurate but instead reflect a lack of understanding on my part. Keep that in mind when reading the manual and the code. If you think things could be done differently for clarity or processing speed please let me know.
I (DTM) might be a C++ expert.
I (SVP) might be a C++ fanboy.