ROOT ユーザーが python を使う上で知っていると便利な Tips を紹介。
Contents
脱 ROOT ライブラリ?
長く C++ で ROOT をベッタリ使っていた人が、
python をメインにして多くの便利なモジュールに慣れ、
色んな意味で扱いの面倒(後述)な (py)ROOT を手放そうと思う時に残る課題は次のようなものだと(勝手に)思う。
- まともなフィッティング
- まともな Minimizer
- I/O として TFile, TTree のみ使いたい
これらの代替案を掲載する。
これらのモジュールは conda-forge などから一発でサクッと入るので扱いやすいです。
フィットをまともにしたい
ヒストグラムなどを真面目に (誤差を考慮して) フィットする際に、
scipy.optimize.curve_fit
なんて使うとエライ目に合う(下図参照)。
そういう場合は lmfit
を用いれば良い。
lmfit
はいくつかの定義済み 'model' を備えているので、
これらを組み合わせることで誤差をまともに扱う ROOT と consistent なフィットができる。
次の図は mean: 100, sigma: 15 の正規分布で 500個の乱数を生成した際に得られたヒストグラムを、
lmfit, scipy, pyROOT でフィットして比較したものです。
lmfit, ROOT は数字で見てもほぼ同じ値になっていますが、
scipy.optimize.curve_fit
のみ異なるフィット結果になっています。
Minimizer が使いたい
もう少し凝ったフィッティングや、複数パラメータの最適化を行いたい場合は、
ROOT だと TMinuit を使ったりする。
そんな場合は iMinuit
を使えば良い。
TFile, TTree のみデータ I/O として使いたい
何らかの(C++/ROOT で書かれた) データ収集系が別に存在して、
そこから得られる TFile, TTree のデータを解析だけしたい場合、
つまり単に I/O として TFile, TTree を使いたい場合は、uproot
が使える。
--> [uproot] Python で ROOT ファイルを簡単に扱う
(py)ROOT は python 複数バージョン同時対応にできる
動機: (py)ROOT は面倒?
冒頭で (py)ROOT は 扱いが面倒
と書きました。
それは (py)ROOT は python のマイナーバージョンでも変わると動かなくなるからです。
conda-forge などから ROOT を1コマンドで入れることもできますが、
python の ver が異なる仮想環境毎に数GB 入れ直すことになるので、
容量的にも手間的にも避けたいところです。
代わりに python 側の verを固定する方法もありますが、
今度は python ver が固定されて、機械学習系など比較的新しい pythonモジュールを追いかけるのが困難になります。
また、jupyter-hub/lab なんかを間に挟むと、どの (py)ROOT を参照しているのかより複雑になります。
時には (cronやシェルなどで) python仮想環境に入り忘れてシステムの python を呼んでハマることも。
だから冒頭で述べたように '脱ROOT' できないかなと一時思うわけです。
確かに前半で紹介したモジュールを用いれば、やりたいことはほとんどできると思います。
私も、やりたいことの 99% はカバーでき、より手軽で便利になったので、
「これで (py)ROOT はもう不要かな」と思いかけました。
でも、一部まだ代替できていないことがあります。
具体的には '多次元(可変長?)配列をTTreeに詰める' 操作です。
(これは私の理解では uproot でまだサポートされていないと思います)
というわけで、アプローチを変えて (py)ROOT の面倒な点を何とかする方向で考えます。
具体的には (py)ROOT を一手間加えて python 複数バージョンに対応させます。
pyROOT 複数バージョン対応方法
例えば私のある環境の場合、以下の python バージョンが環境に存在しています。
- pyhon2.7 (system)
- python3.8 (system)
- python3.10 (miniforge-base)
- python3.11 (miniforge-envs)
これら全てから import ROOT
できるような (py)ROOT 環境を作成します。
(私は ROOT v6.30.02(Ubuntu x86_64), v6.28.06(macOS aarch64) で確認)
ベースとなる ROOT をビルド
まず初めに'ベース' 環境で ROOT をビルドします。
私は miniforge-base な python3.10 を用いて最初のビルドを行いました。
tmva-pyvma
などもビルドしたい場合は numpy, python-devtools など (conda-forge の場合) が必要になるかも。
(Ubuntu/system の python なら apt から python(X)-numpy, python(X)-dev?)
ビルドオプションの個別詳細は本家ページに譲りますが例えば以下のような感じ。
1 2 3 |
$ cmake ../root-6.xx.xx -DCMAKE_INSTALL_PREFIX=/path/to/root -Dxxx=ON -Dyyy=OFF ... [-DPYTHON_EXECUTABLE=/path/to/bin/python] $ cmake --build . -- -jXX $ cmake --build . --target install |
- CMAKE_INSTALL_PREFIX: ビルドした ROOT をインストールしたい path
- PYTHON_EXECUTABLE: pyROOT を対応させたい python の path
(/path/to/miniforge/bin/python, /path/to/miniforge/envs/envName/bin/python, /usr/bin/pythonX など) - (-D)xxx, yyy: 必要なその他のオプション
PYTHON_EXECUTABLE を省略すると PATH で最初に見つかる (path の通った) python になると思いますが、
続けて順に各 python 用にビルドしていくので、明示的に指定した方が無難でしょう。
これで指定した python 用の pyROOT を含む ROOT が、CMAKE_INSTALL_PREFIX にインストールされました。
他に対応させたい python 用のビルド
次に他の対応させたい python 用のビルドを行います。
PYTHON_EXECUTABLE に目的の python を指定すれば良いです。
誤って install
してしまっても問題ないように PREFIX は /tmp/root_vxx.xx などとしておけば良いでしょう。
ビルドディレクトリは使い回しはできないようですので、都度削除して作り直してください。
例えば以下のような感じ。
1 2 3 |
$ rm -rf root_build && mkdir root_build && cd root_build $ cmake ../root-6.xx.xx -DCMAKE_INSTALL_PREFIX=/tmp/root_v6.xx.xx -Dxxx=ON -Dyyy=OFF ... -DPYTHON_EXECUTABLE=/path/to/another/python $ cmake --build . -- -jXX |
ここで cmake --build . --target install
して別のインストール先 (上記の例では /tmp 以下) から操作しても構いませんが、
手元の操作では python2 用のビルドが install
に失敗したので、敢えて install
せずに、
build
した直下から以下のようにして必要なライブラリを 'ベース' をインストールした先へコピーします。
1 |
$ cp -a lib/lib*X_x.so /path/to/root/lib/ |
X
は pythonのメジャーバージョン, x
は同マイナーバージョンです。
したがって例えば python3.11 なら lib/lib*3_11.so
というようになります。
この操作を対応させたい python のバージョンだけ繰り返します。
ROOT 全体のビルドを繰り返すのでそれなりの時間がかかります。
ビルド後の確認
各バージョン用の pyROOT のビルドとコピーを繰り返した後、
${ROOTSYS}/lib 以下の python に関係しそうなライブラリをリストアップすると以下のようになります。
(python2.7, 3.8, 3.10, 3.11 用をビルドした場合)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
/path/to/root/lib $ ls -1 lib*.so | 'grep' -i py libJupyROOT2_7.so libJupyROOT3_10.so libJupyROOT3_11.so libJupyROOT3_8.so libPyMVA.so libROOTPythonizations2_7.so libROOTPythonizations3_10.so libROOTPythonizations3_11.so libROOTPythonizations3_8.so libROOTTPython.so libcppyy2_7.so libcppyy3_10.so libcppyy3_11.so libcppyy3_8.so libcppyy_backend2_7.so libcppyy_backend3_10.so libcppyy_backend3_11.so libcppyy_backend3_8.so |
pythonバージョンを含まない 'libPyMVA' や 'libROOTTPython' を呼ぶとどうなるかについては評価していませんが、
ROOT を import して TFile, TTree, TH1 などの基本的なクラスや操作は、各 python から一通りできているようです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
(base) $ python Python 3.10.12 | packaged by conda-forge | (main, Jun 23 2023, 22:40:32) [GCC 12.3.0] on linux Type "help", "copyright", "credits" or "license" for more information. >>> import ROOT >>> ... (base) $ conda activate py311 (py311) $ python Python 3.11.7 | packaged by conda-forge | (main, Dec 23 2023, 14:43:09) [GCC 12.3.0] on linux Type "help", "copyright", "credits" or "license" for more information. >>> import ROOT >>> ... $ ... $ python3 Python 3.8.10 (default, Nov 22 2023, 10:22:35) [GCC 9.4.0] on linux Type "help", "copyright", "credits" or "license" for more information. >>> import ROOT >>> $ ... |
痒いところ
以上のようにして、少し時間が掛かりますが、pyROOT を複数の python バージョンに対応させることができます。
pyROOT に関係ない余計なビルドを排して、pyROOT 関連だけビルドできればもっと手軽なのですが、
今の所、pythonバージョン毎に全ビルドが必要なのかなと思います。
-Dxxx=OFF
オプションで無効化したおす??
(もし良い手をご存じの方があればご教授ください)
python2系の対応について
私の試した環境: ROOT v6.30.02 で、python2.7 から import ROOT
などとすると、
ROOT v6.32 で python 2系のサポートが打ち切られる旨の警告が表示されます。
1 2 3 4 5 6 |
$ python2 Python 2.7.18 (default, Jul 1 2022, 12:27:04) [GCC 9.4.0] on linux2 >>> import ROOT /path/to/root/lib/ROOT/__init__.py:37: DeprecationWarning: The support for Python 2 in ROOT is deprecated. It will be removed in ROOT version 6.32. "ROOT version 6.32.", category=DeprecationWarning) |