ユーザ用ツール

サイト用ツール


サイドバー

Menu

Latest

study:software:root:ntuple

目次

Ntuple

猿ルートによるとNtupleはsingle,double,triple,quadruple,quintuple,sextuple,septuple,octuple,nonuple,decuple· · ·の n 番目という意味で、エヌタプルまたはエヌチュープルと読む。

タプルというのは要素の組を表す概念。

ROOTではNtupleといった場合はTTreeのことを指すと思って問題ない(Ntupleはpawにもあるが、treeはc++用にpawのNtupleを拡張したものという感じ)。

ちなみにTNtupleというクラスがあるが、TTreeの下位互換で、float型に限定されたTTreeである(TNtupleDはdouble型のみを扱える)。

TTree

まず最初にTTreeを調べる便利なメソッドたちDraw・ヒストグラムを描くで、予め手元に用意されたTTreeの中身を調べるメソッドについて書いた。

正味のTTreeの使い方についてはTTreeによる解析にある。ただし、ネット上にはもっとわかりやすい解説がたくさんあるので、他を参照した方がいいと思う。

TTreeとは

TTreeは変数データやオブジェクトをひとまとめにして管理するクラスである。何らかの物理データをROOTで使いやすい形に保存しておくために使う。

例えば、何かの実験値を記録したテキストデータファイルがあったとき、そのデータをヒストグラムに直接いれて解析をする…というのもありうるが、

いかにも面倒だし、何度もそのデータをいろんな方法で解析するにはあまり効率がよくない。我々のするべき作業はある程度決まっているし、root上でデータを完結できたほうがメリットは多い。


TTreeでは変数データはbranchというものに納められ、我々はTTreeのbranchを参照することで、TTreeの中の欲しいデータにアクセスできる。

メリット

各種データをtree構造でまとめるだけなら、適当なコンテナを使うのと大して変わりないと思うかもしれないが、以下のようなメリットがある。

  • いくつか有用なメソッドが予め用意されている
    • ヒストグラムに詰めなおさなくても、そのままDrawできる
      • しかも別の変数を条件式として使ったり、変数同士を演算したヒストグラムも一瞬で作れる
    • 変数同士の中身をターミナルに出力して比較ができる etc…
  • 各Entryごとに解析することが簡単
  • 興味のある変数の特定の値だけを取り出すことができるのでメモリも節約できるし、処理速度も速い。
  • その他、std::vectorやROOTの用のクラスもbranchにいれられるので、応用範囲が広い

イメージ

イメージ的には各Branchに収められた変数データはExcelデータに横一列に並べたような状態になる(使ったこと無いのでよく知らないけどSQLみたいな感じって言えばいいのか)。

ただ、Excelの場合、一つのセルには一つのデータしかいれられないが、TTreeの場合、配列だろうが、構造体だろうが、クラスだろうが、あらゆるオブジェクトをbranchにいれることができる。夢は広がる。

下はTTreeをExcel的なイメージで書いてみた。このTTreeにはa,b,cというbranchがある。

Excel的なTTreeのイメージ
int a int b vector<int> c
Entry 1 4 2 要素0 5
要素1 3
要素2 2
Entry 2 2 0 要素0 9
要素1 0
Entry 3 4 -3 要素0 5
要素1 -7
要素2 1
要素3 16

TTreeの1Entryを1イベントに対応させて使うことが多いので、各イベントごとに解析するということがとても簡単になる

TTreeを調べる便利なメソッドたち

TTreeを実際に作成する方法を後回しにして、まずTTreeの入ったrootファイルがある状況を想定して、それにどういうデータが入っているかを調べるメソッドを書く。

GetEntries・treeのEntry数を調べる、条件を満たすEntry数を調べる

下のようにtreeの全Entry数がわかる。

[ ] tree->GetEntries()
(Long64_t)10000

またある条件を満たすEntry数の数も下のように調べられる。とても便利。

[ ] tree->GetEntries("jet_pt > 50*1000")
(Long64_t)8931

ちなみにDrawやScanも条件を指定した場合は同様にEntry数が返り値になっている。

Scan・変数を走査する

下では例として、手元にあったrootファイルでいくつかの変数をScanで表示した。

el_nはint型、el_pt,el_eta,el_phiはvector<float>型。RowっていうのがEntryに対応していて、instanceがvectorのelementに対応している。

root [0] tree->Scan("el_n:el_pt:el_eta:el_phi");
***********************************************************************
*    Row   * Instance *      el_n *     el_pt *    el_eta *    el_phi *
***********************************************************************
*        0 *        0 *         1 * 5166.8374 * 1.0805516 * 1.8123858 *
*        1 *        0 *         5 * 20541.097 * 1.2024279 * 0.6764502 *
*        1 *        1 *         5 * 14151.347 * 1.3263747 * 0.4326341 *
*        1 *        2 *         5 * 2475.8696 * 0.5573488 * -1.052149 *
*        1 *        3 *         5 * 2657.1799 * 1.0578159 * 0.6876739 *
*        1 *        4 *         5 * 5691.4233 * 4.0138554 * 0.0908878 *
*        2 *        0 *         8 * 9473.3232 * 0.7828827 * -1.211876 *
*        2 *        1 *         8 * 6606.4653 * -1.311744 * 0.5083770 *
*        2 *        2 *         8 * 4374.0971 * -1.136177 * 0.5089252 *
*        2 *        3 *         8 * 2030.7614 * -1.271750 * 0.5521807 *
*        2 *        4 *         8 * 3286.9755 * -1.192063 * 0.4581041 *
*        2 *        5 *         8 * 10785.448 * -2.971761 * -2.281221 *
*        2 *        6 *         8 * 12366.143 * -2.965951 * -2.307743 *
*        2 *        7 *         8 * 8788.3554 * -2.788091 * -2.159878 *
*        3 *        0 *        15 * 41173.386 * 0.8645746 * -1.292289 *
...

