3.2. Parallel DDASSpecTcl

Example 3-6. Obtaining the Parallel SpecTcl skeleton


	    mkdir parallel
	    cd parallel
	    cp /usr/opt/spectcl/6.0-000/DDASSkel/* .
	  

The skeleton consists of several files. The example has been created following the model of analysis pipeline of the one existing experiments.

In editing MySpecTclApp.cpp, you need to consider three things:

To setup the event processing pipeline you'll need to provide #include directives to pull in the headers for your event processors. Don't specify absolute paths, take care of that in the Makefile.

Here's an example

Example 3-7. Including event processor headers


	    ...
	    //  Here you should include your headers for your event processors.

	    #include <MyCalibrator.h>
	    ...
	  

The command shown is in the skeleton to indicate where to put these #include directives.

Next, locate the class MySpecTclApp, create and register the event processor(s) you need in the order you want them called. For example,

Example 3-8. Registering MyParameters, MyParameterMapper, and the processing pipeline


	    void
	    CMySpecTclApp::CreateAnalysisPipeline(CAnalyzer& rAnalyzer)
	    {
	    MyParameters* pParams = new MyParameters("ddas");
	    MyParameterMapper* pMapper = new MyParameterMapper(*pParams);
	    RegisterData(pMapper);
	    RegisterEventProcessor(*(new DAQ::DDAS::CDDASBuiltUnpacker({1, 2, 3 })), "Raw");
	    RegisterEventProcessor(*(new MyCalibrator()), "Cal");
	    }
	  

It is fundamental to notice how objects are declared and dynamically created. Global static object are forbidden by multithreading programming. Local static object are good. Declaring any of those objects without initialization outside CMySpecTclApp::CreateAnalysisPipeline would cause undefined behaviors.

Let's look now at the MyParameters definition.

Example 3-9. Definition of MyParameters.h


	      #ifndef MYPARAMETERS_H
	      #define MYPARAMETERS_H

	      #include <config.h>
	      #include <TreeParameter.h>
	      #include <string>
	      #include <PipelineData.h>
	      #include <vector>
	      #include "MyPipelineData.h"
	      #include "MyParameters2.h"

	      struct ChannelData {

	      CTreeParameter energy;
	      CTreeParameter timestamp;

	      // Initialize the TreeParameters
	      //
	      // We will create TreeParameters with names associated with
	      // the name passed in. For example, if name = "rawdata", then
	      // we will create TreeParameters with names rawdata.energy and
	      // rawdata.timestamp.
	      //
	      // \param name  name of parent in tree structure

	      ChannelData();
	      ChannelData(const ChannelData& rhs);
	      void Initialize(std::string name);
	      void Reset();
	      };

	      //____________________________________________________________
	      // Struct for top-level events
	      //
	      struct MyParameters {

	      ChannelData      chan[1000];
	      CTreeParameter   multiplicity;
	      MyPipelineData   data;
	      MyParameters2    example;

	      // Ctor
	      MyParameters(std::string name);
	      // Dtor
	      ~MyParameters(){};
	      // Copy Ctor
	      MyParameters(const MyParameters& rhs);

	      void Reset();
	      };
	      #endif
	    
The most important part is the copy constructor for every structure or class we will define. This ensures that objects are correctly copied for each thread we will instantiate. MyPipelineData is a special structure that contains STL vectors. I highly suggest to keep these separated from the CTreeParameters. Nesting classes in a logical data structure according to tasks is a good approach for multithreading programming. Similar structure constructions can be observed for MyPipelineData and MyParameters2.

The core of your analysis pipeline is your MyCalibrator. Let's look at it more in details:

Example 3-10. Definition of MyCalibrator.h


	      #ifndef __MYCALIBRATOR_H
	      #define __MYCALIBRATOR_H

	      #include <EventProcessor.h>
	      #include <TranslatorPointer.h>
	      #include <TCLAnalyzer.h>

	      class MyParameterMapper;

	      class MyCalibrator : public  CEventProcessor
	      {
	      public:
	      MyParameterMapper* m_pParameterMapper; (1)

	      MyCalibrator(); (2)
	      MyCalibrator(const MyCalibrator& rhs);
	      ~MyCalibrator();
	      virtual MyCalibrator* clone() { return new MyCalibrator(*this); }

	      void setParameterMapper(DAQ::DDAS::CParameterMapper& rParameterMapper); (3)

	      virtual Bool_t operator()(const Address_t pEvent,  (4)
	      CEvent&         rEvent,
	      CAnalyzer&      rAnalyzer,
	      CBufferDecoder& rDecoder,
	      BufferTranslator& trans,
	      long thread);
	      };
	      #endif
	    
(1)
MyParameterMapper is the common interface class that handles the mapping between channels and data structure. Just for information, here the definition of the public members:

Example 3-11. Definition of MyParameterMapper.h


		    ...
		    class MyParameterMapper : public DAQ::DDAS::CParameterMapper
		    {
		    public:
		    
		    MyParameters  m_params;           // reference to the tree parameter structure
		    std::map<int, int> m_chanMap;     // global channel index for crates
		    ...
		  
As descibed above, this file doesn't need to be modified.
(2)
This is a very important part of the thread-safe code for the processor. We need an explicit declaration of the class constructor, copy constructor, destructor, and clone method. If one does NOT implement correctly the class, failure will occur at compilation time.
(3)
The registration and setting of the parameter mapping is crucial for the success of Parallel SpecTcl.
(4)
If compared to normal SpecTcl, the function operator requires extra arguments to for the proper definition of our multithreading program.

For the implementation of MyCalibrator class:

Example 3-12. Definition of MyCalibrator.cpp


	      #include <ThreadAnalyzer.h>
	      #include "MyCalibrator.h"
	      #include "MyParameterMapper.h"
	      #include "MyParameters.h"
	      #include <ZMQRDPatternClass.h>

	      MyCalibrator::MyCalibrator()
	      {}

	      MyCalibrator::MyCalibrator(const MyCalibrator& rhs)
	      {}

	      MyCalibrator::~MyCalibrator() {
	       delete m_pParameterMapper;
	      }

	      void
	      MyCalibrator::setParameterMapper(DAQ::DDAS::CParameterMapper& rParameterMapper) (1)
	      {
	       m_pParameterMapper = reinterpret_cast<MyParameterMapper*>(&rParameterMapper);
	      }

	      Bool_t
	      MyCalibrator::operator()(const Address_t pEvent,
	      CEvent&         rEvent,
	      CAnalyzer&      rAnalyzer,
	      CBufferDecoder& rDecoder,
	      BufferTranslator& trans,
	      long thread)
	      {
	       auto& params = m_pParameterMapper->m_params; (2)

	       // loop over hits
	       for(int i= 0; i<params.data.m_chanHit.size(); i++){   (3)

 	          int id = params.data.m_chanHit[i];
	          double ran = ( static_cast<double>(rand())) / (static_cast<double>(RAND_MAX));

    	          if( id == 341) {   
	           if (rEvent[params.chan[id].energy.getId()].isValid()){
	           params.example.ex1.energy = (params.chan[id].energy + ran) + 0.0;
	           params.example.ex1.ecal = params.example.ex1.energy*params.example.var.var1.c1; 
	          }
	          if (rEvent[params.chan[id].timestamp.getId()].isValid()) 
	           params.example.ex1.time = params.chan[id].timestamp + 10.0;
	         }
      	       }
	       return kfTRUE;
	      };
	    
(1)
The four canonical constructor, destructor, copy constructor, and clone methods have to be standard for every event processor an user wants to define. The setParameterMapper definition is fundamental for the correct copy of the parameter mapper to each worker thread.
(2)
This line dictates your entry point to access the data structure you defined in MyParameters.h. Your params object is the top tree of your nested structure. If ones looks back at Figure 3-1, can see the actual structure and how to access each component (in red).
(3)
Example of simple loop over hits. This is pretty self explanatory. One can see how the data structure has been linearized to access the members.

Once you've edited everything, use make to build the shared object and don't forget to add it to the Makefile.