猿ルートによるとNtupleはsingle,double,triple,quadruple,quintuple,sextuple,septuple,octuple,nonuple,decuple· · ·の n 番目という意味で、エヌタプルまたはエヌチュープルと読む。
タプルというのは要素の組を表す概念。
ROOTではNtupleといった場合はTTreeのことを指すと思って問題ない(Ntupleはpawにもあるが、treeはc++用にpawのNtupleを拡張したものという感じ)。
まず最初にTTreeを調べる便利なメソッドたちとDraw・ヒストグラムを描くで、予め手元に用意されたTTreeの中身を調べるメソッドについて書いた。
正味のTTreeの使い方についてはTTreeによる解析にある。ただし、ネット上にはもっとわかりやすい解説がたくさんあるので、他を参照した方がいいと思う。
TTreeは変数データやオブジェクトをひとまとめにして管理するクラスである。何らかの物理データをROOTで使いやすい形に保存しておくために使う。
例えば、何かの実験値を記録したテキストデータファイルがあったとき、そのデータをヒストグラムに直接いれて解析をする…というのもありうるが、
いかにも面倒だし、何度もそのデータをいろんな方法で解析するにはあまり効率がよくない。我々のするべき作業はある程度決まっているし、root上でデータを完結できたほうがメリットは多い。
TTreeでは変数データはbranchというものに納められ、我々はTTreeのbranchを参照することで、TTreeの中の欲しいデータにアクセスできる。
各種データをtree構造でまとめるだけなら、適当なコンテナを使うのと大して変わりないと思うかもしれないが、以下のようなメリットがある。
イメージ的には各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を実際に作成する方法を後回しにして、まずTTreeの入ったrootファイルがある状況を想定して、それにどういうデータが入っているかを調べるメソッドを書く。
下のようにtreeの全Entry数がわかる。
[ ] tree->GetEntries() (Long64_t)10000
またある条件を満たすEntry数の数も下のように調べられる。とても便利。
[ ] tree->GetEntries("jet_pt > 50*1000") (Long64_t)8931
ちなみにDrawやScanも条件を指定した場合は同様にEntry数が返り値になっている。
下では例として、手元にあった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のすべてのbranchに入っている変数の値を表示できる。
tree->Show(表示したいEntry);
エントリー数、ブランチやリーフの数、ファイルサイズなんかを表示できる。
tree->Print();
guiでtreeを表示したり、任意の変数をいくつか選んで、2,3次元ヒストグラムを作成したり、なんてこともできる。
root [] tree->StartViewer();
または、TBrowserで、treeのところで右クリックでも呼び出すことが出来る。
root [] TBrowser b;
http://root.cern.ch/root/html/TTree.html#TTree:Draw@2
treeのDrawはやたらと強力である。何が強力かって、複雑な演算を処理したとしても高速でDrawが可能である。
tree->Draw("var1"); tree->Draw("var1:var2","(条件式)","Drawのオプション",Entryの上限,Entryの下限);
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をとる。いや、あらためて言うまでもないのだが。
長い条件式を毎回書くのは面倒だが、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");
配列やvectorなどをDrawする場合。
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は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を返す。
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");
で得ることができる。
実際に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はある一定のサイズを超えるとAutoSaveされるらしく(仕組みは不明)、そのときにTFileがないとerrorが出る。
つまりFillした時点で保存されることがあるわけで、そのあとでwriteすると二重に保存されてしまう。
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も作ることができる。
関係の深い変数同士(例えば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で参照している変数の最小単位が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 * ***********************************
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ではある特定の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の値が代入される。
またSetBranchAddressでは入れられた変数と型が一致しているかどうかのcheckは行われない。変数型を間違えてもコンパイルは通るので、気づきにくいバグとなる。
(2)GetEntry・ブランチの値をセットする
GetEntryを使用すると、ブランチにそのEntryでの値が代入される。
通常は下のように全Entryでloopをまわして使う。
for(int iEntry=0;iEntry<nEntry;++iEntry){ tree->GetEntry(iEntry); /* ここに行いたい解析内容を書く */ }
前述したような、SetBranchAddressしてGetEntryしてloopをまわして解析を行うような手続きは一般的なので、TTreeにはこれを行うスケルトン関数やクラスを自動的に生成するメソッドが存在する。
TTreeの解析をするためには必要な変数をすべてSetBranchAddressする必要があるが、もしも大量に変数がある場合ものすごく手間となる。
ここを書き間違えてエラーとか出ると相当いらつく。
このメソッドを使えば、その部分のコードを自動生成できるので、正味のコードに集中することができる。
PyROOTではSetBranchAddressをする必要はないので、このメソッドはお役御免である。
PyROOTバンザイ。
このメソッドはすでにobsoleteなので、MakeClassかMakeSelectorが推奨されるが、単純なので私はこれが好き。
でもまあ、あまりオブジェクト指向っぽいやり方ではない。
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を返すようにして使う。
MakeClassはSetBranchAddressをするヘッダーと実際に解析を行うソースファイルにわけているわけだが、MakeSelectorはさらにソースファイルをBegin(ヒストグラムを作る等),Process(ヒストグラムに値をFillする等),Terminate(ヒストグラムをDrawする等)の3つの部分にわける。
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を使う
ユーザが編集するのは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.
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(/*何か*/); }
そこはなとなく何か問題が発生しそうな香りがする。あるいは静的変数で宣言すればいいのか。でもそれならメンバ変数でいい気がする。
使い方をしらない。というか、これは同じようなメソッドなのかどうかも知らない。
あまりちゃんと読んでいないけれど、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);
GetEntryはすべてのBranchの値をバッファに呼び込むわけだけど、使わないBranchを読み込むのはメモリが無駄だし時間もかかる。
下のように使用しないブランチを無効にすることで効率化することができる。
tree->SetBranchStatus("*",0); //すべてのブランチを無効化 tree->SetBranchStatus("branch_name",1); //"branch_name"というブランチを有効化。1はデフォルトなので省略できる。
無効化されたBranchの値はGetEntry()でメモリに読み込まれなくなるので、場合によってはかなり高速になる。
当然、GetEntryしてもアドレスが渡されるわけではないので、アクセスしようとした時点でsegmentation violationになる。
segmentation violationが出る場合はまだましで、初期値がNULLではければそのままプログラムが走ってしまう。気付かずにやっていると本当に悲惨。
無効にしたことを忘れないようにすること。
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();
下のマクロの使い方は、まず予め変数のリストを用意して、
$ 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; }
tree->CopyTree("条件式");
もしも条件を入れない場合(そのままコピーしたいなら)
tree->CopyTree("1");
CloneTreeもCopyTreeもそうだけど、このメソッドを使った時に返り値のTTreeの容量がある一定以上の大きさになると(具体的にはよく知らない)自動的にautosaveの機能が働いてcurrent directoryにwriteされるみたい。
つまり、その後でwriteを行うと二重に書き込まれる可能性がある(実際経験上同じTTreeが2つ書き込まれているの目にしたことがあると思う)。
もしもcurrent directoryが書き込み不可ならerrorが返されると思う。たしか。
すでに存在している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 *ch=new TChain("tree", "title"); //treeの部分は使用したいtreeの名前を入れる必要がある ch->Add("~.1.root") ch->Add("~.2.root") ...
現実的には下のように正規表現を使うことが多い。
ch->Add("*.root")
解析するときには、いつもと同じ気分でGetEntryでloopをまわす。
(haddはTTree用のコマンドではないが、実質Treeを合成することがほとんどなので、ここに書いておく。)
同じ形式のTree(同じnameで同じBranchをもつTree)が保存された複数ファイルを合成して一つにすることができる。
hadd hadd.root file1.root file2.root ...
ただし、haddで合成できるTTreeの許容量は100GBまで。それ以上ならTChainを使うのが推奨される。
Treeに限らずTH1でも、(やったことはないが、おそらく)TEventListsも合成できる。
FindLeafは使ったことない。
TBranch *br = tree->FindBranch("name");
何に使うかは私の知識ではあまり想像できないが、私はbranchをもっているかどうかのチェックに使った。
rootでエラーが出てきたら、ほとんどはTTreeのせいだと思う。
毎度おなじみ *** Break *** segmentation violation。
これが一番よくある。Branchの名前が間違っていたりして、treeに存在しないBranchに対してSetBranchAddressをした場合。
segmentation violation起こすんじゃなくて、エラーメッセージとか出して、例外処理でなんとかしてほしいんだけどなあ。
存在しない場合は処理を止めるようにwrapすればいいのかなあ。
下のように初期化してない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の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によっては出たり出なかったりする
発生条件が謎。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が存在しないと、エラーが出るんだと思う。
#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;
を加えたら、エラーが消えた。意味わからない。プログラム終了時にはどっちにしてもオブジェクトは削除されるんだから関係ないじゃないか。
おそらくこれを見るのは、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> >が使えるはず。
何で出てきたかあんまりよくわかってないけど、コンパイルオプションに -lTreePlayer を加えたらエラーが消えた。
g++ `root-config --cflags --libs` -lTreePlayer macro.cxx -o macro