JAVAからNAG数値計算ライブラリを利用する : Example 3

テクニカルレポート

4.3. Example 3

数値積分ルーチン、関数 d01ajc

ここではユーザ提供の評価関数を引数として取る NAG C Library 関数 d01ajc をコールする方法を示す。またデータをCからJavaプロパティにフィードバックする方法についても示す。

内容

  1. NAG C Library マニュアルからの関数プロトタイプ
  2. Javaプログラム中でのネイティブ関数の宣言
  3. Javaプログラムのコンパイル
  4. C用のヘッダファイルの生成
  5. ネイティブ関数のCによる実装
  6. 共用ライブラリ/DLLの作成
  7. プログラムの実行
  8. クイックサマリ

  1. NAG C Library マニュアルからの関数プロトタイプ
  2. C Library マニュアルによれば関数 d01ajc に対するプロトタイプは次のとおりである。

      #include <nag.h>
      #include <nagd01.h>
     
      void d01ajc(double (*f)(double x), double a, double b,
                  double epsabs, double epsrel, Integer max_num_subint,
                  double *result, double *abserr, Nag_QuadProgress *qp,
                  NagError *fail);
    
    関数 d01ajcf(x) の数値積分を実行する。ここに f(x) はユーザから提供される関数である。その際の積分範囲は有限の区間 [a,b] である。

    被積分関数 f(x)d01ajc に対する第1引数として指定されるが、それは次のように宣言される。

      double (*f)(double x)
    
    すなわち一つの倍精度引数を取る関数で、その応答の型も倍精度である。

    引数 epsabsepsrel は積分計算に際しての演算精度の制御に用いられる。引数 max_num_subintd01ajc が区間 [a,b] を分割するに際してのサブ区間数の上限を規定する。積分結果は引数 result を介して戻されるが、それに対する絶対誤差の推定値は abserr 中にセットされる。

    型が Nag_QuadProgress の引数 qp は計算に関するさらなる情報の引渡しに使用される。その中には実際に設定されたサブ区間数や f(x) のコール数が含まれる。

  3. Javaプログラム中でのネイティブ関数の宣言
  4. Example 1Example 2 の場合と同様、NagError 構造体の内容をJavaには戻さないものとする。従ってJavaプログラム内では関数を次のように宣言すれば良い。

      // Declaration of the Native (C) function
      private native int d01ajc(String funName,
                                double a, double b,
                                double epsabs, double epsrel,
                                int max_num_subint); 
    
    これは int を応答として返すメソッドを意味する。ここでは fail 引数は用いず、エラーコードはすべて int 戻り値によって送り返すものとする。

    関数の引数を直接JavaからCに引渡すことはできないので、ここでは文字列引数 funName を介して関数の名前のみを引渡すことにする。この名前を用いてどのようにして実際のJavaで書かれた関数 f(x) にアクセスするかは後述する。

    上記Javaの宣言の中には d01ajc の出力引数が何ら含まれていない点にも注意。出力引数に含まれる情報をJavaに返すための別の方策を考えなくてはならない。

  5. Javaプログラムのコンパイル
  6. 次に示すのはJavaプログラム Quadrature.java のソースコードである。
    public class Quadrature
    {
     
      // Declaration of the Native (C) function
      private native int d01ajc(String funName,
                                double a, double b,
                                double epsabs, double epsrel,
                                int max_num_subint);
     
      // Member variables to hold results returned by d01ajc
      double result, abserr;
      int nFun, nSubInt;
     
      static
        {
          // The runtime system executes a class's static
          // initializer when it loads the class.
          System.loadLibrary("nagCJavaInterface");
        }
     
      // An example function to be integrated.
      private double myFunction(double x)
        {
          double ret;
          ret = x * x * x;
          return ret;
        }
     
      // Another example function to be integrated (this one is from
      // the example program for d01ajc in the NAG C Library manual).
      private double myFunction2(double x)
        {
          double ret;
          ret = x * Math.sin(x * 30.0) /
                   Math.sqrt(1.0 - x * x / (Math.PI * Math.PI * 4.0));
          return ret;
        }
     
      // The main program
      public static void main(String[] args)
        {
          double a, b;
     
          // Create an object of class Quadrature
          Quadrature quad = new Quadrature();
     
          System.out.println();
          System.out.println("Calls of NAG quadrature routine d01ajc");
          System.out.println();
     
          // Integrate the first example function
          a = 0.0;
          b = 1.0;
          System.out.println("Integral of x*x*x");
          quad.Integrate("myFunction", a, b);
     
          // Integrate the second example function
          a = 0.0;
          b = Math.PI * 2.0;
          System.out.println("Integral of x*sin(30*x) / sqrt(1-x*x/(4*PI*PI))");
          quad.Integrate("myFunction2", a, b);
     
        }
     
      // A routine to integrate by calling the native (C) function
      private void Integrate(String functionName, double a, double b)
        {
          double epsabs, epsrel;
          int max_num_subint, resCode;
     
          epsabs = 0.0;
          epsrel = 0.0001;
          max_num_subint = 200;
     
          resCode = d01ajc(functionName, a, b, epsabs, epsrel, max_num_subint);
     
          // Check the result code returned by the C function.
          if (resCode == -1)
            System.out.println("Cannot load library nagc.dll / libnagc.so");
          else if (resCode == -2)
            System.out.println("Cannot find function d01ajc in nagc.dll / libnagc.so");
          else if (resCode == -3)
            System.out.println("Cannot find method " + functionName +
                               " with signature (D)D");
          else if (resCode > 0)
            {
              System.out.print("NAG function d01ajc returned non-zero exit code: ");
              System.out.println(resCode);
            }
          else
            {
              // resCode = 0 - we got some results.
              System.out.print("Lower limit of integration = ");
              System.out.println(a);
              System.out.print("Upper limit of integration = ");
              System.out.println(b);
              System.out.print("Requested relative error = ");
              System.out.println(epsrel);
              System.out.print("Integral value = ");
              System.out.println(result);
              System.out.print("Estimate of absolute error = ");
              System.out.println(abserr);
              System.out.print("Number of function evaluations = ");
              System.out.println(nFun);
              System.out.print("Number of subintervals used = ");
              System.out.println(nSubInt);
            }
          System.out.println();
        }
    } 
    
    このプログラムに関する注意事項:

    • Javaコード中で定義されるクラス Quadrature には result, abserr, nFun, nSubInt という名前のメンバ変数が含まれている。出力引数を介して直接Javaに情報を送り返すことはできないので、d01ajc によって計算された結果を収納するのにこれらの変数が利用される。

    • 被積分関数の例として2つの関数が示されている。最初は myFunction という名前の関数で、y = x3 の値を計算する。2番目の関数は myFunction2 であり、
            
      の値を計算する。

    • それぞれの関数を積分し結果を印刷するために、メインのJavaプログラムは myFunctionmyFunction2 という名前を引数とする形で Integrate というメソッドをコールする。メソッド Integrate は入力引数をセットアップし、実際の計算を行う NAG C Library 関数 d01ajc をコールする。

    Javaプログラムをコンパイルするには次のコマンドを使用すれば良い。

      % javac Quadrature.java 
    

  7. C用のヘッダファイルの生成
  8. Quadrature.java コンパイルが終わると、javah を使ってCヘッダファイルを作成することができる。

      % javah -jni Quadrature 
    
    生成されたヘッダファイル Quadrature.h には次の関数プロトタイプが含まれている。
      JNIEXPORT jint JNICALL Java_Quadrature_d01ajc
        (JNIEnv *, jobject, jstring, jdouble, jdouble, jdouble, jdouble, jint); 
    

  9. ネイティブ関数のCによる実装
  10. ヘッダファイル Quadrature.h が作成できたところで、今度は Java_Quadrature_d01ajc のCコードによる実装に移る。

    5.1 Cインタフェースライブラリ用ソースコード

    次はファイル QuadratureImp.c の内容を記したものである。

    #include <jni.h>         /* Java Native Interface headers */
    #include "Quadrature.h"  /* Auto-generated by javah -jni */
    #include <stdio.h>
    #include <math.h>
     
    #include <nag.h>      /* NAG C Library headers */
    #include <nagd01.h>
     
    /* Nasty global variables; they are global because they are
       required by the function evaluator intFun, but come from
       Java via the native interface routine Java_Quadrature_d01ajc. */
    JNIEnv *globalJavaEnv;
    jobject globalJavaObject;
    jmethodID globalMid;
     
    /* This is the interface to the Java function which is to
       be integrated. */
    double intFun(double x)
    {
      jdouble res;
     
      /* Here's where we call back to the user's function in Java code */
      res = (*globalJavaEnv)->CallDoubleMethod(globalJavaEnv, 
        globalJavaObject, globalMid, (jdouble)x);
      return (double)res;
    }
     
     
    /* Our C definition of function d01ajc declared in Quadrature.java.
       The return value is an error code.
       Other results are returned via direct JNI access to Java object
       member variables. */
    JNIEXPORT jint JNICALL
      Java_Quadrature_d01ajc(JNIEnv *env, jobject obj,
                             jstring funName,
                             jdouble a, jdouble b,
                             jdouble epsabs, jdouble epsrel,
                             jint max_num_subint)
    {
      static NagError fail;
      Nag_QuadProgress qp;
      double result, abserr;
      jclass cls;
      const char *functionName;
      jfieldID fid;
      int retVal;
     
      fail.print = Nag_FALSE;
     
      /* Copy the Java env pointers to global space
         so that intFun can access them. */
      globalJavaEnv = env;
      globalJavaObject = obj;
     
      /* Get hold of the name of the user's Java evaluation func. */
      functionName = (*env)->GetStringUTFChars(env, funName, 0);
     
      /* Now we have the Java evaluation function name we
         can use it to get hold of a handle (method ID) to the function.
         Once more, the method ID is stored globally so that intFun
         can use it. Note that the Java function signature must be
         "(D)D" (function with double argument, returning double). */
      cls = (*env)->GetObjectClass(env, obj);
      globalMid = (*env)->GetMethodID(env, cls, functionName, "(D)D");
     
      /* Free up the Java string argument so we don't leak memory. */
      (*env)->ReleaseStringUTFChars(env, funName, functionName);
     
      if (globalMid == 0)
        /* Cannot find method "functionName" with signature (D)D */
        return -1;
      else
        {
          /* Now call the function we're interested in from NAG Library.
             intFun is the function that we want to integrate. */
          d01ajc(intFun, (double)a, (double)b,
                 (double)epsabs, (double)epsrel,
                 (Integer)max_num_subint,
                 &result, &abserr, &qp, &fail);
     
          if (fail.code == 0)
            {
              /* Put the results back to Java. */
              /* Get the ID of the Java Quadrature class member variable
                 "result" (type double, hence the "D" signature). */
              fid = (*env)->GetFieldID(env, cls, "result", "D");
              /* Set the result value via the ID */
              (*env)->SetDoubleField(env, obj, fid, result);
              /* Repeat for other results */
              fid = (*env)->GetFieldID(env, cls, "abserr", "D");
              (*env)->SetDoubleField(env, obj, fid, abserr);
              fid = (*env)->GetFieldID(env, cls, "nFun", "I");
              (*env)->SetIntField(env, obj, fid, qp.fun_count);
              fid = (*env)->GetFieldID(env, cls, "nSubInt", "I");
              (*env)->SetIntField(env, obj, fid, qp.num_subint);
            }
        }
     
      /* Return any fail code that nagc.dll function d01ajc returned. */
      return fail.code;
    }
    

    5.2 Cコードの解説

    これまでと同様、Cソースファイル中には適切な NAG C Library ヘッダファイルが含まれていなくてはならない。Java_Quadrature_d01ajc という名前の関数はJavaで宣言されたメソッド d01ajc をCで実装したものである。

    f(x) の値を計算するJavaメソッドを直接 NAG C Library 関数 d01ajc に引渡すことはできないので、それをC関数中にラップ(wrap)する必要がある。それが intFun という名前のC関数である。

      double intFun(double x) 
    
    それは NAG Library 関数によって必要とされる引数の型と応答の型を持つ。intFun 内でやることと言えば、関数値を計算するためのJavaメソッドをコールするだけである。トリックはJavaに対するコール方法を知ることができるという点にある。

    そのためには jni.h 中に宣言されている JNI 関数 CallDoubleMethod を使用する(応答タイプの違いにより他にも CallVoidMethod, CallIntMethod 等の類似の関数が用意されている)。

    CallDoubleMethod いくつかの引数を必要とするが、それには JNIEnv ポインタ引数 env とJavaオブジェクト引数(共にJava_Quadrature_d01ajc に引渡されたもの)が含まれる。それはコールすべきJavaメソッドに対する methodID という引数も必要とする。これら3つの引数はすべて既知であるか、もしくは関数 Java_Quadrature_d01ajc によって求めることができるが、関数 intFun からは直接知ることはできない。intFun に対してはこれらの引数をグローバル変数を介して引渡す必要がある。Cコード中でのグローバル変数の宣言は次のように行う。

      JNIEnv *globalJavaEnv;
      jobject globalJavaObject;
      jmethodID globalMid; 
    
    これら3つの変数はグローバルであるため、Java_Quadrature_d01ajcintFun の双方からアクセス可能である。

    上記の3つの引数以外に intFun は、Javaメソッドが積分関数 f(x) の評価を行うために必要とする実際の引数も CallDoubleMethod に引渡さなくてはならない。CallDoubleMethod は任意の数の引数を受けられるが、今の場合は一つの引数 x だけで良い。

    関数 f(x) の評価を行うために intFun からJavaメソッドをコールする代りに、Cで評価用のコードを記述する方法もある。その場合には CallDoubleMethod やそれに関連した他のルーチンを使用する必要はなくなる。しかしここで紹介した方法にはメリットもある。一旦インタフェースライブラリができてしまえば、評価用の関数が変わってもそれを再作成する必要はない。単に異なるJava評価関数を用意するだけで良い。

    5.3  f(x) 評価用Javaメソッドの求め方

    関数 Java_Quadrature_d01ajc は最初に引数 envobj をグローバル変数 globalJavaEnv, globalJavaObject にコピーする。

    次に jstring 引数 funName として引渡されたJavaメソッド名を取り出し、それをメソッドIDに変換する。ただし jstring を直接アクセスするのは危険なため、JNI 関数 GetStringUTFChars を用いて jstring をCの文字ポインタ functionName に変換する。その上で JNI 関数 GetObjectClass, GetMethodID を用い、Java評価関数のメソッドIDを得る。

      functionName = (*env)->GetStringUTFChars(env, funName, 0);
        ...
      cls = (*env)->GetObjectClass(env, obj);
      globalMid = (*env)->GetMethodID(env, cls, functionName, "(D)D"); 
    
    (本用例における関数評価用メソッドの名称は "myFunction" と "myFunction2" であったことを思い出して欲しい。)GetMethodID に対する引数に注意。型が jclass の2番目の引数は該当メソッドを含むクラスを意味する。第3引数はメソッド名、第4引数はメソッドに対する署名(signature)である。署名 "(D)D" は一つの倍精度引数を取り、応答として一つの倍精度の値を返すメソッドであることを意味する

    intFun で用いられるメソッドIDが得られたので、Cストリング functionName はもはや必要ない。そこで JNI 関数 ReleaseStringUTFChars をコールする形でそれの解放を行う。

    5.4 ランタイムスキーム

    この時点で NAG C Library 関数 d01ajc をコールするのに必要な仕掛けはすべて整った。次の図は実行時の処理の流れを示したものである。


    Java、Cインタフェース、NAG C Library間のコールシーケンス

    5.5 結果のJavaへの引渡し

    NAG Library から戻ったら、その結果をJavaに返さなくてはならない。そのためには再度 JNI 関数をコールする。我々のJavaクラスには result, abserr, nFun, nSubInt という名前の属性(変数)が含まれていることを思い出して欲しい。JNI 関数 GetFieldID はこれらの変数の名前と署名に基づき field ID を応答として返してくる。この情報を別の JNI 関数に渡すことによって値の設定が行える。例えば関数 SetDoubleField はフィールドIDによって示される倍精度属性の値をセットする。

      fid = (*env)->GetFieldID(env, cls, "result", "D");
      /* Set the result value via the ID */
      (*env)->SetDoubleField(env, obj, fid, result); 
    
    は属性 result のフィールドIDを求め、その値をCコード中に含まれる変数(これも result という名前を持つ)の値にセットする。同様に、
      fid = (*env)->GetFieldID(env, cls, "nFun", "I");
      (*env)->SetIntField(env, obj, fid, qp.fun_count); 
    
    は属性 nFun(署名は"I")のフィールドIDを求め、その値を qp.fun_count にセットする(qp は型が Nag_QuadProgress の構造体であり、その要素 fun_count は評価関数に対するコール数を表す)。

  11. 共用ライブラリ/DLLの作成
  12. このステップはOS依存となる。

    • Linux上での作成

        % gcc -c -fPIC -I/opt/jdk1.6.0_11/include \
            -I/opt/jdk1.6.0_11/include/linux \
            -I/opt/NAG/cll6a09dgl/include QuadratureImp.c
        % ld -G -z defs QuadratureImp.o -o libnagCJavaInterface.so \
             /opt/NAG/cll6a09dgl/lib/libnagc_nag.so -lm -lc -lpthread 
      

      他のUNIX系マシン上ではさらなるライブラリの追加がリンク時に必要となる場合がある(Example 1 の注意参照)。

    • Windows上でのVisual C++による作成

        C:\> cl -Ic:\jdk1.6.0_11\include
               -Ic:\jdk1.6.0_11\include\win32
               -I"c:\Program Files\NAG\cldll094zl\include"
               /Gz -LD QuadratureImp.c
               "c:\Program Files\cldll094zl\lib\CLDLL094Z_nag.lib"
               -FenagCJavaInterface.dll
      

    関連するコンパイラフラグについては Example 1 セクション7 を参照されたい。

  13. プログラムの実行
  14. これまでの操作がすべて正常に行われたとすると、次のコマンドによってプログラムを実行できる。

      % java Quadrature 
    
    期待される出力は次のとおり。

        Calls of NAG quadrature routine d01ajc
     
        Integral of x*x*x
        Lower limit of integration = 0.0
        Upper limit of integration = 1.0
        Requested relative error = 1.0E-4
        Integral value = 0.25
        Estimate of absolute error = 1.387778780781446E-15
        Number of function evaluations = 21
        Number of subintervals used = 1
     
        Integral of x*sin(30*x) / sqrt(1-x*x/(4*PI*PI))
        Lower limit of integration = 0.0
        Upper limit of integration = 6.283185307179586
        Requested relative error = 1.0E-4
        Integral value = -2.543259618925085
        Estimate of absolute error = 1.2751911290909135E-5
        Number of function evaluations = 777
        Number of subintervals used = 19 
    

    (ライブラリが見つけられないという趣旨のエラーメッセージが発行された場合には、Example 1 ヒント を参照)

  15. クイックサマリ
  16. 2つのソースファイル Quadrature.javaQuadratureImp.c が与えられたとして、次のコマンドを発行する。

    • Javaクラスのコンパイル:
        % javac Quadrature.java 
      
    • ヘッダファイルの作成:
        % javah -jni Quadrature
      
    • インタフェースライブラリのコンパイル:

      • (Linux)
          % gcc -c -fPIC -I/opt/jdk1.6.0_11/include \
              -I/opt/jdk1.6.0_11/include/linux \
              -I/opt/NAG/cll6a09dgl/include QuadratureImp.c
          % ld -G -z defs QuadratureImp.o -o libnagCJavaInterface.so \
               /opt/NAG/cll6a09dgl/lib/libnagc_nag.so -lm -lc -lpthread 
        
        ただしディレクトリ名称 /opt/jdk1.6.0_11/include, /opt/jdk1.6.0_11/include/linux, /opt/NAG/cll6a09dgl/include, /opt/NAG/cll6a09dgl/lib はJavaと NAG C Library のインストレーションに合せて適宜調整を要す。

      • (Windows/Visual C++)
          C:\> cl -Ic:\jdk1.6.0_11\include
                 -Ic:\jdk1.6.0_11\include\win32
                 -I"c:\Program Files\NAG\cldll094zl\include"
                 /Gz -LD QuadratureImp.c
                 "c:\Program Files\cldll094zl\lib\CLDLL094Z_nag.lib"
                 -FenagCJavaInterface.dll 
        
        ただしディレクトリ名称 c:\jdk1.6.0_11\include, c:\jdk1.6.0_11\include\win32, "c:\Program Files\NAG\cldll094zl\include", "c:\Program Files\NAG\cldll094zl\lib" はJavaと NAG C Library のインストレーションに合せて適宜調整を要す。

    • Javaプログラムの実行:
        % java Quadrature 
      

Copyright 2009 Numerical Algorithms Group
Page last updated 2009-04-21 14:12:10 mick
[NP3671]


関連情報
Privacy Policy  /  Trademarks