tree->SetScanField(30); //一度に表示する数。デフォルト値は50。0にすると一度に全て表示する
tree->Scan("var1:var2","(条件式)","(オプション)",表示する最後のEntry,表示する最初のEntry);
//例
tree->Scan("var1","var2>var3"); //var2>var3が満たされているrowのみ表示
tree->Scan("var1*var2"); //変数の演算も可能
tree->Scan("var1:var2","","lenmax=5 colsize=10",6,2); //lenmaxは表示する最大のinstanceの数、colsizeはcolumnの文字数。デフォは9

オプションについてはよくわからないので、詳しくは下を参照。

http://root.cern.ch/root/html/TTreePlayer.html#TTreePlayer:Scan


ちなみにScanやShowで得られた情報をテキストにダンプしたいという欲求が起こるかもしれない。

rootでは下のようにリダイレクトがサポートされているので、それを使うといいと思う。

[ ] tree->Scan("var1"); > out.text

Show・あるEntryの変数の値を一覧する

ShowはあるEntryのすべてのbranchに入っている変数の値を表示できる。

tree->Show(表示したいEntry);

Print・treeの情報を表示する

エントリー数、ブランチやリーフの数、ファイルサイズなんかを表示できる。

tree->Print();

StartViewer・ツリービューワを使う

guiでtreeを表示したり、任意の変数をいくつか選んで、2,3次元ヒストグラムを作成したり、なんてこともできる。

root [] tree->StartViewer();

または、TBrowserで、treeのところで右クリックでも呼び出すことが出来る。

root [] TBrowser b;

Draw・ヒストグラムを描く

http://root.cern.ch/root/html/TTree.html#TTree:Draw@2

treeのDrawはやたらと強力である。何が強力かって、複雑な演算を処理したとしても高速でDrawが可能である。

tree->Draw("var1");
tree->Draw("var1:var2","(条件式)","Drawのオプション",Entryの上限,Entryの下限);

  • :で区切ると多次元ヒストグラムにできる
  • branchがvectorでもヒストグラムにできる

変数を演算して描く

tree->Draw("var1*var2");
tree->Draw("sin(var1)"); //関数を呼び出したり
tree->Draw("TMath::Max(var1,var2)"); //TMathを呼び出すことも当然できる

条件をいれて描く

下のように書くと、条件式が真であるようなrowのみをヒストグラムに詰めることができる。

tree->Draw("var1","var2>100");

さらに論理値そのものを値として使うこともできる。下ではvar2<20ときは99、var2>=20のときはvar1を詰めることになる。

tree->Draw("(var2<20)*99+(var2>=20)*var1");

つまり(var2<20)は真のときは1、偽のときは0をとる。いや、あらためて言うまでもないのだが。

条件式にTCutを使う

長い条件式を毎回書くのは面倒だが、TTreeの条件式を代入しておけるクラスが用意されている。 いたれりつくせりつーかなんというか。

TCut c1 = "x<1";
TCut c2 = c1+"y>3";
tree->Draw("x", c1&&c2);
tree->Draw("x", c1||"x>-1");
tree->Draw(c1*"x");

配列の特定要素をDrawする、条件式に使う

配列やvectorなどをDrawする場合。

2次元配列を例にして

int box[2][2]だとする。

tree->Draw("box")

box[0][0],box[0][1],box[1][0],box[1][1]

tree->Draw("box[][1]")

box[0][1],box[1][1]

tree->Draw("box[0][1]")

box[0][1]

tree->Draw("box[][1] - box2[][]")

box[0][1] - box2[0][0],box[1][1] - box2[0][0],box[0][1] - box2[0][1],box[1][1] - box2[0][1],….,box[1][1] - box2[1][1]

vectorまたは可変配列

vectorはentryによって、要素数が違う。でなければvectorを使う意味が無い。

[ ] tree->Draw("v.at(0)");
Error in <TTreeFormula::Compile>:  Part of the Variable "v.at(0)" exists but some of it is not accessible or useable

そのため、上のようにatや[]で普通にアクセスしてしまうと要素数が足りていない場合はエラーが返される。

[ ] tree->Draw("Alt$(v,0)");

上のようにすると、v[0]が存在するときはその値、存在しなければ0を返す。

よくわからないけど、今のrootはたぶんatや[]でアクセスできるみたい

Drawして生成するヒストグラムのbin幅などを変更する

TTreeでDrawした場合、自動的にビン幅やtitleを決めてヒストグラムが生成されるが、その設定を変えたい場合もある。

TTreeはDrawすると同時にhtempという名前のTH1Dを生成するので、下のようにhtempにアクセスすることで設定を変更できる。

tree->Draw("var1");
htemp->SetTitle("title"); 

上ではオブジェクト名をもってアクセスしているが、もしcintを使わないのならば、きちんとgROOTを使ってアクセスする必要がある。

また、予めヒストグラムを作っておいて、それにリダイレクトすることもできる。

TH1D *h1 = new TH1D("h1","title",ビンの数,下限.,上限.);
h1->SetXTitle("xtitle");
tree->Draw("var1>>h1");

実は、ヒストグラムをわざわざ宣言しておかなくても、リダイレクトするだけで自動的にヒストグラムが生成される。

