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倍ぐらい時間がかかる。