Scalaで書いたコードをMATLABから呼ぶ
MATLABは研究でよく使われている。既存のコードも多い。ただプログラマにとっては、MATLABの文法や関数の作法が特殊で書きづらく生産性が低く感じる。数学や統計の関数はともかく、ファイル操作などのユーティリティ関数をMATLABのために新たに覚えるのが面倒。また速度にも不満がある。手動で行列要素を操作したりすると、我慢できない遅さになることがある。組み込みの行列演算は速いのだが。
というわけで、Scalaで外部プログラムを書いてMATLABと連携させてみた。かなり簡単にできた(Mac OSX Lion、Scala 2.9.1とMATLAB R2011bを使用)。
- 流れ
- Scalaでプログラムを書き、Jarファイルを作る。
- MANIFEST.MFファイルにClass-Path: scala-library.jarの1行を追加
- scala-library.jarファイル(Scalaのバイナリ配布物の/lib内にある)をJarと同じフォルダにコピー
- MATLABにてjavaaddpathコマンドで、出来たJarファイルを追加する。
- 必要ならばMATLABのJavaヒープサイズを拡大。
- Scalaでプログラムを書き、Jarファイルを作る。
Scalaプログラム作成
matlabtest.scala
package nebuta.matlablib import scala.math._ object Main { def hello: Unit = { println("Hello.") } def main(args:Array[String]): Unit = { hello } } object Filter { type Double3D = Array[Array[Array[Double]]] def numel(mat: Double3D): Int = mat.length * mat(0).length * mat(0)(0).length def matsize(mat: Double3D): (Int,Int,Int) = (mat.length, mat(0).length, mat(0)(0).length) def matsum(mat: Double3D): Double = mat.flatten.flatten.sum def matmean(mat: Double3D): Double = matsum(mat)/numel(mat) def normalize(a: Seq[Double]): Seq[Double] = { val s = a.sum a.map(_/s) } } class Complex(val re: Double, val im: Double) { def abs: Double = sqrt(re*re + im*im) def arg: Double = atan2(im,re) }
MANIFEST.MF
Manifest-Version: 1.0 Created-By: 1.6.0_29 (Apple Inc.) Main-Class: nebuta.matlablib.Main Class-Path: ./scala-library.jar
fsc matlabtest.scalaでコンパイル後フォルダ構造は以下のようになる。
$ tree . ├── MANIFEST.MF ├── nebuta │ └── matlablib │ ├── Complex.class │ ├ .... (class files) │ └── Main.class └── matlabtest.scala
Jar作成
$ jar cvfm ../ScalaTest.jar MANIFEST.MF .
これで親フォルダにScalaTest.jarができる。
scala ScalaTest.jarあるいはjava -jar ScalaTest.jarで実行してみる。
$ cd .. $ java -jar ScalaTest.jar Hello.
上手くいった。ScalaのライブラリがJarのマニフェストファイルで指定されているので、単なるjavaコマンドで実行できる。まあここまでは単なるScalaの話。
MATLABで読み込み
MATLABでScalaTest.jarのあるフォルダに移動し、以下のコマンドでJarが読み込まれる。
>> javaaddpath ScalaTest.jar
あとは普通に呼べる。
>> import nebuta.matlablib >> Main.hello Hello. >> a = Complex(1,1); >> [a.re a.im] ans = 1 1 >> a.abs ans = 1.4142 >> b = rand(3,3,3); Filter.matsum(b) ans = 12.5841
なお、Jarを更新したときは
MATLAB上で
- 使用しているScalaオブジェクトをclear
- javarmpath ScalaTest.jarで一回消し、
- 再度javaaddpath ScalaTest.jar
する必要がある。
それを自動化したのが以下のMATLABコマンド。コピペしてEnterで順に実行される。
javarmpath ScalaTest.jar cd scalatest [status,result] = system('/opt/local/share/scala-2.9.1.final/bin/fsc matlabtest.scala','-echo'); if status == 0 system('/usr/bin/jar cfm ../ScalaTest.jar MANIFEST.MF .'); cd .. javaaddpath ScalaTest.jar else cd .. disp('Compile error.') end
MATLABのsystemコマンド内ではfscやjarのフルパス(bashのwhich fscで調べる)を指定する必要がある。
ヒープサイズの拡大
デフォルトだと128MBとかですぐにオーバーしてしまう。以下を参照してMATLABの設定を変更。
http://www.mathworks.com/support/solutions/ja/data/1-B8OO49/index.html?product=ML
Macの場合はアプリケーションを右クリックしてパッケージの内容を表示し、java.optsファイルに1行、-Xm2048mなどと加える。
まとめ
いったんScalaで書く方法を確立しておけば、今後はいろいろ出来て楽そう。MATLABのデータとJavaのデータがどう変換されるのかなど、まだ分からないこともある。