tree->Draw("var1>>h1(ビンの数,下限.,上限.)");

この場合、nameはh1のヒストグラムが生成される。アドレスは

TH1F *h = (TH1F*)gROOT->FindObject("h1");

で得ることができる。

TTreeの作り方

新しくTreeを作るときの流れ

実際にtreeにデータを入れる方法を書く。

例としてInt_t val1,Double_t val2,Float_t val3のBranchが入ったtreeを作るサンプルマクロ。

TFile *file = new TFile("file.root","recreate"); //新規rootファイルを生成
TTree *tree = new TTree("tree","tree");

Int_t a;
Double_t b;
Float_t c;

tree->Branch("val1", &a,); 
tree->Branch("val2", &b); 
tree->Branch("val3", &c); 

a = 3; b = 1.22; c=0.34;
tree->Fill();
a = 1; b = 2.4; c=1.33;
tree->Fill();
/*
実際には上のように一つ一つ値をFillすることはまずないと思うけど。
*/ 

tree->Write();

以下、説明。

(1)TTreeオブジェクトを作る

TFile *file = new TFIle("hoge.root","recreate"); //TTreeを作る前にrootファイルを必ず生成しておく
TTree *tree = new TTree("tree","tree");

TTreeの第一引数はオブジェクト名、第二引数はtitle。TTreeの場合はtitleはほとんどどうでもいいが、オブジェクト名はとても重要。

(2) Branchを作る

Int_t a; //Branchに間接的に値を代入するための変数をあらかじめ宣言する
Double_t b; //一時的に使うだけなので、名前はなんでもいい。がややこしいので普通はBranchの名前と同じにする。こんな書き方は普通はしない。
Float_t c;
tree->Branch("val1", &a);
tree->Branch("val2", &b);
tree->Branch("val3", &c);

(3)Treeに値を詰める

下のようにしてBranchにデータを入れることが出来る。

a = 3; b = 1.22; c=0.34;
tree->Fill(); //Fillすると、すべてのBranchに値が詰められる
a = 1; b = 2.4; c=1.33;
tree->Fill(); //次のrowに値が詰められる
...


今、この状態でScanコマンドを使うと

root [] tree->Scan("val1:val2:val3")
************************************************
*    Row   *      val1 *      val2 *      val3 *
************************************************
*        0 *         3 *      1.22 * 0.3400000 *
*        1 *         1 *       2.4 * 1.3300000 *
************************************************

となる。

(4)Treeを保存する

//TTreeを作る前にTFileを作る
TFile *fout = new TFile("hoge.root", "recreate"); //もともとhoge.rootがディレクトリにあると上書きされるので注意

/*TTreeを作る*/

tree->Write();
fout->Close();

//TTreeを作る前にTFileを作る
TFile *fout = new TFile("hoge.root","update");

/*TTreeを作る*/

tree->Write();
fout->Close();

TTreeを作る前には必ずTFileを作っておいて、そこでTTreeをFillする必要がある。

TTreeはある一定のサイズを超えるとAutoSaveされるらしく(仕組みは不明)、そのときにTFileがないとerrorが出る。

つまりFillした時点で保存されることがあるわけで、そのあとでwriteすると二重に保存されてしまう。

テキストデータをダンプしてBranchを作る

Branchを作る方法さえわかれば、テキストデータからBranchを作るのは、c++の知識さえあればできるはず。

//ファイルの入力
std::ifstream fin;
std::string filename;
fin.open(filename.c_str()); //データファイルを読み込む
if(fin.fail()){// エラー処理を書いておく方がいい
  cerr << "can't open file : " << filename << endl;
  return 1;
}

//treeにbranchを作り、ループでデータをいれていく
float val1,val2,val3; //いれたいデータの数だけ変数を宣言する
TTree *tree = new TTree("tree","tree"); //第一引数はオブジェクト名、第二引数はタイトル
tree->Branch("val1",&val1,"val1/F");
tree->Branch("val2",&val2,"val2/F");
tree->Branch("val3",&val3,"val3/F");

while (fin >> val1 >> val2 >> val3){
  tree->Fill();
}
//whileループが終わると、treeにデータがダンプされる

//ファイルの保存
tree->SaveAs("hoge.root"); //treeを保存する
fin.close();

実は上のようなことをしなくても、treeにはデータをダンプするメソッドがあって、これを使えば三行で終わる。

TTree *tree = new TTree("tree", "tree");

tree->ReadFile("hoge.data", "val1:val2:val3/I:val4:val5");
//ちなみに変数型を省略すると直前の変数と同じ型、前に変数がなければFloat_tと同じになる。この場合val1,val2はFloat_t型、val3,val4,val5はInt_t型になる。

tree->SaveAs("hoge.root");

いろんなBranchを作る

先程の例ではBranchはすべて組み込み型だけだったけど、任意のクラスのBranchも作ることができる。

構造体

関係の深い変数同士(例えば4vectorの成分とか)は構造体にしてひとまとめにしたくなるかもしれない。

struct str{
    Int_t var1;
    Float_t var2;
}

str s; s.var1=3; s.var2=4.455;

TTree *tree = new TTree("tree","tree");
tree->Branch("s",&s,"var1/I:var2/F")
tree->Fill();

三番目の引数はメンバーの型を書く。もしも省略すれば一つ前の変数と同じ型と判断される。また先頭で省略した場合はFloat_tと判断される。例えば

"var1:var2:var3/I:var4"

ならば、va11,var2はFloat_tで、var3,var4はInt_tになる。

