How to write a detector class for the C++ analyzer

Ole Hansen
Rev 1.0, 14 June 2003

This document serves as a guide to writing a detector class for the Hall A C++ analyzer. It is applicable as of version 1.0 of the software.

Index


Getting started

Before you start programming your new detector class, you will need to make a few decisions:
Once you have decided where your new detector fits into the class hierarchy, take one of the existing classes as a template and start rewriting it. There are a few requirements to keep in mind. Your class must Also, your class should

A simple example for a detector class is the generic scintillator class THaScintillator.


Initialization

Every detector must have a public Init() method of type

THaAnalysisObject::EStatus Init( const TDatime& date )

This method should obtain all detector-specific parameters (geometry, mapping, calibrations, etc.) from the database. It should not be called from the constructor(s). Init() is called by the standard analyzer at the beginning of the analysis of a run. The TDatime object that is passed contains the time stamp of the prestart event of the run. It should be used to do time-dependent initializations, such as retrieving appropriate calibration parameters from tye database. While you can write your own Init() function, it is recommended that you use the default Init() provided by the THaAnalysisObject base class. This default function will establish the connection to the database for you. In analyzer version 1.0, the database is simply a collection of plain text files. There is support time-dependence with a 1-day granularity and less (see database). If you do use the default Init() method, you should (but do not have to) implement one or both of the following methods in your detector class. (These methods may be protected to prevent direct calls.)

virtual Int_t ReadDatabase( const TDatime& date )

virtual Int_t DefineVariables( Emode mode )

The first action in ReadDatabase() should be to open the plaintext database file corresponding to the detector (as determined by the detector's and containing apparatus's names) as well as the time stamp of the current run. A convenience function OpenFile() is available for this purpose. You can use the FILE* handle returned by OpenFile() with standard C file I/O commands (fscanf, fgets etc.) to read the file. The file must be closed before leaving ReadDatabase().

DefineVariables() is called after ReadDatabase() and should be used for setting up global symbolic variables. If your detector does not need a database file, you can put your initializations either in Init() or in DefineVariables(), or write a ReadDatabase() method that does not actually open any files. If you do not need any initialization, you do not need to implement any functions.


Global Variables

Any data of your detector that you want to be accessible within the global analyzer context should be registered as global symbolic variables in the global list gHaVars. Any variables that you want to have available in
tests/cuts or output to a ROOT file must be registered in this way. You can register variables either with a series of calls to

gHaVars->Define( ... )

or with a single call to

DefineVariables( const VarDef* vars )

. The second form is recommended. Both forms can be mixed since DefineVariables() simply make the appropriate calls to Define(). You must call Define() explicitly in order to define multidimensional arrays.

VarDef is an array whose entries have the structure (see VarDef.h)

  struct VarDef {
    const char*      name;     // Variable name
    const char*      desc;     // Variable description
    Int_t            type;     // Variable data type (see VarType.h)
    Int_t            size;     // Size of array (0/1 = scalar)
    const void*      loc;      // Location of data
    const Int_t*     count;    // Optional: Actual size of variable size array
  };
A typical sequence for defining global variables (e.g. in SetupDetector()) would be
  #include "VarDef.h"

  VarDef vars[] = {
    { "nhit", "Number of hits",  kInt,   0,      &fNhit },
    { "adc",  "ADC values",      kFloat, fNelem, fADC },
    { "csum", "Cluster sums",    kFloat, MAXCLU, fCsum, &fNclusters },
    { NULL }
  };
  DefineVariables( vars );
This will register three global variables called "nhit", "adc", and "csum", prefixed with the apparatus name and detector name, e.g. "L.s1.nhit". The data types (kInt, kFloat, see VarType.h) must correspond exactly to the types of the variables (5th field). "nhit" is a scaler (size = 0), "adc", a fixed-size array of size fNelem, and "csum" is a variable-size array of maximum size MAXCLU and actual size contained in the variable fNclusters. The actual size is allowed to change from event to event. The 5th field (&fNhit, fADC, fCsum) must be the address of the variable or the first array element. (You could also write &fADC[0] instead of fADC if you like to type a lot.) The 6th field (&fNclusters), if not NULL, must be the address of a variable of type Int_t. (If the 6th field is not specified, it is automatically initialized to NULL.) The definitions must always end with a final { NULL } entry or your program will crash.

Decoding

Every detector must have a Decode() method. It must be of type

Int_t Decode( const THaEvData& evdata )

