I/O関係のクラスが一番rootを特徴付けている。
rootで得られたヒストグラムなどのデータは拡張子.rootのファイルに記録されているので、読み込んで情報を引き出す必要がある。
rootはインタープリター上で実行する方法と、マクロを読みこませる二通りの方法があるが、その二種類の方法を書く。
下のようにすると、rootファイルを読み込むことが出来る。
$ root hoge.root
これはファイルを読み込み専用で開いていることになる。
コマンドラインで上カーソルボタンを押して履歴を表示すると、次のコマンドが残っているのが確認できる。
root [] TFile *_file0 = TFile::Open("hoge.root") //自動的に_file0というポインタが生成されている
つまりrootファイルを開くという行為は、rootでTFile型のオブジェクトを作っていることにあたる。macroで.root fileを読み込みたい場合は、このようにして自分でTFileを生成する必要がある。
ファイルのもつオブジェクトは.lsコマンドで確認できる。この場合はTH1F型のhistというオブジェクトが保存されている。
$ root hoge.root root [0] Attaching file hoge.root as _file0... root [1] .ls TFile** hoge.root TFile* hoge.root KEY: TH1F hist;1 hist
実はファイルを読み込んだ時点ではオブジェクトはメモリに呼ばれない。実際に使用する段階で、自動的にTKeyの情報を使ってオブジェクトの実体がメモリに呼ばれる。
この仕組みによって、ユーザは巨大なrootファイルのうち、実際にアクセスしたいデータ一つに効率良くアクセスできる。
下のようにhistというオブジェクトがあれば、インタープリター上でそのメソッドを使用することができる。
root [2] hist->Draw(); //オブジェクトのメソッドを使用した
fileを読み込んだ時点ではオブジェクトのアドレスをいれたポインタは存在しないので(それどころかオブジェクトはメモリに存在していない!)、通常はこれにアクセスはできないが、cintならば上のようにオブジェクト名をポインタのように使ってアクセスすることができる。
もう一度.lsをしてみよう。
root [3] .ls TFile** hoge.root TFile* hoge.root OBJ: TH1F hist hist : 0 at: 0x7f92db4bb1b0 KEY: TH1F hist;1 hist
前述したように使用するまでは、オブジェクトはメモリに呼ばれないが、DrawしたことでOBJのhistが追加されていることが確認できる。これはメモリにオブジェクトが呼ばれていることを示す。
インタープリターで出来るのはせいぜいrootファイルの中身を確認するぐらいで、本格的な解析はマクロを使う。下のようにマクロを作っておいて、それを読みこむのが普通だと思う。
$ root macro.cxx
マクロでrootファイルを読み込み見たい場合は、マクロの中でrootファイルを読み込むコードを書いておく必要がある。
しかし、ファイルを読み込んだだけではファイルに保存されたオブジェクトのアドレスを格納したポインタがないので、どうにかする必要がある。
サンプルコードだけを下に書いておくので、詳細は次からの項目に譲る。
#include <TFile.h> #include <TH1.h> //cintで実行するならincludeは必要ない void macro(){ //マクロ名と関数名は同じにする TFile *file = new TFile("hoge.root"); TH1F *h1 = (TH1F*)file->Get("hist"); h1->Draw(); }
ちなみに上のマクロはCINTで読み込むだけであれば
{ TFile::Open("hoge.root"); hist->Draw(); }
などと簡潔に書くことができるが、次からの説明はCINTの機能を使わずに正確にc++の文法に従って書く。
下のようなオプションが選べる。
TFIle *file = new TFile("hoge.root","オプション"); //
READ | デフォルト値なので省略可能。読み込み専用でファイルを開く |
UPDATE | 書き込み可能で開く。ファイルが存在しない場合は作成 |
NEW/CREATE | 新規ファイルを開く。すでにファイルが存在していた場合は開かない |
RECREATE | 新規ファイルを開く。すでにファイルが存在していた場合、上書き |
rootファイルに保存されているオブジェクトは使用するためには、そのオブジェクトのアドレスを格納したポインタを生成させる必要がある。
ここにはオブジェクトのアドレスを返す方法を書く。
カレントディレクトリにあるオブジェクトは下のいずれかの方法でアドレスを返すことが出来る。
file->Get("name");
file->GetList()->FindObject("name");
gDirectory->Get("name");//gDirectoryはカレントディレクトリへのポインタ
ここでfileはTFile型のポインタ、nameは呼び出したいオブジェクトの名前である。
実際的にはオブジェクトのメソッドにアクセスしたいので、
TH1F* h = (TH1F*)file->Get("hist");//Getで得られるのはTObject型なのでキャストしている。 h->Draw();
のようにポインタを作ってからアクセスするか、又は
((TH1F*)file->Get("hist"))->Draw();
のようにする。
後者の方法ではアクセスコストが高いので(つまり二重にメンバ関数を使用するので)、マクロの中で何度も使用するのはおすすめしない。
ほとんどのオブジェクトはgROOTによってアクセスすることができる。
gROOTは存在しているTCanvas、TFile、TF1などのリストをもっている。
gROOTのFindObjectはgROOTがもつリスト+カレントディレクトリのもつTListの中からオブジェクトを検索して、アドレスを返すということができる(それぞれの個別のリストを呼び出すこともできる)。
TH1F *h = (TH1F*)gROOT->FindObject("hist");
.lsしたときの出力をよく見ると、オブジェクト名に数字がくっついていると思う。通常は;1と記されているはずだが、中には下のように;2や;3が書かれているものもある。
root [] .ls TFile** hoge.root TFile* hoge.root KEY: TH1F hist;2 title KEY: TH1F hist;1 title
これは同名のオブジェクトを区別するためのものでnamecycleと呼ばれる。たぶん(私もよくわかってない)。
この場合は下のようにnamecycleをくっつけて呼び出せばいい。
file->Get("hist;1") file->Get("hist;2")
別の言い方をすると、保存するだけなら同じ名前でもできるが、同じディレクトリに同じ名前のオブジェクトをいれることはできない。
もっと厳密に言うと同じTListに同じ名前のオブジェクトは作れない。例えば、TH1とTCanvasなら同じ名前でも問題ない。
TFileではおそらく同名オブジェクトを保存できるようにnamecycleという仕組みがあるのではないかと思っている。
rootにはTListというオブジェクトのコンテナクラスがあり、TFileはこのクラスをメンバーに持っている。
root fileに保存されたオブジェクトというのは、TFileオブジェクトのメンバー変数のTListにそのオブジェクトが入っている状態である。
rootのオブジェクトは生成されると同時にカレントディレクトリのTListに加わるものもあり、そうでないものもある。
ディレクトリのListに加わる | TH1,TTree,TEventList |
gROOTが専用のListを持っている | TCanvas,TF1,TFile,TColor,TStyle,TBrowser,etc… |
どちらにもあてはまらない | TGraph,TLegend,TLine,TArrow,TBox,etc,… |
.lsでコマンドで一覧できるのはTH1やTTreeのみで、TGraphは表示されないことを経験的に知っていると思うが、それはTGraphなどのオブジェクトはデフォルトではカレントディレクトリに加わらないためである。
ディレクトリに属さないオブジェクトでかつ、gROOTのリストにもないようなオブジェクト(例えばTGraph)はポインタを失えば、もはやアクセスできなくなる。
それが困る場合は、下のようにしてオブジェクトをディレクトリのリストに加えておけばいい。
file->Add(obj); file->Add("name");//オブジェクト名を引数にすることもできる
ちなみにAddしただけでは保存はされない。
rootでは単にオブジェクトを生成させただけではfileには書き込まれない。そのままrootを終了するとオブジェクトは失われる。
詳しく言うと、.lsしたときに
root [23] .ls TFile** hoge.root TFile* hoge.root OBJ: TH1F hist title : 0 at: 0x7ffd21d8ddc0
のようにOBJとしか書いてなくて、KEYと書かれていない場合はそのオブジェクトはまたfileに保存されていない。
ファイルを保存したい場合は下のようにすればいい。
//fileはTFile型 file->Write(); //fileにおかれている全てのオブジェクトを保存する
上の場合は、カレントディレクトリにあるオブジェクトをすべてを保存するが、選択的に保存したい場合には下のようにすればいい。
obj->Write(); //カレントディレクトリに書き込む obj->Write("name"); //"name"というオブジェクト名で保存
上の方法では、そのディレクトリに属していないオブジェクトも保存することができる。
TFileはTDirectoryというクラスを継承した、rootの空間におけるDirectoryである。
rootファイルを複数読み込んだ場合は、Directoryが複数あるのと同じでカレントディレクトリを意識する必要がある。
カレントディレクトリはTFileを宣言したときに、そこに自動的に移る。
ところで、異なるTFileにおかれたオブジェクトは.lsで見ることができない。
cd()メソッドを使うと、カレントディレクトリを変えることができる。
//hoge1.root(file1),hoge2.root(file2)というファイルを開いている状況で今hoge1.rootにいるとする root [] .ls TFile** hoge1.root TFile* hoge1.root KEY: TCanvas c1;1 c1 root [] //上ではhoge1.rootのオブジェクト一覧が表示された root [] file2->cd(); //hoge2.rootに移動した root [] .ls TFile** hoge2.root TFile* hoge2.root KEY: TH1F hist;1 hist root [] //今度はhoge2.rootのオブジェクト一覧が表示される root [] hist->Draw(); //ヒストグラムが表示される
<追記> そんな状況に出会った
下のようにすればできる。
root [] file = new TFile(hoge.root","update") //updateで読み込まないと削除できない root [] .ls TFile** hoge.root TFile* hoge.root KEY: TTree name;1 name KEY: TTree name;2 name root [] file->Delete("name;1"); //nameはオブジェクト名 root [] file->Delete("name;2"); //namecycleが2なら;2をつける root [] file->Delete("name"); //namecycleを何も付けないと、TOBJだけ消えてTKEYは残る(つまりメモリから消えるだけ)
上の方法で問題なくできるが、こんな方法も考えた。でも、なにか残っている気もするなあ。
TFileからオブジェクトを削除するには、TObjectではなく、TKeyをDeleteする必要がある。
file->FindKey("オブジェクト名")->Delete(); //FindKeyで削除したいKeyを呼び出し、Deleteで削除している
root [] object->Delete();
つまり、Deleteは基本的にはc++の普通のdeleteを行わせるメソッド。
例えばfile1からfile2にTH1F"hist"をコピーする場合。
root [] TFile *fin = new TFile("file1.root"); root [] TFile *fout = new TFile("file2.root","recreate"); root [] TH1F *hist = ((TH1F*)fin->Get("hist"))->Clone(); root [] hist->Write(); //ここで.lsすると、histのKeyとObjが生成しているはずである。
あるいはCloneを使わずに、これでも出来る。
root [] TFile *fin = new TFile("file1.root"); root [] TFile *fout = new TFile("file2.root","recreate"); root [] fin->Get("hist")->Write();
ここでは(なんだかバグみたいな話だが)file1.rootとfile2.rootで同じhistというオブジェクトを共有していることになる(.lsでアドレスを確認してみるといい)。
実はrootでは2つのディレクトリに同じオブジェクトが所属することが許されている。
もっとも、TFileは内部的にはlistにオブジェクトを加えているだけなので、おかしなことではないのだが。
大量のヒストグラムが保存されたrootファイルの一つ一つDrawするのは面倒くさいと思う。
TFileにはDrawメソッドが用意されて、そのファイルに書き込まれているオブジェクトをすべてDrawできる。
が、しかし、このメソッドの対象はObjの状態でTFileに存在するオブジェクトだけ(内部的にはTFile::GetList()で呼び出されているので)。TKeyの状態ではだめ。
つまり一度DrawするなりGetするなりして、メモリにオブジェクトを呼び出していないとDrawできない。一体このメソッドは何に使うのだろうか。
あと、描くCanvasの変更もやり方がわからない。一体どう使うことを想定しているのか。
TFile *file = new TFile("ファイル名"); TCanvas *c1 = new TCanvas("c1","c1"); TKey *key = 0; const char* str = "画像名.pdf"; c1->Print(Form("%s(",str)); //ページの最初に白紙を追加。 TIter next(file->GetListOfKeys()); while((key = (TKey*)next())){ key->ReadObj()->Draw(); c1->Print(str); c1->Clear(); } c1->Print(Form("%s)",str)); //ページの最後に白紙を追加 c1->Close(); //ついでにCanvasを閉じる
例えば上のようなマクロなら全てのオブジェクトをDrawして画像にできる(これだと前後に白紙が入るけど)。
rootにはTDirectoryとそれを継承したTFileとTROOT、TDirectoryFileというものがある。さらにTFolderなんてものもある。TFolderについてはTDirectoryと関係はないし、積極的に使う人間が地球にいるかどうか未確認だし、私もよく知らない。
rootはunixに近いディレクトリー構造を持つ。
$ root hoge.root
とした場合のカレントディレクトリはTFile型のhoge.rootである。このことは.pwdコマンドで確認できる。
$ root hoge.root [ ] .pwd Current directory: hoge.root:/ Current style: Modern
関係ないことだけど、ModernというのはCanvasのStyleのこと。デフォルト値は.rootrcに記述されている。
ではfileを開かずに、rootのshellを起動させた場合はどうなるかというと、
$ root [ ] .pwd Current directory: Rint:/ Current style: Modern
Rintとなっている。これはgROOT(TROOTのインスタンスをさしたグローバル変数)のオブジェクト名である。
ちなみに、hoge.rootにも、Rintにも:/と書いてあるので、これらはともにルートディレクトリであることが分かる。つまり、ファイルを読み込んだ場合は並列に2つのディレクトリ、hoge.rootとRint(gROOT)が存在していることになる。
TDirectoryのmkdir()を使えばサブディレクトリも作れる
$ root hoge.root [ ] _file0->mkdir("sub"); [ ] .ls TFile** hoge.root TFile* hoge.root TDirectoryFile* sub sub [ ] sub->cd() [ ] .pwd Current directory: hoge.root:/sub Current style: Modern
サブディレクトリはTDirectoryFile型になるようである。名前がややこしすぎる。
gDirectoryはカレントディレクトリを指すグローバル変数である。
$ root hoge.root [ ] _file0->mkdir("sub"); [ ] sub->cd() [ ] gDirectory->cd("..") [ ] .pwd Current directory: hoge.root:/ Current style: Modern
まとめに入ると、
以上、まったくもって、誰にも役に立たない知識でした。
rootのだいたいのオブジェクトはTObjectを継承している。
ここには全般的に言えることだけ書いておく。
私はrootを使い始めた頃、c/c++の知識がすかすかでnewが何なのかもあまり理解してなかった。
なので多くのrootの解説書を見ると、下のように
TH1F *hist = new TH1F("hist","title",100,-10,10);
と書いてあって、なぜ
TH1F hist("hist","title",100,-10,10);
としないのかよくわからなかった。
そもそもnew演算子はヒープ領域に動的メモリを確保してインスタンスを生成している。よく言われるメリットは
rootの場合はnewを使わない場合にとくに問題になってくるのはインタープリターとmacroのスコープは違うということ。
なので、下のように、macroでスタック領域に作ったオブジェクトは関数終了時点ですべて破棄されてしまう。
$ root macro.cxx root [0] Processing macro.cxx... /* macro.cxxでは TH1F h1("h","title",100,0,100); などとしている */ root [1] .ls /* 何も出ません */
つまり、オブジェクトはスタックに作ってしまうと、例えマクロを走らせても解析結果を保持したオブジェクトは消滅してしまう(なので、マクロの中でファイルに保存する必要がある)。
たぶん多くの人はマクロとコマンドラインを行き来しながらマクロを調整したりして解析をするので、これは大きな問題だと思う。
ちなみに話がそれるが、newによってインスタンス化を行なっても、macro終了時にはそのアドレスを格納したポインタ自体は当然失われる。
$ root macro.cxx root [0] Processing macro.cxx... /* TH1F *h1 = new TH1F("h","title",100,0,100); をしている */ root [1] .ls OBJ: TH1F h title : 0 at: 0x7ffa9437db30 root [2] h1->Draw(); Error: Symbol h1 is not defined in current scope (tmpfile):1: Error: Failed to evaluate h1->Draw() *** Interpreter error recovered ***
上のようにポインタを使ってオブジェクトにアクセスすることはできない。
普通はこの状況はただのメモリリークになると思うけど、rootではオブジェクト名によって再びアドレスを呼び出すことが可能になっている。
一つのオブジェクトだけを保存したファイルを作る場合、いちいちTFileを作るのは面倒である。SaveAsを使えば、直接rootファイルを作れる
obj->SaveAs("hoge.root"); //.rootで終わるとそのオブジェクトを保存したrootファイルが生成される obj->SaveAs("hoge.xml"); //.xmlとして保存 obj->SaveAs("hoge"); //c++のマクロとして保存?よく知らない
Cloneするだけなのだが、引数を指定せずにCloneするとオブジェクト名がかぶるので、自分で名前を変えなくてはならない。
例として、TH1Fをコピーする。
TH1F *h_new = (TH1F*)h->Clone(); h_new->SetName("h_new"); //又は TH1F *h_new = (TH1F*)h->Clone("h_new");
http://heptech.web.fc2.com/root/string.htmlからのコピー。
#include "TString.h" TH1F *h[10]; for (int i=0;i<10;i++){ h[i]=new TH1F(Form("h[%d]",i),"",100,0.,100.); }
説明はしない。