ラベル Java の投稿を表示しています。 すべての投稿を表示
ラベル Java の投稿を表示しています。 すべての投稿を表示

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

}

2014年9月13日土曜日

GradleのApplicationプラグインで生成したスクリプトが動かない


GradleのApplicationプラグインで、生成したスクリプトが実行出来ませんでした。原因は、パスが通ってないことが原因でした。
生成されたスクリプトのパスを通している部分を見ると、依存関係のあるJARファイルには全てパスが通ってますが、$APP_HOME/libにパスがないじゃありませんか。これでは、依存するクラスファイルにはパスが通りません。

僕は、以下の方法でこの問題を解決しました。

build.gradleの編集

build.gradleに以下の記述を追加します。

build.gradle
10 
11 
12 
13 
14 
15 
16 
17 
task modifyscripts( dependsOn: startScripts ) {
  
  outputs.dir startScripts.outputDir

  doLast {    
    def uf = file(startScripts.getUnixScript())
    uf.write( uf.text.replace('CLASSPATH=$APP_HOME''CLASSPATH=$APP_HOME/lib:$APP_HOME') )
    
    def wf = file(startScripts.getWindowsScript())
    wf.write( wf.text.replace('CLASSPATH=%APP_HOME%''CLASSPATH=%APP_HOME%\\lib;%APP_HOME%') )
  }
  
}

applicationDistribution.from(modifyscripts) {
  into "${installApp.destinationDir}/bin"
}

これで、$APP_HOME/libにもパスが通るようになります。

2013年12月7日土曜日

[Java][Scala] 浮動小数点の有効桁数と同値性

浮動小数点で掛け算や割り算を行うと微妙に正確な値からずれていって困っちゃいますよね。例えば、いろいろ計算した結果、机上ではぴったり1になるはずなのに、コンピュータ上では、0.9999999になってたりとか。こんなとき困るのが同値性を検証するときです。こっちは1だと思ってるので、とうぜん、
if( x == 1.)
とかで比較したくなりますよね。でも実はxが0.999999だったりしてfalseになっちゃったりします。でも、これほぼ1やん!って。trueにしよて!ってなりますよね。こんなときに、上位何桁まで一致してたら同じって判断してくるようなものないでしょうか。

どうやらあるようです。

そりゃそうですよね。こんなシチュエーションってよくあると思いますから、解決方法もちゃんと用意されているはずですよね。
参考になったのが、このサイトです。

このサイトで言ってるのは、java.lang.Mathクラスのulpメソッドを使えってことです。簡単に言うと、このメソッドは、浮動小数点の分解能を返してくれます。精度って言ってもいいかもしれません。いくつかの例で試してみましょう。
scala> java.lang.Math.ulp(1.0f)
res2: Float = 1.1920929E-7
1.0fの場合は、10-7くらいの精度をもってるようです。
次は大きい数字で試してみましょう。
scala> java.lang.Math.ulp(87654321.0f)
res3: Float = 8.0
107くらいの大きな数の場合は、100くらいの精度のようです。
次はものすごく小さい数字です。
scala> java.lang.Math.ulp(0.00000001234567f)
res6: Float = 8.881784E-16
10-8くらいの小さな数の場合は、10-16くらいの精度のようです。
こうやってみると、Float型は、総じて、だいたい上位7,8桁くらいまで表現できる能力を持っているようですね。このメッソドを使えば、上位から好きな桁数で簡単に比較できそうですね。

もっとも細かく比較する場合は、
if( Math.abs( x - y ) < Math.ulp( x ) )
とすればよさげですね。

もっと精度を低くしたければ、
if( Math.abs( x - y ) < Math.ulp( x )*10 )
のようにulpを10倍、100倍としていけば、どんどん粗い桁で同値性を検証できます。

2013年5月11日土曜日

Scala + SBTでJavaFxを使う

Scala + SBTでJavaFxを使う

Scala + SBTの環境でJavaFxを使って、単純に「Hello World」と表示させてみました。
まずは、SBTからアプリを実行させ、ウィンドウに「Hello World」と表示させます。
次に、ブラウザでこのアプリを表示させてみます。

作業ディレクトリ

今回は、HelloWorldFxというディレクトリの中で作業します。

SBTのダウンロード

次のURLからSBTをダウンロードしてきます。
SBTの現在の最新バージョンは、0.12.3でした。