C: 0文字で終了する文字列
B: 8ビットの符号付きの整数
b: 8ビットの符号なしの整数
S: 16ビットの符号付きの整数
s: 16ビットの符号なしの整数
I: 32ビットの符号付きの整数
i: 32ビットの符号なしの整数
L: 64ビットの符号付きの整数
l: 64ビットの符号なしの整数
F: 32ビットの浮動小数点数
D: 64ビットの浮動小数点数

TTreeはTBranchのListだが、実はTBranch自体もTLeafのListである。基本の変数型をBranchにした場合はTLeafは一つなのだが、Branchを構造体にした場合はそのメンバー変数がそれぞれのBranchのLeafになる。

(たぶん)TTreeで参照している変数の最小単位がTLeaf(例えばShowで一覧しているのはTLeaf)で、TBranchはそれらの変数を一纏めにしてアドレスをやり取りするための箱といった概念だと思う。

でも実際にはTBranchに構造体を使うことはそれほど多くはないので、TBranchとTLeafは概念的に同一視されがち(もちろんクラスとしては階層構造なので全然違うのだけど)。

配列

int a[3] = {3,6,7}
tree->Branch("a",a,"a[3]/I")

  • 配列の名前は先頭要素のアドレスを示すので上のようになる

Branchが配列やstd::のコンテナクラスはScanした場合、下のように要素を一覧できるので便利である。

root [] tree->Scan()
***********************************
*    Row   * Instance *         a *
***********************************
*        0 *        0 *         3 *
*        0 *        1 *         6 *
*        0 *        2 *         7 *
***********************************

std::vector

vectorは下のように、

std::vector<double> *v=0;
tree->Branch("v",&v);

とするだけで、Branchが作れる(Branchするとvectorのインスタンスがvに代入されるらしい)。

ポインタでなくても、実体として宣言しても(たぶん)同じ。

std::vector<double> v;
tree->Branch("v",&v);

vector以外の任意のクラスについては自分でDictionaryを作る必要がある。

TTreeによる解析

データを取り出して、解析をする

TTreeに入ったデータを使用して実際に解析を行う方法を書く。

TTreeのデータは膨大なので全てを一度にメモリに読み込むのは無駄だし、いくらメモリがあっても足らない。

そのため、TTreeではある特定のEntryの値だけを一度にメモリに読みこむという方法をとる。

(つまりはTTreeが得意な解析というのは同じEntryの変数同士を演算すること。数Entryまたいで異なる変数を演算するとなるとおぞましいことになる。その場合、もう別のクラスを開発した方がいい気がする)


例として、下にTTreeに入っているaというBranchの値を全Entryでloopしてprintfするマクロを示す。

int a = 0;
tree->SetBranchAddress("a",&a);
int nEntry = tree->GetEntries(); //treeのEntry数を取得して代入する
for(int iEntry=0;iEntry<nEntry;++iEntry){ //すべてのEntryでループをまわす
  tree->GetEntry(iEntry);
  printf("%d\n",a);
}

下に各メソッドの説明をつけておく。

(1)SetBranchAddress・変数のアドレスをブランチに渡す

int a = 0;
tree->SetBranchAddress("Branchの名前",&a)
double b[10] = {0};
tree->SetBranchAddress("Branchの名前",b)
std::vector<float> *v = 0;
tree->SetBranchAddress("Branchの名前",&v)

SetBranchAddressは各Branchの値を代入するアドレスを指定するメソッド。

次に説明するように、GetEntryをするときに指定されたアドレスに指定されたEntryのBranchの値が代入される。

当たり前だが、あらかじめBranchの名前がわかってないといけない。tree→Show()などで変数の名前を調べる必要がある。

またSetBranchAddressでは入れられた変数と型が一致しているかどうかのcheckは行われない。変数型を間違えてもコンパイルは通るので、気づきにくいバグとなる。

(2)GetEntry・ブランチの値をセットする

GetEntryを使用すると、ブランチにそのEntryでの値が代入される。

通常は下のように全Entryでloopをまわして使う。

for(int iEntry=0;iEntry<nEntry;++iEntry){ 
tree->GetEntry(iEntry);
/*
ここに行いたい解析内容を書く
*/
}

解析用のスケルトンコードを生成する

前述したような、SetBranchAddressしてGetEntryしてloopをまわして解析を行うような手続きは一般的なので、TTreeにはこれを行うスケルトン関数やクラスを自動的に生成するメソッドが存在する。

ここに書かれているメソッドは使わなくても、特に問題はない。

TTreeの解析をするためには必要な変数をすべてSetBranchAddressする必要があるが、もしも大量に変数がある場合ものすごく手間となる。

ここを書き間違えてエラーとか出ると相当いらつく。

このメソッドを使えば、その部分のコードを自動生成できるので、正味のコードに集中することができる。

むしろいちいちSetBranchAddressをしなくてはならないTTreeの仕様こそが問題ありだと思う。

PyROOTではSetBranchAddressをする必要はないので、このメソッドはお役御免である。

PyROOTバンザイ。

MakeCode

このメソッドはすでにobsoleteなので、MakeClassかMakeSelectorが推奨されるが、単純なので私はこれが好き。

でもまあ、あまりオブジェクト指向っぽいやり方ではない。

MakeClass(推奨)

MakeClassで解析を行うためには事前にコードを生成する必要がある。

tree->MakeClass("MyClass");

これでMyClass.C,MyClass.hが生成される(引数なしではtreeの名前で出力される)。

使い方としては、下のようにクラスのメンバー関数を使用する形になる。

