CERN開発の解析ツールROOTで自作クラスを扱う


はじめに

ROOTはCERN(欧州原子核研究機構)によって開発されたC++ベースのデータ開発フレームワークで、素粒子・原子核・宇宙実験で特に使用されています。

ROOTにはヒストグラムやグラフといったデータを扱うクラスだけでなく、SQLデータを読み込むクラスやXMLファイルをパースするクラスに至るまで多くのクラスがあり、実験を行う上で必要な道具がたくさん入っています。

ROOT ホームページ

何をやりたいのか?

  • 自作のクラスをROOTファイル(*.root)に保存してみたい。

  • このクラスを使ったROOTマクロを作りたい。

ここら辺についてはROOTの公式ドキュメントに書かれているのですが、私にとっては難しかったのでいくつかの日本語の記事を参考にしつつ自前で少しまとめてみました。

やり方

ここでは、例として"THistory"という自作のクラスを作ってみます。
誤差つきのグラフを扱うTGraphErrorsクラスを継承し、適当なところを基準にScaleする機能を持たせたシンプルなクラスです。

自作クラスの中身

まず、クラスそのものを記述するTHistory.hとTHistory.cppを書きます。
(コードはかなり適当なのである程度読み飛ばしてください)

THistory.h
#include <TObject.h>
#include <TGraphErrors.h>

class THistory: public TGraphErrors
{
public:
   THistory();
   THistory(Int_t N);

   void NormalizeAt(Int_t index=-1);
   void Scale(Double_t c1, Double_t c1err);
   THistory* Clip(Double_t xmin,Double_t xmax);

   ClassDef(THistory,1);
};

THistory.cpp
#include "THistory.h"
#include <TObject.h>
#include <TMath.h>
#include <iostream>

ClassImp(THistory);

Double_t DivisionError(Double_t num,Double_t numerr,Double_t den,Double_t denerr){
   return TMath::Sqrt(TMath::Power(numerr/den,2)+TMath::Power(denerr*num/(den*den),2));
}

Double_t ProductionError(Double_t num1,Double_t num1err,Double_t num2,Double_t num2err){
   return TMath::Sqrt(TMath::Power(num1err*num2,2)+TMath::Power(num2err*num1,2));
}

THistory::THistory() {};
THistory::THistory(Int_t N)
{
   this->Set(N);
};
// void Draw(TString chopt=""){this->TGraphErrors::Draw("ap");};

void THistory::NormalizeAt(Int_t index)
{
   if(index < 0) {
      // Find the earliest point
      index = TMath::LocMin(this->GetN(), this->GetX());
   }
   Double_t minx, miny, minxerr, minyerr;
   miny = fY[index];
   minyerr = fEY[index];
   this->Scale(1./miny,DivisionError(1,0,miny,minyerr));
}
void THistory::Scale(Double_t c1, Double_t c1err)
{
   // Scale each element of history by c1 with error c1err.
   for(Int_t i = 0; i < fNpoints; i++) {
      Double_t ytmpval = fY[i];
      Double_t ytmperr = fEY[i];
      this->SetPoint(i, fX[i], ytmpval * c1);
      this->SetPointError(i, fEX[i], ProductionError(ytmpval,ytmperr,c1,c1err));
   }
}

ここでのポイントは、

  • クラス定義の末尾にClassDef(<Class Name>, <Class Version ID>)をいれる
  • クラスの関数を実装する前にClassImp(<Class Name>)をいれる
  • デフォルトコンストラクタ(引数を取らないコンストラクタ)を必ず定義する

というあたりでしょうか。
Class Version IDに関してはあまり理解できていませんが、公式ドキュメントによればクラスのデータメンバを変更したらインクリメントする必要があるそうです。

Dictionaryを作る

このように自作クラスが定義できたらDictionaryを作ることが必要です。
それには次のようなLinkDef.hが必要になります。(あまり中身はわかっていませんが。)

LinkDef.h
#ifdef __CINT__
#pragma link C++ class THistory+;
#endif