今回は、ZIP形式をダウンロードしてきました。
そのZIPファイルを適当な場所に解凍します。解凍したディレクトリ中にあるbinディレクトリの中身をすべてHelloWorldFxディレクトリの中にコピーします。

ディレクトリ構成は、以下のようになりました。


$ ls -R
.:
classes  jansi.jar  sbt-launch-lib.bash  sbt-launch.jar  sbt.bat  src  win-sbt

./classes:
SbtJansiLaunch.class

./src:
main

./src/main:
scala

./src/main/scala:
Main.scala

SBTでJavaFxアプリのコンパイルに必要なJARファイル

SBTでJavaFxアプリをコンパイルするには、
jfxrt.jar
が必要です。
このファイルは、Javaがインストールされているディレクトリ(C:\Program Files\Java\jre7など)のlibディレクトリの中にあります。
Javaのインストール時にJavaFxが標準でついてくるのは、Java7の途中からだったと思うので、なければ最新のJavaをインストールする必要があります。

このjfxrt.jarをHelloWorldFx/libにコピーすれば、コンパイルは通るようになるはずです。

ソースコードの場所

ソースコードは、HelloWorldFx/src/main/scala/Main.scalaに作成します。
ウィンドウに「Hello World」と表示させるだけの単純なアプリです。

Main.scala
import javafx.application.Application
import javafx.stage.Stage
import javafx.scene.Scene
import javafx.scene.layout.BorderPane
import javafx.scene.control.Label

class Main extends Application {

  override def start( stage:Stage ) {
    stage.setTitle("HelloWorld")
    stage.setScene(new Scene(new BorderPane {
      setCenter( new Label("Hello World") )
    }))
    stage.show
  }

}

object Main {

  def main( args:Array[String] ) {
    Application.launch( classOf[Main], args:_* )
  }

}

コンパイル

コンパイルします。
$ ./sbt.bat compile

実行に必要な環境変数を設定する

コンパイルは通っても、これだけでは実行できません。
実行するには、$JRE_HOME/binにパスを通す必要があります。

実行

実行します。
$ ./sbt.bat run

ウィンドウに「Hello World」と表示されていれば成功です。

ブラウザで実行する

つぎに、ブラウザ上でアプリを実行させてみます。

用意するもの

  • JARファイル:アプリのJARファイルと、scala-library.jarです。sbtで作る。
  • JNLPファイル:デプロイ情報を記したXMLファイルです。javafxpackagerで作る。
  • HTMLファイル:JavaScriptを記述したHTMLファイル。javafxpackagerで作る。
JNLPとHTMLは、javafxpackagerが作成してくれます。
javafxpackagerは、Java SDKのインストールディレクトリのbinディレクトの中にあります。
このディレクトリへパスを通しておくと良いでしょう。

javafxpackagerを実行すれは、JNLPとHTMLファイルを作成してくれますが、
実行には、scala-library.jarとアプリのJARファイルが必要です。

アプリのJARファイルの作成

アプリのJARファイルは、sbtで簡単に作成できます。

$ ./sbt.bat package

とすれは、HelloWorldFx/target/scala-<version>/に作成されます。
あとでjavafxpackagerで使いやすいように、作成されたjarファイルを
HelloWorldFx/src/main/resourcesにコピーしておきます。

scala-library.jarをコピー

scala-library.jarは、$HOME/.sbt/boot/scala-2.9.2/libにあります。
これも、HelloWorldFx/src/main/resourcesにコピーします。

JNLPファイルとHTMLファイルの作成

JNLPとHTMLを作成する準備が整ったので、javafxpackagerを使ってJNLPとHTMLを作成します。
オプションの「-srcdir」でHelloWorldFx/src/main/resourcesにあるファイルを指定します。
出力先は、「-outdir」で指定します。今回はsrc/main/webappにしました。
実行するmain関数のあるクラスを、「-appclass」で指定します。

$ javafxpackager -deploy -outdir "src\main\webapp" -outfile helloworldfx -width 100 -height 100 -name HelloWorldFx -appclass Main -v -srcdir src\main\resources

HTMLファイルがsrc/main/webappに作成されているはずです。
作成されたHTMLファイルをブラウザで表示させれば、完了です。

※セキュリティの設定でブラウザで表示できない場合もあります。そのときは、コントロールパネルからJavaのセキュリティ設定を変更してください。

2011年12月30日金曜日

LiftでCassandraを使ってみる

LiftでCassandraを使ってみる