[] .L MyClass.C  // ACLiCを使うなら .L MyClass.C+
[] //ACLiCを使った場合は共有ライブラリができるので、二回目以降は.L MyClass.soでもいい
[] MyClass* t = new MyClass();
[] t->Loop();

loop()関数で実際の解析が行われる。生成したマクロでそれぞれやっていることは

MyClass.h クラス定義をし、rootファイルを読み込んで、treeをつくりSetBranchAddresする
MyClass.C 全entryでLoopをまわす関数を定義

MyClass.Cには下のようなメンバー関数が定義されている。実際に解析を行うときはこの関数だけを編集すれば基本的には十分。

コメントアウトで解説を加えておく。

void MyClass::Loop()
{
   if (fChain == 0) return;

   Long64_t nentries = fChain->GetEntriesFast();

   Long64_t nbytes = 0, nb = 0;

   //全entryでfor loopをまわす
   for (Long64_t jentry=0; jentry<nentries;jentry++) {

      //ientryに、TChainの中でjentryを含むtreeの番号をいれる
      Long64_t ientry = LoadTree(jentry);
      //もしもjentryを含むtreeが存在しなければbreak
      if (ientry < 0) break;
      
      //nbにtreeのjentryに含まれるbyte数をいれる
      //nbytesは読み込んだbyte数の合計
      //代入するだけで何もしない
      nb = fChain->GetEntry(jentry);   nbytes += nb;

      //これを使いたいときはMyClass.hのCut()を編集する
      // if (Cut(ientry) < 0) continue;
      
      /*
      ここに解析コードを書く

      例えば、treeにはa,b,cというbranchがあるとすると、MyClassにはa,b,cというメンバー関数が用意されることになる。
      なので
      std::cout <<  a << std::enddl;
      とでも書けば、各Entryのaの値が出力される
      */
   }
}

MyClass.hは基本的には編集する必要はないが、MyClass::Cut()を使いたいなら場合は編集する必要がある。

Int_t MyClass::Cut(Long64_t entry)
{
// This function may be called from Loop.
// returns  1 if entry is accepted.
// returns -1 otherwise.
   return 1;
}

説明が書かれているが、処理をスキップする条件を書き、もしそれが満たされるようならば-1を返すようにして使う。

MakeSelector

MakeClassはSetBranchAddressをするヘッダーと実際に解析を行うソースファイルにわけているわけだが、MakeSelectorはさらにソースファイルをBegin(ヒストグラムを作る等),Process(ヒストグラムに値をFillする等),Terminate(ヒストグラムをDrawする等)の3つの部分にわける。

MakeClassでは生成されるマクロ自体にfileの名前とtreeの名前が記述されるので(自分で書き換えれば別だが)基本的にはファイルごとに毎回コードを生成することを想定している。

MakeSelectorはコードではファイルは特定することはなく、tree自体にコードを読み込ませるので、同じ形式のtreeに流用しやすい点が多少使いやすい。

まず、MakeClassと同様、下のようにしてマクロを生成する

tree->MakeSelector("MySelector")

MySelector.CとMySelector.hが生成される。

これも、MySelector.Cに詳細な使用方法が書かれているので、ほとんど説明はいらないと思う。

[ ] tree->Process("MySelector.C")
[ ] tree->Process("MySelector.C","",1000,0)//0-1000のentryを処理する
[ ] tree->Process("MySelector.C+")//ACLiCを使う

  • MakeSelector.CはMakeClass.Cとは違い、treeのメソッドの中で使用される。
  • MySelector.C,hはTSelectorというクラスを継承したMySelectorを生成している。
  • Processは引数のファイルで定義されたTSelectorクラス(を継承したクラス)のメンバー関数を実行する。
    • たぶん一時オブジェクトを生成して、Begin(),SlaveBegin(),…って実行しているのではないかな

ユーザが編集するのはMySelector.Cで、下の5つの関数を編集する。説明はいらないと思う。

//    Begin():        called every time a loop on the tree starts,
//                    a convenient place to create your histograms.
//    SlaveBegin():   called after Begin(), when on PROOF called only on the
//                    slave servers.
//    Process():      called for each event, in this function you decide what
//                    to read and fill your histograms.
//    SlaveTerminate: called at the end of the loop on the tree, when on PROOF
//                    called only on the slave servers.
//    Terminate():    called at the end of the loop on the tree,
//                    a convenient place to draw/fit your histograms.

MakeSelectorの現実的な利用

Begin()で定義したローカル変数は当たり前だがProcess()では使えない。しかし、上の説明を読むとBeginでヒストグラムを定義してProcessで詰めろと書いてあるが、関数ブロック分かれているからポインタ共有できないじゃん。どうすればいいんだ…。

公式チュートリアルを読むと、クラスのメンバ変数にTH1のポインタを追加して、Beginでnewするように書いてある。

class MySelector : public TSelector {
private :
    TH1F *hist;
public : 
/*
....
*/
}

void MySelector::Begin(TTree * /*tree*/)
{
hist = new TH1F("name","title",100,-10,10);
/*
....
*/
}
Bool_t MySelector::Process(Long64_t entry)
{
hist->Fill(/*何か*/);
}

ヘッダーにはなるべく依存したくないんじゃなかったのか。

ここはrootの特性を生かして、Beginでヒストグラムを定義してProcessでgROOT->FindObjectで再びヒストのポインタを生成する、というのはどうだろうか。

void MySelector::Begin(TTree * /*tree*/)
{
TH1F *hist = new TH1F("name","title",100,-10,10);
/*
....
*/
}
Bool_t MySelector::Process(Long64_t entry)
{
TH1F *hist= (TH1F*)gROOT->FindObject("name");
hist->Fill(/*何か*/);
}