これらを用いて、rootcintコマンドでDictionary(この場合はTHistoryDict.cpp)を作成します。

rootcint -f THistoryDict.cpp -c THistory.h LinkDef.h

次に、このTHistoryDict.cppを用いて

g++ `root-config --cflags --libs` -shared THistoryDict.cxx THistory.cpp -o libthisotry.so

と-sharedフラグを指定して共有ライブラリを生成します。
(root-config --cflags --libsは必要なrootのライブラリをとってくるコマンドです。)

こうして生成したライブラリはROOTのインタラクティブシェル上では

.L libthistory.so

としてロードすることができ、その後はROOTのクラスと同様に使用することができます。

自作クラスをマクロ内で使用する

次のようなマクロの中で自作クラスを使用することを考えます。

thistory_test.cpp
#include "TFile.h"
#include "TTree.h"
#include "TMath.h"
#include "THistory.h"
#include "THistory.cpp"

void SaveTHistory()
{
   TFile* fout = new TFile("test.root", "recreate");
   THistory* h = new THistory();
   Int_t Nhist = 100;
   Int_t initval = 10;
   for(Int_t i = 0; i < Nhist; i++) {
      h->SetPoint(i, i, initval * TMath::Exp(-0.1 * i));
   }
   h->NormalizeAt(-1);
   fout->cd();
   h->SetName("history");
   h->Write();
   fout->Close();

}

int main()
// void thistory_test()
{
   SaveTHistory();
}

このマクロには二つの使い方があります。
一つはコンパイルして使用する方法。

g++ -o thistory_test thistory_test.cpp \ 
`root-config --cflags --libs` -lthistory -L./

この時、-lthistorylibthistory.soを指しており、-L./はこの共有ライブラリがあるパスを指しています。
以上を踏まえて次のようなMakefileを作れば、make thistoryからのmakeで同様に実行ファイルを作成することができます。

Makefile
CXX           = g++
CXXFLAGS      = -O2 -pipe -Wall -W -Woverloaded-virtual

ROOTCONFIG = root-config
ROOTCFLAGS:= $(shell $(ROOTCONFIG) --cflags)
ROOTLIBS  := $(shell $(ROOTCONFIG) --libs)
ROOTGLIBS := $(shell $(ROOTCONFIG) --glibs)

CXXFLAGS+= $(ROOTCFLAGS)
CXXFLAGS+= -g
LIBS     = $(ROOTLIBS)


test: thistory_test.cpp
    $(CXX) -o thistory_test $(CXXFLAGS) $(LIBS) $^ -lthistory -L./

thistory:
    rootcint -f THistoryDict.cpp -c THistory.h LinkDef.h
    $(CXX) `root-config --cflags --libs` -shared THistoryDict.cxx THistory.cpp -o libthisotry.so

もう一つの方法は、ROOTのインタラクティブシェルでロードしてから実行する方法です。
thistory_test.cppのmain関数をコメントアウトして、代わりにthistory_test関数を定義してから、次のようにすれば実行することができます。

.L libthistory.so
.x thistory_test.cpp

マクロの実行結果

保存したROOTファイルの中身を見ると、

root -l test.root
root [1] .ls
TFile**     test.root
 TFile*     test.root
  KEY: THistory history;1

THistoryクラスのオブジェクトが保存されているのがわかります。

root [2] .L libthistory.so
root [3] history->SetMarkerColor(kRed)
root [4] history->SetMarkerStyle(20)
root [5] history->Draw("ap")

ライブラリをロードしてからこのhistoryを描画すれば、次のように最初の点で1にNormalizeされた指数関数グラフが描画されるはずです。

まとめ

  • ROOTで自作クラスをROOTのクラスと同様に使用するためには、Dictionaryをrootcintで作ってコンパイルする必要がある。
  • コンパイルした自作クラスはマクロ内でも、インタラクティブシェル内でも使用することができる。

参考文献

pn11 さんのgist
ROOT Adding a class
kamono wiki
C++の共有ライブラリの作成と利用