NoSQLのDBであるCassandraをScalaで試した時のメモ。


Cassandraのダウンロード


$> wget http://ftp.jaist.ac.jp/pub/apache//cassandra/1.0.6/apache-cassandra-1.0.6-bin.tar.gz


解凍


$> tar zxvf apache-cassandra-1.0.6-bin.tar.gz


サーバの起動
cassandraの設定はconf/cassandra.yamlで設定されていて、
デフォルトでは、単一モードで起動するようになっているようだ。
なので、今回は何も変更を加えずにそのまま起動。

$> cd apache-cassandra-1.0.6 
$> ./bin/cassandra -f 


管理者権限で実行する必要があるかも。


CLIの起動
CLIはcassandraをコマンドラインで操作する為のクライアント。


CLIのインタプリタに入る。
$> ./bin/cassandra-cli -h localhost 


キースペースを表示する。
[default@unknown] show keyspaces; 

行末にセミコロンを付けるのを忘れずに。


Cassandraのデータ構造
クラスター:Cassandraのインスタンス
キースペース:データベースのようなもの
カラムファミリー:テーブルのようなもの


キースペースは、たいていは1つのアプリに1つ定義されるものらしい。
Scalaから操作するとき用に、あらかじめキースペースを定義しといたほうがよい感じ。


Liftのダウンロード
Liftは、Scalaで書かれたウェブフレームワーク。
Liftには、Scalaのビルドツールのsbtが付いているので、
ウェブアプリを作らないという時でも、ビルドするのに便利。

$> wget https://github.com/lift/lift_24_sbt/tarball/master -O lift2.4.tar.gz


解凍
$> tar zxvf lift2.4.tar.gz

jettyの起動
今回は、関係ないけど、とりあえず、jettyを起動。

$> cd lift-lift_24_sbt-05be36f/scala_29/lift_basic/
$> ./sbt
$> update
$> jetty-run

ブラウザでhttp://localhost:8080が表示されてればOK。

Hectorの取得


mavenのように、project/build/LiftProject.scalaに以下の記述を追加する。
"me.prettyprint" % "hector-core" % "0.8.0-2"

sbtのupdateコマンドでhectorをとってくる。
$> ./sbt
$> update


HectorでColumnの作成と取得
src/main/scala/TestCassandra.scalaを作成して以下のように編集する。
以下のコードは、ほとんどCassandra本家のサイトのサンプルのコピー。

import me.prettyprint._
import cassandra._
import service._
import hector.api._
import factory._
import ddl._
import template._
import serializers._


import java.util._


object TestCassandra {


  def main( args:Array[String] ) {


    val clusterName = "Test Cluster" // クラスターの名前
    val replicationFactor = 1 // リプリケーションするノードの数
    val keyspaceName = "test_keyspace" // キースペースの名前
    val cfName = "test_cf3" // カラムファミリの名前


    // クラスターの取得。名前はcassandraのクラスター名と同じじゃなくてよい。
    val cluster = HFactory.getOrCreateCluster(clusterName, "localhost:9160")


    // カラムファミリの定義
    val cfDef = HFactory.createColumnFamilyDefinition(keyspaceName, cfName, ComparatorType.BYTESTYPE)


    // カラムファミリの追加。既にあれば、例外を投げる
    try {
      cluster.addColumnFamily(cfDef, true )
    } catch {
      // 例外処理
    }


    // キースペースの定義
    val keyspaceDef = HFactory.createKeyspaceDefinition(keyspaceName, ThriftKsDef.DEF_STRATEGY_CLASS, replicationFactor, Arrays.asList(cfDef))


    // キースペースの追加
    if( keyspaceDef == null ) cluster.addKeyspace( keyspaceDef, true )
    
    // キースペースインスタンスの取得
    val ksp = HFactory.createKeyspace(keyspaceName, cluster)


    // カラムの更新
    val template = new ThriftColumnFamilyTemplate[String, String](ksp, cfName, StringSerializer.get(), StringSerializer.get());
    val updater = template.createUpdater("a key");
    updater.setString("domain", "www.datastax.com");
    updater.setLong("time", System.currentTimeMillis());


    template.update(updater);


    // カラムの取得
    val res = template.queryColumns("a key");
    val value = res.getString("domain");


  }


}

実行
sbtのrunコマンドでコンパイルと実行をする。

$>./sbt
$> run

カラムの更新と取得ができてればOK。