Within the standard analyzer, Decode() will be called for every physics event. (You can override this behavior if you use your own apparatus class.) The event's raw data can be accessed via the methods of the THaEvData object that is passed as the argument of Decode(). At the minimum, your Decode() function should find the detector's information in the event buffer and move it to data members of your class. Of course you may apply further processing if appropriate.

Within Decode() you must only do processing that does not require information from any other detectors. If you need information from other detectors, put the corresponding code either in the CoarseProcess()/CoarseTrack() or FineProcess()/FineTrack() method of the detector class or in the Reconstruct() method of the apparatus class to which your detector belongs.


Coarse processing

The CoarseProcess() method is relevant only for non-tracking detectors. Its type is

Int_t CoarseProcess( TClonesArray& tracks )

In the standard analyzer, CoarseProcess() is called for every non-tracking detector after CoarseTrack() has been called for all tracking detectors. Information on the tracks found for the current event, if any, is passed in the TClonesArray. This array contains zero or more THaTrack objects. The track information is "Coarse", i.e. only preliminary reconstruction has been performed.

Here is a simple example how to retrieve the information from all tracks passed in the array:

  #include < THaTrack.h >

  int ntrack = tracks.GetLast()+1; 

  for( int i = 0; i < ntrack; i++ ) {
    THaTrack* theTrack = static_cast < THaTrack* > ( tracks.At(i) );
    if( !theTrack ) continue;
    Double_t fpx    = theTrack->GetX();     // x-position in fp
    Double_t fpy    = theTrack->GetY();     // y-position in fp
    Double_t fpth   = theTrack->GetTheta(); // theta wrt normal to fp
    Double_t fpph   = theTrack->GetPhi();   // phi wrt fp x-axis
    //... do computations ...
  }
Within CoarseProcess(), you cannot generally use information from any other detectors except tracking detectors. There is no guarantee that the non-tracking detectors are processed in any particular order. Therefore, this method should operate only on the tracks passed in the array.


Fine processing

Like
CoarseProcess(), the FineProcess() method is relevant only for non-tracking detectors. Its type is

Int_t FineProcess( TClonesArray& tracks )

FineProcess() is called for every non-tracking detector after all focal-plane track reconstruction has been done by the tracking detectors. Thus, this method should be used for any detector-specific processing that requires precise track information. Track information can be obtained from the TClonesArray of THaTracks in the same manner as described above for CoarseProcess().

Within FineProcess(), you can use all "coarse processing" information from all other detectors as well as all information from all tracking detectors. To access information from another detector, either retrieve a global physics variable defined by that detector or use the public interface of that detector. For example:

  // Retrieve global physics variable called "r.s.x"
  #include "THaVarList.h"
  #include "THaVar.h"
  Double_t x;

  THaVar* pvar = gHaVars->Find("r.s.x");
  if( pvar ) x = pvar->GetValue();

  //-------------------------------------------------------

  // Call method GetX() of detector "mydet"
  #include "THaApparatus.h"
  #include "THaMyDet.h"       //as appropriate
  Double_t x;

  THaMyDet* mydet = static_cast< THaMyDet* >( fApparatus->GetDetector("mydet"));
  if( mydet ) x = mydet->GetX();


Coarse tracking

This method is only relevant for tracking detectors. It has the type

Int_t CoarseTrack( TClonesArray& tracks )

CoarseTrack() is called immediately after Decode() has been called for all detectors. The TClonesArray of tracks is empty when the function is called and is intended to be an output variable. Tracking detectors are not guaranteed to be processed in a particular order, so the only information that can be assumed to be valid in this function is the decoded data for all detectors.

CoarseTrack() is expected to reconstruct tracks from the decoded data in the fastest possible way, create THaTrack objects, and put them in the output array. For convenience, the THaTrackingDetector base class defines a method AddTrack that can be used to add tracks to the array. This method should be be used for this purpose unless there is a good reason not to.


Fine tracking

Like
CoarseTrack(), this method is only relevant for tracking detectors. It has the type

Int_t FineTrack( TClonesArray& tracks )

FineTrack() is called after Decode(), CoarseTrack(), and CoarseProcess() have been executed for all detectors. Upon entry, the TClonesArray of tracks contains the tracks found by CoarseTrack(). Tracking detectors are not guaranteed to be processed in a particular order, so the only information that can be assumed to be valid in this function is the decoded and coarse-processed data for all detectors.

FineTrack() is expected to reconstruct tracks in the most precise way possible, using the decoded data and the coarse tracks as input. The function should modify the tracks already present in the input array rather than add new tracks. THaTrack provides several Set() methods that allow altering track data.


Last modified: Mon Jun 16 15:38:28 EDT 2003

Maintained by Ole Hansen