2015年2月14日土曜日

ROOTのJAVAバインディングを作りました


ROOTとは、高エネルギー物理の分野で広く使われている統計解析フレームワークです。ヒストグラムやグラフを描画することはもちろん、データを解析したり、保存したりすることができます。僕は長らくROOTを使ってきましたが、少しだけ不便に思っていたことがあります。それは、ROOTがC++で記述されているということです。つまり、ROOTはC++のライブラリなので、使う側もC++で書かなくてはいけません。C++の何が嫌ってメモリ管理をしなきゃいけないところです。多くの言語がガベージコレクションを実装している中で、今更自分でメモリ管理をする気にはあまりなれません。最近では、C++も少し改善されて、スマートポインタが導入されて、ある程度楽にはなりました。ROOTもバージョン6からC++11に対応しているようです。でも、やっぱり他の言語で書きたい。簡単な解決策としては、ROOTが提供しているPythonとRubyのバインディングを使うことです。僕は、しばらくROOTのRubyバインディングを使っていたんですが、やはり普段使うことが多いJVM言語(JavaやScala)で使うことができたらなぁと常々思ってました。そこでネットを探してみました。あることはあるんです。ただ、古くてROOTのバージョン3しか対応してなかったり、ROOTとはインターフェースが大きく変わっていたりして満足できるものではありませんでした。しばらくRubyを使うことで諦めていたんですが、もうこうなったら一念発起し自分で作ることにしました。

ここからダウンロードできます。 http://java-root.appspot.com
Javadocです。 http://java-root.appspot.com/javadoc/index.html

設計概要

とにかくよく使うクラスは、実装しようと思います。ヒストグラムやグラフ、描画ツール、データアクセスのようなものは全て実装します。それに依存するようなクラスもできるだけ実装します。例えば、TH1Fを実装するならTAxisも実装しなければいけません。TH1Fは、TAxisを返すGetXaxisメソッドを持っているからです。ほかには、TTreeを実装するならTBranch、TLeafなども必要でしょう。とにかくこのような依存しているクラスも実装対象とします。
また、Javaでは表現できないことは、実装しません。例えばプリミティブの参照渡し。Javaでは、プリミティブを参照渡しすることはできません。このような引数や戻り値を持つメソッドは実装しません。ただ、例外的にTTree::BranchやTTree::SetBranchAddressでは、擬似的にポインタを表現して、これらのメソッドを扱えるようにします。
クラスの継承関係は、できるだけ再現します。ROOTでは多重継承しているクラスが多く見られますが、Javaではクラスの多重継承はすることができません。そこでJava版では、すべてのROOTのオブジェクトは、インターフェースにすることにしました。TObjectもTH1Fもインターフェースです。インスタンスの生成は、ファクトリメソッドで生成することします。
ROOTのメソッド名は、大文字から始まりますが、これはJavaのメソッド名の慣例と違うので、Javaの慣例に従ってメソッド名は小文字で始めることにします。

ROOTのリフレクションを使ってJNIコードを自動生成

JavaからC++にアクセスする方法としてJNIを採用することにします。JNIは、C++のコードとJavaの対応づけをするコード記述する必要がありますが、ROOTのクラスは、めちゃめちゃ多い。これらのクラスにアクセスするJNIコードをいちいち手で実装していっては大変です。各クラスに対して記述するコードは、だいたい似たようなものです。同じようなことの繰り返しを自動化するのは、コンピュータの得意分野です。幸いROOTではリフレクションが使えるので、メソッドの名前や引数などを取得できます。これによってJNIコードを自動生成できます。

TTree

ROOTではTTreeにデータを保存するときは、ブランチにポインタのポインタを渡して、各イベントでそのポインタのアドレスをデータのアドレスに書き換えることで、値を受け渡ししています。
TLorentzVector *= new TLorentzVecotr();
tree->Branch( "x""TLorentzVector"&);

Javaでは、ポインタがないので、TPointerクラスを導入してこれを実現しました。
TLorentzVector x = newTLorentzVecotr();
TPointer ptr = newTPointer(x);
tree.branch( "x""TLorentzVector", ptr );

このままでは、プリミティブが保存できないので、プリミティブのクラスも導入しました。作成したクラスは、
  • TInt
  • TLong
  • TFloat
  • TDouble
です。

TInt x = newTInt();
TPointer ptr = newTPointer();
tree.branch( "x""TInt", ptr );

Example

次のコードは、ヒストグラムを描画するコードです。

10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
20 
21 
22 
23 
24 
import net.blackruffy.cern.root.*;
import static net.blackruffy.cern.root.ROOT.*;

public class DrawTH1F {

  public static void main( String[] args ) {

    final TApplication app = newTApplication( "app" );

    final TH1F h = newTH1F( "h""h", 100, -10., 10. );
    
    h.fillRandom("gaus", 100);
    
    final TCanvas c = newTCanvas( "c""c", 800, 600 );
    
    h.draw();
    
    app.run();
    
    c.destroy();
    h.destroy();
  }

}