The basic structure of an analysis task

In the analysis framework, all analysis tasks are written in a specific format that then gets converted into a processor within Data Processing Layer (DPL). This allows for a convenient way of taking advantage of the features of DPL without having to write a full processor manually: the only requirement is to follow a recipe for writing a task, similarly to what was done in AliPhysics. A task is defined with

struct MyTask : AnalysisTask {
};

Such a task can then be added to a workflow via the adaptAnalysisTask helper. A full blown example can be built with:

#include "Framework/runDataProcessing.h"
#include "Framework/AnalysisTask.h"

struct MyTask : AnalysisTask {
};

defineDataProcessing() {
  return {
    adaptAnalysisTask<MyTask>(TaskName{"my-task-unique-name"});
  };
}

Implementation details: AnalysisTask is simply a struct. Since struct default inheritance policy is public, we can omit specifying it when declaring MyTask.

AnalysisTask will not actually provide any virtual method, as the adaptAnalysis helper relies on template argument matching to discover the properties of the task. It will come clear in the next paragraph how this allow is used to avoid the proliferation of data subscription methods.

Minimum requirements for a complete task

  • License agreement
  • Required header files
  • O2 basic name spaces
  • Struct comprising an init and a process function
  • Defined Workflow

See also tutorial Analysis Task.

Tip

Sometimes it’s handy to perform an action when all the data has been processed, for example executing a fit on a histogram we filled during the processing. This can be done by implementing the postRun method.

Creating histograms

Typically, in analysis, histograms have to be declared and filled with relevant information. You can do so by using the Histogram helper:

struct MyTask : AnalysisTask {
  Histogram etaHisto;

  void process(o2::aod::EtaPhi const& etaphi) {
    etaHisto.fill(etaphi.eta());
  }
};

The histogram Registry

The histogram registry is a class to create and manage histograms in a consistent and optimized way.

The constructor of the histogram Registry has five arguments

HistogramRegistry(char const* const name,
                  std::vector<HistogramSpec> histSpecs = {},
                  OutputObjHandlingPolicy policy = OutputObjHandlingPolicy::AnalysisObject,
                  bool sortHistos = true,
                  bool createRegistryDir = false)

name is a unique identifier of the registry. histSpecs is a vector of HistogramSpec, which contain the specifications of the histograms. The argument policy can take two values, either OutputObjHandlingPolicy::AnalysisObject or OutputObjHandlingPolicy::QAObject. The histograms managed by a HistogramRegistry are automatically saved to file. AnalysisObjects are written by default to the root file AnalysisResults.root, whereas QAObjects are written into QAResults.root. If the argument sortHistos is set true then the histograms are written in alphabetical order to the output file, and if createRegistryDir is set true then a dedicated directory is created to hold all the histograms contained in the registry.

A histogram is defined with a HistogramSpec. Its constructor has four arguments.

HistogramSpec(char const* const name,
              char const* const title,
              HistogramConfigSpec config,
              bool callSumw2 = false)

The argument HistogramConfigSpec contains the details of the histogram. If callSumw2 is set true then the Sumw2 method of the respective histogram is executed when it is created.

The constructor of a HistogramConfigSpec has three arguments.

HistogramConfigSpec(HistType type,
                    std::vector<AxisSpec> axes,
                    uint8_t nSteps = 1)

HistType specifies the type of the histogram. The supported histogram types are listed in HistogramSpec.h. The vector of AxisSpec describes the axes of the histogram. nSteps is only used for histograms of type StepTHn.

Histogram axes are realized by AxisSpec which has two constructors.

AxisSpec(std::vector<double> binEdges,
         std::optional<std::string> title = std::nullopt,
         std::optional<std::string> name = std::nullopt)

AxisSpec(int nBins,
         double binMin,
         double binMax,
         std::optional<std::string> title = std::nullopt,
         std::optional<std::string> name = std::nullopt)

They differ in the way the axis bins are defined. In the first version a vector of bin edges is provided, which allows bins of different widths, whereas in the second case the edges of the equally wide bins are computed with the provided number of bins and the range of the axis, defined by binMin and binMax.

By-the-way, there is in fact a third version of the AxisSpec constructor, which is similar to the first version, but takes as first argument a ConfigurableAxis (= Configurable<std::vector<double>>) instead of a std::vector<double>.

Adding histograms

A HistogramRegistry can be created together with the histograms it contains. It can however also be created empty and the histograms can be added later with the add method of which there are three versions.

void add(const HistogramSpec& histSpec);

void add(char const* const name,
         char const* const title,
         const HistogramConfigSpec& histConfigSpec,
         bool callSumw2 = false);

void add(char const* const name,
         char const* const title,
         HistType histType,
         std::vector<AxisSpec> axes,
         bool callSumw2 = false);

Filling histograms

HistogramRegistry has a fill method to update the histograms. There are two versions and both are templated.

template <typename... Ts, typename H>
void fill(const H& histName,
          Ts&&... positionAndWeight)

template <typename... Cs, typename T, typename H>
void fill(const H& histName,
          const T& table,
          const o2::framework::expressions::Filter& filter)

In both cases histName represents the name of the histogram. In fact to address the histogram which was defined with name = histname one needs to supply HIST("histname") as the first argument to the fill method.

positionAndWeight is a comma separated list of values to fill into the histogram. There must be n or n+1 values, where n is the number of histogram axes. A weight can be given as last number in the list.

The second variant of fill method allows to copy filtered values from a table into a histogram.

Accessing histograms

The get method allows to access a histogram contained in a HistogramRegistry.

template <typename T, typename H>
std::shared_ptr<T>& get(const H& histName)

Again HIST("histname") must be provided as argument to get the histogram with name = histname.

Practical examples of histogram manipulations in O2 can be found in the tutorials Histograms and Histogram Registry.