読者です 読者をやめる 読者になる 読者になる

ローグウェーブソフトウェアのブログ

開発をシンプルに 安全で高品質のコードを 素早くお客様のもとへ

JavaコードをScalaから呼び出すのは簡単か?(2) - CodeBuzzから

IMSL 記事紹介 製品とサービス

前回の記事に引き続きローグウェーブのEd StewartがCodeBuzzに投稿したScalaJMSLについての記事をご紹介します。

blog.roguewave.jp

目次

JavaコードをScalaから呼び出すのは簡単か?(2)

blog.klocwork.com

1人のJava開発者として、私がこのScala言語を学ぶ時に知ったこと - 馴染み深いJavaライブラリを呼び出すことを通じて - をお伝えします。前回の記事では基礎をカバーしました。今回は問題に応じてどのようにユーザ定義関数を作成するのか、またJMSLのチャート機能で結果を見やすくする方法を紹介しようと思います。

ユーザ定義関数

ユーザ定義関数はどんな数値計算ライブラリにとっても必要不可欠な存在です。多くの数値計算や統計解析がデータを渡すことで実行可能ではありますが、多くのシナリオではそれぞれの問題に対してユーザーごとに個別の解法 - 方程式の形や係数 - が必要です。方程式の根を求めたり最適化を行う問題はそのよい例です。JMSLライブラリはユーザ定義関数を受け付けるインタフェースを採用しています。APIはメソッドのシグネチャ等の実装に必要なインタフェイスの名前を定義します。ユーザーが定義した関数はソルバーのアルゴリズムによって内部的に繰り返し呼ばれることになります。

最初の例としてJMSLはZerosFunction アルゴリズムを使って問題を解きます。このクラスはユーザーが指定した関数の根を求めます。Javaではインタフェースを実装するには単にオブジェクトを作成するだけです。今回の例ではインタフェイスは ZeroSystem.Functionという名前で、2つの倍精度の配列を受け取りvoidを返し、fという名のpublicメソッドを持ちます。JMSLライブラリのクラスにはユーザ定義関数のインタフェイスを活用するクラスが様々な形で存在します。値を返す必要がある場合もありますが、今回の例では文字通りxを受け取って計算を行う関数f(x)を定義することになります。

ドキュメントに記載された問題例は以下のような連立方程式です。

{ \displaystyle
f_1(x)=x_1+{e^{x_1-1}}+{(x_2+x_3)}^2-27 \\
f_2(x)=e^{x_2-2}/x_1+{x_3}^2-10 \\
f_3(x)=x_2+\sin(x_2-2)+{x_2}^2-7
}

Javaではこのユーザ定義関数は以下のように実装されます。

   ZeroSystem.Function fcn = new ZeroSystem.Function() {
        public void f(double x[], double f[]) {
            f[0] = x[0] + Math.exp(x[0] - 1.0)
            + (x[1] + x[2]) * (x[1] + x[2]) - 27.0;
            f[1] = Math.exp(x[1] - 2.0) / x[0] + x[2] * x[2] - 10.0;
            f[2] = x[2] + Math.sin(x[1] - 2.0) + x[1] * x[1] - 7.0;
        }
    };

Scalaに翻訳してもほとんど同じです。

val fcn = new ZeroSystem.Function {
    def f(x: Array[Double], f: Array[Double]) {
        f[0] = x[0] + Math.exp(x[0] - 1.0)
            + (x[1] + x[2]) * (x[1] + x[2]) - 27.0;
        f[1] = Math.exp(x[1] - 2.0) / x[0] + x[2] * x[2] - 10.0;
        f[2] = x[2] + Math.sin(x[1] - 2.0) + x[1] * x[1] - 7.0;
    }
};

このサンプルの残りの部分でZeroSystemオブジェクトを実際に作成します。初期推定値を与え、根を求めて出力します。

val zs = new ZeroSystem(3)
zs.setGuess(Array(4.0, 4.0, 4.0))
val zsx = zs.solve(fcn)
new PrintMatrix("ZeroSystem solution").print(zsx)
ZeroSystem solution
   0
0  1
1  2
2  3

面白いことに、Scalaでも同様にtraitを定義することができます。このメソッドはやや長く、Javaらしくもありませんが、こちらを好むユーザーもいるでしょう。以下では繰り返しを避けるためにソルバー MinUnconが使われています。この関数は非拘束型の最小化アルゴリズム {f(x)={e}^x-5x} を解きます。

trait muFcn extends MinUncon with MinUncon.Function {
        def f(x: Double): Double = {
            Math.exp(x) - 5.0 * x
        }
    }

val mu = new MinUncon with muFcn
mu.setGuess(0.0)
mu.setAccuracy(0.00001)
println("\nMinUncon solution = " + mu.computeMin(mu))
MinUncon solution = 1.6094353603002598

traitが最初に定義され、継承されるクラスと実装されるインタフェースを明示的に宣言しています。MinUnconオブジェクトがインスタンス化されたらtraitはwithキーワードで参照されます。関数呼び出しmu.computeMin(mu)は再帰的に見えますが、muオブジェクトそのものはインタフェースを実装しています。これはJavaでクラスを宣言してインタフェースを実装し、thisを引数としてcomputeMin()メソッドに渡しているのと同じことです。

チャート(グラフ)描画