そこはなとなく何か問題が発生しそうな香りがする。あるいは静的変数で宣言すればいいのか。でもそれならメンバ変数でいい気がする。

MakeProxy

使い方をしらない。というか、これは同じようなメソッドなのかどうかも知らない。

高速に解析を行うヒント

TEventList・Eventを抽出する

TEvnetListとTEntryListの違いがよくわかりません。

あまりちゃんと読んでいないけれど、TChainを使った場合、TEventListはTChainの中のそれぞれのTTreeで呼び出されて無駄だが、TEntryListはTChainによって所有されるみたいな感じでしょうか。

なので、TEntryListの方が上位互換なのだろうか。うーん。でもオプションになっているのはなぜ?

解析を行うときにある条件の場合はスキップする(cutをかける)ことは多い。例えばあるtriggerがなっているイベントだけ抽出したり、electronの数が2以上という条件で解析を行うなど。

このような場合、TTreeにGetEntryさせてからその変数の値を評価することになるが、しかし条件がfalseの場合その後の処理は行わないので他の変数の値をメモリに読み込む分だけ時間が無駄である。

TEventListを使えばある条件をみたすEventを予め抽出することできるので、必要最低限の回数だけGetEntryをすればいいことになる。

しかも(ifで条件分岐させる場合に比べ)記述が簡潔になるので、必ずマスターした方がいいと思う。


//TEventListを生成する
tree->Draw(">>elist", "条件式");
TEventList *elist = (TEventList*)gROOT->FindObject("elist");
//tree->GetEventList()でもいいと思う

//GetNはその条件をみたすEventの数を返す
int N = elist->GetN();

for(int i=0;i<N;++i){
  //elist->GetEntry(i)は条件を満たすi番目のevent numberを返す
  tree->GetEntry(elist->GetEntry(i));
}

ちなみにTEventListを作るときはDrawを使うが、グラフィック表示はされないらしい。

あと、TEventListにはオプションで色々種類があるみたいだが、詳しくはしらない。


下のようにすると、EventListに新たにeventを追加できる(+をつけないと新しく作りなおされる)。

tree->Draw(">>+elist","条件式");


TEventListをtreeにsetすると、ScanやDrawなどで自動的にそのeventだけが選ばれて表示される(もっとも、DrawやScanはもともと条件指定が可能なのでそれほど意味は無いが)。

tree->SetEventList(elist);
tree->Draw();
tree->Scan();


その他有用なメソッドを示しておく。

//抽出されたEventを具体的に表示
elist->Print("all");

//そのEntryが含まれていればTrueを返す
elist->Contains(nEntry);

//特定のEntryを追加
elist->Enter(nEntry);

//別のEventListを合成する
elist->Add(list2);

SetBranchStatus・ブランチを無効化する

GetEntryはすべてのBranchの値をバッファに呼び込むわけだけど、使わないBranchを読み込むのはメモリが無駄だし時間もかかる。

下のように使用しないブランチを無効にすることで効率化することができる。

tree->SetBranchStatus("*",0); //すべてのブランチを無効化
tree->SetBranchStatus("branch_name",1); //"branch_name"というブランチを有効化。1はデフォルトなので省略できる。

無効化されたBranchの値はGetEntry()でメモリに読み込まれなくなるので、場合によってはかなり高速になる。

無効化されたBranchにSetBranchAddressしてもエラーは吐かない。

当然、GetEntryしてもアドレスが渡されるわけではないので、アクセスしようとした時点でsegmentation violationになる。

segmentation violationが出る場合はまだましで、初期値がNULLではければそのままプログラムが走ってしまう。気付かずにやっていると本当に悲惨。

無効にしたことを忘れないようにすること。

TTreeをコピーする、skimmingする、slimmingする

CloneTree・treeをコピーする

Treeをコピーする。

TTree *tree2 = tree->CloneTree(nEntries);

0EntryからnEntryまでがコピーされる。引数なしで全Entryをコピー。


このメソッドを使って、TTreeを別のファイルにコピーしてみる(下の例ではClone()を使うのと大して変わらない)。

"origin.root"から"copy.root"へ、"tree"というTTreeをコピーする。

TFile* fin = new TFile("origin.root","read"); //ファイルの読み込み。readはデフォルトなので省略可
TFile* fout = new TFile("copy.root","recreate"); //生成するファイルを定義
TTree* tree_copy = ((TTree*)fin->Get("tree"))->CloneTree();

tree_copy->Write();
fout->Close();

マクロ:treeから一部の変数だけを残して、新しくtreeを作る

SetBranchStatusで無効化されたBranchはCloneTreeではコピーされないことを利用する。

下のマクロの使い方は、まず予め変数のリストを用意して、

$ root 'tridasu.cxx++(rootfileの名前,treeの名前,変数のリスト)'

とすると、引数のrootfileのtreeから変数のリストに記入された変数だけがコピーされたtreeが保存されたcopy.rootが出力される。

#include <string>
#include <iostream>
#include <fstream>
#include <TFile.h>
#include <TTree.h>

