Scalaで書いたコードをMATLABから呼ぶ

MATLABは研究でよく使われている。既存のコードも多い。ただプログラマにとっては、MATLABの文法や関数の作法が特殊で書きづらく生産性が低く感じる。数学や統計の関数はともかく、ファイル操作などのユーティリティ関数をMATLABのために新たに覚えるのが面倒。また速度にも不満がある。手動で行列要素を操作したりすると、我慢できない遅さになることがある。組み込みの行列演算は速いのだが。

というわけで、Scalaで外部プログラムを書いてMATLABと連携させてみた。かなり簡単にできた(Mac OSX Lion、Scala 2.9.1とMATLAB R2011bを使用)。

  • 流れ
    1. Scalaでプログラムを書き、Jarファイルを作る。
      1. MANIFEST.MFファイルにClass-Path: scala-library.jarの1行を追加
    2. scala-library.jarファイル(Scalaのバイナリ配布物の/lib内にある)をJarと同じフォルダにコピー
    3. MATLABにてjavaaddpathコマンドで、出来たJarファイルを追加する。
    4. 必要ならばMATLABのJavaヒープサイズを拡大。

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上で

  1. 使用しているScalaオブジェクトをclear
  2. javarmpath ScalaTest.jarで一回消し、
  3. 再度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のデータがどう変換されるのかなど、まだ分からないこともある。