JMSLライブラリには数値アルゴリズムの他にチャート機能もあります。一般的にチャート機能はより大きなユーザーアプリケーションの一部でJPanelChartをコンテナとしてJPanelが使えるところならどこでも使用可能です。JSPアプリケーションに対してはChartServletが提供され、Java3Dを使って3次元グラフィクスもサポートしています。

今回のScalaを使った例では、簡単のために自前のメニューバーを持ったJframeのような振る舞いをするJFrameChartを使います。

object MyChart extends JFrameChart {
    var a = new AxisXY(this.getChart)
    var d = new Data(a, Array(3.0, 6.0, 8.0, 2.0, 4.0))
    
    var ann = new Annotation(a, "Scala!", 3.5, 5.5)
    ann.setTextColor(java.awt.Color.blue)
}
MyChart.setVisible(true)

このプログラムではシングルトンを生成しているので何かをインスタンス化する必要はなく、setVisible()メソッドもインスタンスのではなくオブジェクトのものとして呼ばれます。注釈がグラフ内の指定された座標に表示されています。

f:id:RWSJapan:20160609140914p:plain

まとめ

Scalaの機能やクロスランゲージを調べるのにJMSL数値ライブラリを使用しました。staticメソッド、インスタンスクラス、ユーザ定義関数、チャート機能などがサンプルとともに示されました。Scalaの知識が全く無いJava開発者として、この2つの言語を並べて見るというのは大変なことではありませんでした。

今回使ったコード

import com.imsl.math.{Sfun, LU, Bessel, ZeroPolynomial}
import com.imsl.math.{ZerosFunction, ZeroSystem}
import com.imsl.math.{MinUncon, MinConGenLin}
import com.imsl.chart._

object HelloJmsl {
    def main(args: Array[String]): Unit = {
        /* easy case of static method */
        println("\n -- Static method call --")
        val erf2 = Sfun.erf(0.5)
        println("erf(0.5) = " + erf2)

        /* instantiate a class with vector input and output */
        println("\n -- Class instance --")
        val zp = new ZeroPolynomial
        val coef = Array(1.0, 2.0, 5.0)
        val roots = zp.computeRoots(coef)
        println("x_1 = " + roots(0))
        println("x_2 = " + roots(1))
        
        /* instantiate a class that requires a matrix */
        println("\n -- Class instance --")
        val a = Array(
            Array(1.0, 3.0, 3.0), 
            Array(1.0, 3.0, 4.0), 
            Array(1.0, 4.0, 3.0))
        val b = Array(12.0, 13.0, 14.0)
        val lu = new LU(a)
        val x = lu.solve(b)
        println("LU answer = " + x(0) + "\t" + x(1) + "\t" + x(2))
        new com.imsl.math.PrintMatrix("x").print(x)
        println("LU condition number = " + lu.condition(a))
        
        /* User-defined function */
        println("\n -- User-defined function --")        
        val zsFcn = new ZeroSystem.Function {
            def f(x: Array[Double], f: Array[Double]) {
                f(0) = x(0) + Math.exp(x(0) - 1.0) + (x(1) + x(2)) * (x(1) + x(2)) - 27.0
                        f(1) = Math.exp(x(1) - 2.0) / x(0) + x(2) * x(2) - 10.0
                                f(2) = x(2) + Math.sin(x(1) - 2.0) + x(1) * x(1) - 7.0
            }
        }
                
        var zs = new ZeroSystem(3) // with zsFcn
        zs.setGuess(Array(4.0, 4.0, 4.0))
        var zsx = zs.solve(zsFcn)
        new com.imsl.math.PrintMatrix("ZeroSystem solution").print(zsx)

        /* Using a trait for a user-defined function */
        println("\n -- User-defined function with a trait --")
        trait muFcn extends MinUncon with MinUncon.Function {
            def f(x: Double): Double = {
                Math.exp(x) - 5.0 * x
            }
        }
        val mu = new MinUncon with muFcn
        mu.setGuess(0.0)
        mu.setAccuracy(0.00001)
        println("\nMinUncon solution = " + mu.computeMin(mu))
    
        /* charting example */
        println("\n -- Chart example (blocking window should open) --")  
      object MyChart extends JFrameChart {
            var a = new AxisXY(this.getChart)
            var d = new Data(a, Array(3.0, 6.0, 8.0, 2.0, 4.0))
            
            var ann = new Annotation(a, "Scala!", 3.5, 5.5)
            ann.setTextColor(java.awt.Color.blue)
        }
        
        // note that this object (singleton instance of a class) but it 
        // could be a class then you'd need to instantiate it with     
        // val c = new MyChart and display with c.setVisible(true)
        MyChart.setVisible(true)   
    }
}

Ed Stewart

編集後記

Scala(スカーラーと発音するようですね)はTwitterLinkedIn (Scala at LinkedIn)、英ガーディアン紙などを始めとするさまざまな大規模なWebシステムで使用されています。

先日の7.3リリースで、JMSLの統計機能 - サポートベクターマシンや決定木などの統計解析の機能が一段と充実しました。 blog.roguewave.jp こうしたハイエンドな機能をScalaからマーシャリングやデータ変換なしでそのまま使えるということはとても便利なことだといえます。Javaによる数値計算、統計解析の可能性はまだまだ広がりを見せています。

ローグウェーブ セールスエンジニア 柄澤(からさわ)