int tridasu(const std::string FileName,
            const std::string TreeName,
            const std::string BranchList) {

    //Reading input root file
    TFile *file = new TFile(FileName.c_str());
    if(!file) { // Input file is not found.
        std::cout << FileName.c_str() << " is not found. The pointer of the file is null." << std::endl;
        return 1;
    }

    //tree in the file.
    TTree *tree = (TTree*)file->Get(TreeName.c_str());
    if(!tree) { // Input tree is not found.
        std::cout << TreeName.c_str() << " is not found. The pointer of the tree is null." << std::endl;
        return 1;
    }

    //Reading value list
    std::ifstream fin;
    fin.open(BranchList.c_str());
    if(fin.fail()){// Input file is not found.
        std::cerr << "can't open file : " << BranchList.c_str() << std::endl;
        return 1;
    }

    std::string BranchName;
    tree->SetBranchStatus("*",0);
    while(fin >> BranchName){
        tree->SetBranchStatus(BranchName.c_str());
    }

    TFile* fout = new TFile("copy.root","recreate");
    TTree *NewTree = ((TTree*)file->Get(TreeName.c_str()))->CloneTree();
    NewTree->Write();
    fout->Close();
    
    return 0;
}

CopyTree・条件をみたしたEntryをコピーする

tree->CopyTree("条件式");

もしも条件を入れない場合(そのままコピーしたいなら)

tree->CopyTree("1");

条件指定してコピーすること以外はCloneTreeと同じ…と思いきや、CopyTreeは使った瞬間にWriteも行われるらしい。すごく使いにくいけど、これしかないからしょうがない。

CloneTreeもCopyTreeもそうだけど、このメソッドを使った時に返り値のTTreeの容量がある一定以上の大きさになると(具体的にはよく知らない)自動的にautosaveの機能が働いてcurrent directoryにwriteされるみたい。

つまり、その後でwriteを行うと二重に書き込まれる可能性がある(実際経験上同じTTreeが2つ書き込まれているの目にしたことがあると思う)。

もしもcurrent directoryが書き込み不可ならerrorが返されると思う。たしか。

ちなみにCloneTreeと同様、SetBranchStatusで無効化したBranchはコピーされないみたい。

TChainにCloneTreeさせると帰り値はTTreeになるけど、CopyTreeだとTChainが返ってく?

複数のtreeの取り扱い

AddFriend・treeに別のtreeのBranchを追加する

すでに存在しているtreeにBranchを追加する場合は、普通にBranchを作って、イベントloopしてBranch->Fill()すればいいんだけど、ユーザーマニュアルによればこれは推奨されない。

代わりにAddFriendを使って、別のtreeをくっつけるという手段をとる。

tree->AddFriend("new_tree","hoge.root");

第二引数は省略すると同じファイル内のtreeから選ばれる。追加したtreeの方がEntry数が少ないとwarningが表示される。

また追加するtreeがもとのtreeと同じ名前なら下のように別名をつけることもできる。

tree->AddFriend("tree1 = tree","hoge.root");

追加したBranchは

