ユーザ用ツール

サイト用ツール


サイドバー

Menu

Latest

study:software:root:pyroot

PyROOT

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のメリット・特徴>

  • 強い動的型づけ言語
    • なので記述が完結になる
  • オブジェクト指向
  • スクリプト言語の中では比較的高速?(と言われている)
  • 強力な標準ライブラリ
  • c++などの他言語によって、容易に拡張できる
  • ガベージコレクション(いらなくなったオブジェクトを破棄してメモリを開放する機能)
  • 多くのプラットフォームで動作する
  • 近年googleを始めとした多くの企業で採用されたりしていて、人気が年々高まっている

<デメリット>

  • 動作速度はc++と比べれば遅い
  • 基本的に実行しないとエラーチェックできない(ものすごく、これが問題になると思う)
  • 日本語の情報が少ない
  • PyROOTを使っていてわからないことがあったら、結局c++のソースに立ち返る必要がある

使い方

普通にROOTをインストールするとenable-pythonでインストールされるので何もしなくてもPyROOTは使える。

もしデフォルトと違うpythonのversionを使いたいなら、下のようにして自分でインストールする。

$ ./configure <arch> --enable-python [--with-python-incdir=<dir>] [--with-python-libdir=<dir>]
$ gmake

(archのところはlinux、win32、macosxなどをいれるみたいだけど、いれなくてもインストールできるみたい)

準備としては、以下の環境変数だけ設定しておく。

export PYTHONPATH=$ROOTSYS/lib:$PYTHONPATH

pathのディレクトリにROOT.pyというファイルがあることを確認する(ls $ROOTSYS/lib)。なければ、別のところにある($ROOTSYS/lib/ROOTとか)

あとは普通に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.pyという名前で関係ないファイルを作ってしまっていたことがあって、 いくらimportしてもカレントディレクトリのモジュールが優先されるので、PyROOTが使えない!みたいなことがあった。

原因がなかなか分からなくてROOTのリビルドまでしてしまった。

rootpy:もっとPythonicに

http://rootpy.org/

http://rootpy.org/intro.pdf

これって公式なのかなあ?一応rootの公式ページからも飛べるけど。

cppyy:もっとハイパフォーマンスに

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.

ipythonやbpythonではこれらの内部コマンドは使用不可。

ちなみにROOT.pyによってreadlineとrlcompleterが読み込まれて設定がされているので、タブ補完ができる。

ついでにディレクトリやファイルの補完も設定してくれている。

が、しかし、PyROOTの仕様上の問題で、使っていないROOTのクラスの補完はできない(一度使えば補完できるようになる)。

.xと.Lの違い

ややこしいことに.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によると

  1. ~/.rootlogon.pyが存在しなければ読み込む
  2. 1で読み込まれなけば、.rootlogon.Cを下の順番に探して読み込む
    1. gRootDir
    2. ~
    3. Current Directory

というふうになっている。

python scriptを実行するときに-nが引数にあればこれは読み込まれない。

そのうち修正されると思うけど.rootlogon.Cを読み込もうとすると

: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

などと表示されて、うまくいかない。なんやろな、これ。

scriptの読み込み後にinterpreterを起動する

ROOTではmacroを読み込んだ後にそのままinterpreterの操作に移ることができる。やはりこれは便利だと思う。

-iオプションをつけてpythonを実行するとscriptの読み込み後interpreterが起動できる。

python -i script.py
#scriptの実行
>>>

-iをつけた場合でもPYTHONSTARTUPは読み込まれない

またはscriptの中でpython interpreterを起動させるには下のようにすればいい。

#ROOTを使う場合
ROOT.TPython.Prompt()

#codeを使う場合
import code
code.InteractiveConsole(globals()).interact()

PyROOTはpythonなので、scriptの処理が終わるとTCanvasなどを閉じてしまう(常にroot -qになっている感じ)。 これはとても不便で、必要なら何らかの方法で、イベントループさせるか、動作を停止させなくてはいけない。

個人的にはスクリプトの終了時に上のコードをつけたしてinterpreterを起動するのがいいと思う。

C++からPyROOTを使う

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でのTTreeの扱い

Branchの値を取り出す

PyROOTではSetBranchAddressをする必要はなく、TTreeのメンバー変数として直接Branchの値にアクセスできる。

#p1というBranchがあるとして
tree.GetEntry(3)
print tree.p1

これはすごく便利。

これだけ見てもPyROOTを使用するmotivationになりうる。やっぱり初心者にはPyROOTの方がいいかもしれない。

Branchを作る

vectorなどのBranchを作る

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)

組み込み型のBranchを作る

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()

TGraphなど、c配列を引数にする場合

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)

(自作スクリプト)pyrootコマンド

rootコマンドをPyROOTで再現させてみた。

普段はPyROOT使っているけど、rootファイル開くときは面倒くさいのでrootコマンド使っているという人いませんか?

もうそんな必要はありません。

このコマンドを使えば、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

特徴としては

  • MacroPathにあるrootlogon.pyを探して実行する
    • rootlogon.pyと読み込む対象のスクリプトはglobal空間を共有させている(つまりrootlogon.pyで定義したオブジェクトをスクリプトで使用することが出来る)
  • スクリプトのglobal空間はpyrootスクリプトの空間とは別にしている(同名のオブジェクトがあっても誤動作とかはないはず)。
    • pyrootコマンドでrootファイルやc++のコードをpythonスクリプトと同時に読み込んでも、__main__をインポートしない限りはスクリプトの中では使えない(global空間をわけているので)
  • ~/.pyroot_historyの名前で履歴ファイルを作る
  • スクリプト読み込み後、interpreterを起動(from ROOT import *する)
  • pyroot [n]の数字は式の評価で値が増える。文だと増えない(printとか代入とか)。文の場合の増やし方がわからない。

起動時間とかはあまり気にせず作った。私の環境では普通のrootのコマンド使うより3倍ぐらい時間がかかる。

study/software/root/pyroot.txt · 最終更新: 2014/02/16 02:17 by kamo

ページ用ツール