http://root.cern.ch/drupal/content/pyroot
PyROOT A Python -- ROOT Bridge 公式manual
python-and-ruby-interfaces - ROOT User’s Guide rootの情報って分散しすぎ
Data Analysis with pyROOT - ~middell
PyROOTはpythonからROOTのライブラリを呼び出すためのラッパーモジュールである。
C++の複雑な作法に従わなくてはならないROOTと比べ、シンプルに使うことが出来る。
<pythonのメリット・特徴>
<デメリット>
普通にROOTをインストールするとenable-pythonでインストールされるので何もしなくてもPyROOTは使える。
$ ./configure <arch> --enable-python [--with-python-incdir=<dir>] [--with-python-libdir=<dir>] $ gmake
(archのところはlinux、win32、macosxなどをいれるみたいだけど、いれなくてもインストールできるみたい)
準備としては、以下の環境変数だけ設定しておく。
export PYTHONPATH=$ROOTSYS/lib:$PYTHONPATH
あとは普通にpythonを実行してROOT.pyをimportすればいい。
>>> import ROOT >>> h = ROOT.TH1F("h1","",100,-5,5)
(これはpython一般の話だけど)下のようにすると名前空間の部分を省略できる。
>>> from ROOT import TH1F, TTree #使うクラスだけimportする >>> h = TH1F("h1","",100,-5,5) >>> t = TTree("tree","tree")
下のようにすれば、すべてのクラスが使えるようになる。
>>> from ROOT import * #名前空間の思想的に非推奨。基本的にはinterpreterのときだけ >>> h = TH1F("h1","",100,-5,5)
個人的にグローバル変数と色はimportしといた方が楽な気がする。
from ROOT import gROOT, gDirectory, gPad, gSystem, gStyle from ROOT import kWhite, kBlack, kGray, kRed, kGreen, kBlue, kYellow, kMagenta, kCyan, kOrange, kSpring, kTeal, kAzure, kViolet, kPink
原因がなかなか分からなくてROOTのリビルドまでしてしまった。
http://doc.pypy.org/en/latest/cppyy.html
cppyyを使うとPyPyでROOTを動かすことが出来る。
PyPyはpythonで記述されたpythonの実装でJITコンパイル機能をもっている。
CPythonと互換性があり、多くの場合CPythonよりも高速で動作する。
またCINTで使用することのできた内部コマンドもpython interpreterで再現されている。
>>> import ROOT >>> h = ROOT.TH1F("h","h",100,0,100) >>> .ls OBJ: TH1F h h : 0 at: 0x7f8902e389a0 >>> .? PyROOT emulation of CINT commands. All emulated commands must be preceded by a . (dot). =========================================================================== Help: ? : this help help : this help Shell: ![shell] : execute shell command Evaluation: x [file] : load [file] and evaluate {statements} in the file Load/Unload: L [lib] : load [lib] Quit: q : quit python session The standard python help system is available through a call to 'help()' or 'help(<id>)' where <id> is an identifier, e.g. a class or function such as TPad or TPad.cd, etc.
ついでにディレクトリやファイルの補完も設定してくれている。
が、しかし、PyROOTの仕様上の問題で、使っていないROOTのクラスの補完はできない(一度使えば補完できるようになる)。
ややこしいことに.xはpythonのスクリプトを読み込み、.Lはc++のファイルを読み込む。
>>> .x script.py #.xはpython scriptを実行 >>> .L macro.cxx #macroを読み込む >>> .L mylib.so #共有ライブラリを読み込む
ROOT.pyを読んでみると、.xはexecfile(pythonのbuilt-in function)をしていて、.LはgSystem.Loadをしている。
gSystem.Loadは.rootrcのRoot.DynamicPathで登録したDirectoryにあるファイルだけを読み込めるのだが、
CINTの.LはRoot.MacroPathで登録したDirectoryにあるファイルを読み込むので、CINTとは少し挙動に違いがある。
もしも、同じようにしたいなら、下のようにする。
>>> ROOT.gROOT.Macro("macro.cxx") #CINTでの.x >>> ROOT.gROOT.LoadMacro("macro.cxx") #CINTでの.L
あるいはProcessLineはCINT上で実行できるので、下のようにするという手もある。
>>> ROOT.gROOT.ProcessLine(".x macro.cxx")
ちなみにここで読み込まれた関数やクラスも名前空間ROOTに属すので、
>>> ROOT.somefunc() >>> ROOT.someclass()
として呼び出される(import ROOT from *をすればROOTは省略できる。もちろん)
PyROOTでは.rootrcはROOTと同様に読み込まれる(ROOT.gEnvで確認してみるといい)。
rootlogonについはROOT.pyによると
というふうになっている。
python scriptを実行するときに-nが引数にあればこれは読み込まれない。
:0: RuntimeWarning: Illegal numerical expression .rootlogon() :0: RuntimeWarning: Illegal numerical expression .rootlogon() :0: RuntimeWarning: Illegal numerical expression .rootlogon() :0: RuntimeWarning: Illegal numerical expression .rootlogon() :0: RuntimeWarning: Illegal numerical expression .rootlogon() :0: RuntimeWarning: Illegal numerical expression .rootlogon() :0: RuntimeWarning: Illegal numerical expression .rootlogon() :0: RuntimeWarning: Illegal numerical expression .rootlogon() :0: RuntimeWarning: Illegal numerical expression .rootlogon() :0: RuntimeWarning: Illegal numerical expression .rootlogon() (const long double)0
などと表示されて、うまくいかない。なんやろな、これ。
ROOTではmacroを読み込んだ後にそのままinterpreterの操作に移ることができる。やはりこれは便利だと思う。
-iオプションをつけてpythonを実行するとscriptの読み込み後interpreterが起動できる。
python -i script.py #scriptの実行 >>>
またはscriptの中でpython interpreterを起動させるには下のようにすればいい。
#ROOTを使う場合 ROOT.TPython.Prompt() #codeを使う場合 import code code.InteractiveConsole(globals()).interact()
個人的にはスクリプトの終了時に上のコードをつけたしてinterpreterを起動するのがいいと思う。
C++のプログラムからpython interpreterを起動させることが出来る(下の例ではCINTから呼び出しているが、c++のプログラムでも可能)。
//pythonコマンドの実行 root[] TPython::Exec("print 1+1"); 2 //interpreterの起動 root[] TPython::Prompt() >>> h = ROOT.TH1F() //予めimport ROOTされている >>> h.Draw()
ROOTのオブジェクトとbuilt-in types(組み込み型、プリミティブ型)についてはCINTとpython interpreter間を越えて使用できる。
//interpreter間でのobjectの共有 //pythonからCINTへ root[] TH1F *h = (void*)TPython::Eval("ROOT.TH1F(\"h1\",\"\",100,0,100)") root[] h (class TH1F*)0x7fe6335e43c0 //CINTからpythonへ root[] TPython::Bind(h,"h") root[] h == (void*)TPython::Eval("h") //比較もできる 1 root[] TPython::Prompt() >>> h <ROOT.TH1F object ("h1") at 0x7fe6335e43c0> //pythonのbuilt-in typesをCINTで使う root[] int i = TPython::Eval( "1+1" ); root[] i (int)2
root[] TH1F *h = (void*)TPython::Eval("ROOT.TH1F(\"h1\",\"\",100,0,100)")
はCINTではダウンキャストを暗黙で行えることを利用している。
マクロをコンパイルするときは下のようにすればいい。
//#include <TPython.h> //#include <TH1.h> TH1F *h = static_cast<TH1F*>( (void*)TPython::Eval("ROOT.TH1F(\"h1\",\"\",100,0,100)") );
TPython::EvalはTPyReturn型を返すが、これはポインタですらないので当然TH1F*に(普通の)キャストはできない。
しかし、いったんvoid*型にしてからstatic_castを使うと型チェックなしでキャストできる。
もちろん間違った型にキャストもできてしまうので、慎重に行う必要がある。
あんまりよく理解していないが、reinterpret_cast使ったほうがいいのかな。
pythonで作ったクラスを読み込むことも出来る。
root[] TPython::LoadMacro( "MyPyClass.py" )
rootでは-bオプションをつけると、バッチモードで起動できてTCanvasなどGUIの表示を止められるが、PyROOTにはこのオプションがない。
面倒だけど、gROOTを使って、バッチモードにする必要がある。
ROOT.gROOT.SetBatch()
たぶんSetBatchするのはsshでリモートログインしていて、かつX connectionを切っている場合が多いと思うけれど、from ROOT import hogehogeをする場合は少し注意が必要。
from ROOT import gROOT gROOT.SetBatch() from ROOT import TFile
のようにimportする前にSetBatchをする必要がある。
from ROOT import gROOT, TFile gROOT.SetBatch()
とすると、TFileをimportした時点でguiが要求されるらしく、SetBatchする前にプログラムが終了してしまう。
PyROOTではSetBranchAddressをする必要はなく、TTreeのメンバー変数として直接Branchの値にアクセスできる。
#p1というBranchがあるとして tree.GetEntry(3) print tree.p1
これはすごく便利。
これだけ見てもPyROOTを使用するmotivationになりうる。やっぱり初心者にはPyROOTの方がいいかもしれない。
c++とほぼ同じようにBranchを作ることが出来る。
from ROOT import TTree , std , TH1F t = TTree('tree','tree') v = std.vector(int)() h = TH1F('hist','',100,0,100) t.Branch('v',v) t.Branch('h',h)
c言語のintやfloatなどの組み込み型はPythonでは作れないので、下に書いた方法のいずかをとる必要がある。
import ROOT ROOT.gROOT.ProcessLine('\ struct MyStruct{\ Int_t a;\ Double_t b;\ };') s = ROOT.MyStruct() t.Branch('val1',ROOT.AddressOf(s,'a'),'a/I') t.Branch('val2',ROOT.AddressOf(s,'b'),'b/D')
from array import array n = array('i',[0]) d = array('f',[0,0,0,0]) t.Branch('val',n,'n/I') t.Branch('arr',d,'d[4]/F') n[0] = 33 d = array('f',[3.,1.,2.,2.]) t.Fill()
import numpy #pythonのfloatはcのdoubleに対応する n = numpy.zeros(1, dtype=float) t.Branch('val',n,'n/D') n[0] = 3.33 t.Fill()
from ctypes import * a = c_int() b = c_float() t.Branch('val1',a,'a/I') t.Branch('val2',b,'b/F') a.value = 3 b.value = 3.13 t.Fill()
pythonにはポインタやcの配列はないが、ROOTでは当然それらを引数にすることがある。
配列を引数にしたい場合はpythonのarrayを代わりに引数にすることが出来る(これってPyROOTに限った話じゃないのかな?)。
from ROOT import TGraph from array import array num = 10 x = array('i', xrange(10)) y = array('i', xrange(0,20,2)) g = TGraph(num, x , y)
せっかくpythonなのだから、listからTGraphを作りたいので、下のようなラッパー関数を作ってみた(というと大げさだけども)。
from ROOT import TGraph from array import array def TPGraph(n, x, y): xx = array('d', x) yy = array('d', y) return TGraph(n, xx, yy)
rootコマンドをPyROOTで再現させてみた。
もうそんな必要はありません。
このコマンドを使えば、rootコマンドと同じ気分でPyROOTが使えます。
#!/usr/bin/env python #pyroot #author: Naoyuki Kamo ### For script name space ### dictionary = {} dictionary.update(globals()) import os import sys try: import traceback except: pass ### Get option parameters ### try: from optparse import OptionParser usage = 'usage: %prog [-b] [-q] [-d DIR] [data1.root data2.root ...] [file.py arg1 arg2 ...]' parser = OptionParser(usage=usage) del OptionParser, usage parser.add_option('-b', '--batch', action='store_true', default=False, help='run in batch mode without graphics') parser.add_option('-n', '--noex', action='store_true', default=False, help='do not execute rootlogon.py in MacroPath specified by .rootrc') parser.add_option('-q', '--quit', action='store_true', default=False, help='exit after processing command line macro files') parser.add_option('-d', '--dir', action='store', help='if dir is a valid directory, cd to it before executing') (options, args) = parser.parse_args() del parser except: print 'Warning: Module optparse doesn\'t exists. Cannot use options.' args = sys.argv del args[0] class tmp(object): def __init__(self): self.batch = False self.noex = False self.quit = False self.dir = None options = tmp() del tmp ### For history ### try: import readline import atexit f = os.path.join(os.environ['HOME'], '.pyroot_history') readline.read_history_file(f) atexit.register(readline.write_history_file, f) del f, atexit except: if traceback: print traceback.format_exc() ### Run pyroot ### try: import ROOT except: if traceback: print traceback.format_exc() print 'PyRoot exits...' sys.exit() ### Set batch mode ### if options.batch: ROOT.gROOT.SetBatch() print 'PyRoot runs on batch mode...' ### Load rootlogon.py in MacroPath ### if not options.noex: for kFile in ROOT.gROOT.GetMacroPath().rsplit(':'): rootlogon = os.path.join(os.path.expanduser(kFile), 'rootlogon.py') if os.path.isfile(rootlogon) and os.access(rootlogon, os.R_OK): try: execfile(rootlogon, dictionary, dictionary) except: if traceback: print traceback.format_exc() break ### Change Directory ### if not options.dir is None: print 'Changing directory to ' + options.dir + '...' if readline: readline.add_history('.cd %s' % options.dir) try: os.chdir(options.dir) except: if traceback: print traceback.format_exc() ### Define function ### def Match(text, string): import re tmp = '\\' + string m = re.search(tmp, text) if m: ext = m.group() else: ext = '' if ext == string: return True else: return False def Ext(text): root, ext = os.path.splitext(text) return ext ### Load files ### Nroot = 0 Nfile = 0 File = None for File in args: ### except arguments ### if not os.path.exists(File): print 'Warning: %s is not found' % File Nfile += 1 elif os.path.isdir(File): print 'Warning: %s is a dirctory' % File print 'If would like to change dirctory, use the option -d' Nfile += 1 ### Load root files ### elif Match(File, '.root'): print 'Attaching file %s as _file%d...' % (File, Nroot) if readline: readline.add_history('_file%d = TFile.Open(\'%s\')' % (Nroot, File)) exec('_file%d = ROOT.TFile.Open(\'%s\')' % (Nroot, File)) Nroot += 1 Nfile += 1 ### Load c++ files ### elif Ext(File) in ('.c', '.h', '.C', '.cc', 'cp', '.cpp', '.cxx', '.so'): print 'Loading file %s...' % File if readline: readline.add_history('.L %s' % File) ROOT.gSystem.Load(File) Nfile += 1 ### Execute python script ### else: import sys sys.argv = args[Nfile:] print 'Processing ' + File + '...' if readline: readline.add_history('.x %s' % File) try: execfile(File, dictionary, dictionary) except: if traceback: print traceback.format_exc() break del Nroot, Nfile, File, args, Match, Ext if traceback: del traceback ### python prompt ### if not options.quit: del options from ROOT import * if readline: ### Set prompt Style ### class LineCounter(object): def __init__(self): self.count = 0 sys.ps1 = 'pyroot [%d] ' % self.count sys.ps2 = 'pyroot ... ' def __call__(self, value): if not value is None: print value self.count += 1 sys.ps1 = 'pyroot [%d] ' % self.count if self.count % 10 == 0: sys.ps2 = 'pyroot ...' for i in range(self.count / 10): sys.ps2 += '.' sys.ps2 += ' ' gInterpreter.EndOfLineAction() sys.displayhook = LineCounter() del LineCounter try: import rlcompleter if issubclass(readline.get_completer().__self__.__class__, rlcompleter.Completer): Super = readline.get_completer().__self__.__class__ else: Super = rlcompleter.Completer del rlcompleter class ClassNameCompleter(Super): def __init__(self): self.Super = Super self.Super.__init__(self) self.classes = map((lambda x: gClassTable.At(x).replace('::', '.').replace('<', '(').replace('>', ')')),\ xrange(gClassTable.Classes())) def class_matches(self, text): if text: matches = [s for s in self.classes if s and s.startswith(text)] else: matches = self.classes[:] return matches def global_matches(self, text): matches = self.Super.global_matches(self, text) if not matches: matches = [] return matches + self.class_matches(text) readline.set_completer(ClassNameCompleter().complete) del Super, ClassNameCompleter readline.parse_and_bind('tab: complete') readline.parse_and_bind('set show-all-if-ambiguous On') del readline except: pass globals().update(dictionary) del dictionary TPython.Prompt()
#PATHの通ったディレクトリにスクリプトを置いておく #interpreterを起動、from ROOT import *する $ pyroot pyroot [0] #from ROOT import *する、rootファイルを読み込む $ pyroot hoge.root Attaching file hoge.root as _file0... pyroot [0] #複数のrootファイルを読み込む $ pyroot hoge.root hoge2.root Attaching file hoge.root as _file0... Attaching file hoge2.root as _file1... pyroot [0] #.root*という拡張子なら読み込める $ pyroot hoge.root.1 Attaching file hoge.root.1 as _file0... pyroot [0] #スクリプトの実行後、interpreterを起動 $ pyroot hoge.py Processing hoge.py... pyroot [0] #スクリプトを普通に実行 $ pyroot -q hoge.py Processing hoge.py... #rootlogon.pyを読み込まない $ pyroot -n hoge.py Processing hoge.py... pyroot [0] #バッチモードで起動 $ pyroot -b hoge.py Processing hoge.py... pyroot [0] #スクリプトに引数を与える $ pyroot hoge.py arg1 arg2 Processing hoge.py... pyroot [0] #c++のマクロをロードする $ pyroot hoge.cxx Loading file hoge.cxx... pyroot [0] #共有ライブラリをロードする $ pyroot hoge.so Loading file hoge.so... pyroot [0] #ディレクトリに移動してから実行 $ pyroot -d dir hoge.py Changing directory to dir... Processing hoge.py... pyroot [0] #rootではdirの移動はオプション引数ではないけど、気持ち悪いからオプション引数にした #ヘルプもいれた $ pyroot -h Usage: pyroot [-b] [-q] [-d DIR] [data1.root data2.root ...] [file.py arg1 arg2 ...] Options: -h, --help show this help message and exit -b, --batch run in batch mode without graphics -n, --noex do not execute rootlogon.py in MacroPath specified by .rootrc -q, --quit exit after processing command line macro files -d DIR, --dir=DIR if dir is a valid directory, cd to it before executing
特徴としては
起動時間とかはあまり気にせず作った。私の環境では普通のrootのコマンド使うより3倍ぐらい時間がかかる。