tree->Draw("<tree_name>.<branch_name>.<var_name>);

で呼び出せる。ただし、変数が一意に決められるなら<var_name>だけでもいい。

下のようにすれば追加したtreeを直接呼び出せる。

tree->GetFriend("new_tree")->Scan()

TChain・複数のROOTファイルのTTreeをまとめて解析する

  • 同じ種類のTTreeが入った複数のroot fileを読み込んで、一度に解析するクラス
  • TChainはTTreeを継承しているので、使い方は同じ

Fileの読み込み

TChain *ch=new TChain("tree", "title"); //treeの部分は使用したいtreeの名前を入れる必要がある
ch->Add("~.1.root")
ch->Add("~.2.root")
...

現実的には下のように正規表現を使うことが多い。

ch->Add("*.root")

解析するときには、いつもと同じ気分でGetEntryでloopをまわす。

hadd・rootファイルを合成する

(haddはTTree用のコマンドではないが、実質Treeを合成することがほとんどなので、ここに書いておく。)

同じ形式のTree(同じnameで同じBranchをもつTree)が保存された複数ファイルを合成して一つにすることができる。

hadd hadd.root file1.root file2.root ...

ただし、haddで合成できるTTreeの許容量は100GBまで。それ以上ならTChainを使うのが推奨される。

Treeに限らずTH1でも、(やったことはないが、おそらく)TEventListsも合成できる。

その他

FindBranch,FindLeaf・Branchを探す

FindLeafは使ったことない。

TBranch *br = tree->FindBranch("name");

何に使うかは私の知識ではあまり想像できないが、私はbranchをもっているかどうかのチェックに使った。

エラー集

rootでエラーが出てきたら、ほとんどはTTreeのせいだと思う。

segmentation violationが出た

毎度おなじみ *** Break *** segmentation violation。

存在しないBranchにSetBranchAddress

これが一番よくある。Branchの名前が間違っていたりして、treeに存在しないBranchに対してSetBranchAddressをした場合。

segmentation violation起こすんじゃなくて、エラーメッセージとか出して、例外処理でなんとかしてほしいんだけどなあ。

存在しない場合は処理を止めるようにwrapすればいいのかなあ。

初期化していないポインタのアドレスをSetBranchAddressに渡す

下のように初期化してないvectorのポインタにSetBranchAddressしたら、出た。

std::vector<float> *v;
tree->SetBranchAddress("v",&v);
tree->GetEntry(n);

下のようにきちんとヌルを指しておけば、大丈夫。

std::vector<float> *v = 0;
tree->SetBranchAddress("v",&v);
tree->GetEntry(n);

その他のエラー

vector<float>のBranchが作れない

普通vectorのBranchを作る場合は

std::vector<double> v;
tree->Branch("v",&v);

とするだけでいい。

が、この方法で、ACLiCでvector<float>のBranchを作ろうとすると、

Error in <TTree::Branch>: The pointer specified for v is not of a class or type known to ROOT

これはvector<float>だけしかでなくて、vector<double>でもvector<int>でもでない。他は試してない。

さらにいうと、cintならエラーは出ない。

どうやらvector<float>についてはなぜかDictionaryに登録されていないらしい。

http://root.cern.ch/phpBB3/viewtopic.php?t=8467によると

gROOT->ProcessLine("#include <vector>");

をいれるとうまくいく。いまいち理解してない。

が、しかし、そんなことをせずとも

std::vector<float> v;
tree->Branch("v","std::vector<float>",&v);

で、普通に通った。


【追記】

vector<float>に関してはいろいろおかしい。コマンドラインで下のようにすると

[ ] vector<float> v;
[ ] v.push_back(2)
Error: Can't call @@ ex autload entry remove by typedef declaration @@vector<float>::push_back(2) in current scope (tmpfile):1:
Possible candidates are...
Error: no such template @@ ex autload entry remove by typedef declaration @@vector<float> (tmpfile):1:
*** Interpreter error recovered ***

などと出る。微妙に言っていることが意味不明なんだけど。

どうやらvector<long>も同じようなメッセージが出てくる。

※たぶんversionによっては出たり出なかったりする

Fillができない

発生条件が謎。treeを生成して、tree->Fill()しようとしたら出た。

 This error is symptomatic of a Tree created as a memory-resident Tree
 Instead of doing:
    TTree *T = new TTree(...)
    TFile *f = new TFile(...)
 you should do:
    TFile *f = new TFile(...)
    TTree *T = new TTree(...)

エラーメッセに従ってTFileを作って、そこにtreeを作るとエラーが消える。

TTreeはautosaveっていう機能があって、Fillしたときbufferのデータ量があるbyteを超える毎に自動的にTFileに書き込む機能がある(らしい)。

実感はないんだけど、さもなくば、Treeの生成途中ではメモリにTTreeのデータをすべて保持することになるので、たちまち破綻するだろう。

というわけで、このあるbyteを超えた時にTFileが存在しないと、エラーが出るんだと思う。

GetEntryしたときのエラー

#include "TFile.h"
#include "TTree.h"

int main(){
    TFile *file = new TFile("hoge.root");
    TTree *tree = (TTree*)file->Get("tree");
    tree->GetEntry();
    return 0;
}

上のマクロをコンパイルして実行したら、なぜか下のようなエラーが出た。

main(60058) malloc: *** error for object 0x7f8ea5001ef0: incorrect checksum for freed object - object was probably modified after being freed.
*** set a breakpoint in malloc_error_break to debug

別の環境で試したらsegmentation violationになった。これはCINTのマクロとして実行した場合も、ACLiCを使った場合も出ないエラーのよう。

理屈は全く不明だが、メッセージはmallocとか出ているので、なにやらメモリリークしているのかと思って、試しに

delete file;

を加えたら、エラーが消えた。意味わからない。プログラム終了時にはどっちにしてもオブジェクトは削除されるんだから関係ないじゃないか。

vector<vector<float> >に関して

おそらくこれを見るのは、ATLASの人がググった場合だと思うけど、

vector<vector<float> >をSetBranchAddressしようとするとしたのようなErrorがでることがある。

Error in <TTree::SetBranchAddress>: The class requested (vector<vector<float> >) for the branch "~" refer to an stl collection and do not have a compiled CollectionProxy.  Please generate the dictionary for this class (vector<vector<float> >)

これは原因不明だが、なぜかvector<vector<float> >のdictionaryが生成されていないからおこる。そもそもこんなものにdictionaryをいちいち作らねばならないのはROOTの設計の欠陥だと思うが。

これに対しては自分でdictionaryを作ればいいんだけど、おそらくdictionaryについて知っている人は高エネルギー業界でも多くないだろうしここに書いておく。

詳しくはMyClassに書いてある。

まず、

#ifndef __VEC__
#define __VEC__

#include <vector>
 
#ifdef __CINT__
#pragma link off all globals;
#pragma link off all classes;
#pragma link off all functions;
#pragma link C++ class vector<vector<int> >+;
#pragma link C++ class vector<vector<bool> >+;
#pragma link C++ class vector<vector<char> >+;
#pragma link C++ class vector<vector<short> >+;
#pragma link C++ class vector<vector<long> >+;
#pragma link C++ class vector<vector<unsigned char> >+;
#pragma link C++ class vector<vector<unsigned short> >+;
#pragma link C++ class vector<vector<unsigned int> >+;
#pragma link C++ class vector<vector<unsigned long> >+;
#pragma link C++ class vector<vector<float> >+;
#pragma link C++ class vector<vector<double> >+;
#pragma link C++ class vector<vector<char*> >+;
#pragma link C++ class vector<vector<const char*> >+;
#pragma link C++ class vector<vector<Long64_t> >+;
#pragma link C++ class vector<vector<ULong64_t> >+;
#pragma link C++ class vector<vector<void*> >+;
#endif

#endif

というファイルを作る。次のコマンドを使って、

$ rootcint -f Dict.cc -c LinkDef.h

とすると、Dict.ccとDict.hが生成される。

後はDict.ccをincludeするか、一緒にコンパイルすればvector<vector<float> >が使えるはず。

コンパイルエラー

  • Error in <TClass::Load>: dictionary of class TTreeIndex not found

何で出てきたかあんまりよくわかってないけど、コンパイルオプションに -lTreePlayer を加えたらエラーが消えた。

g++ `root-config --cflags --libs` -lTreePlayer macro.cxx -o macro

study/software/root/ntuple.txt · 最終更新: 2015/06/05 22:31 by kamo

ページ用ツール