A numerical integration routine, function d01ajc |
According to the C Library Manual, the prototype for function d01ajc looks like this:
#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);The function d01ajc is designed to numerically integrate f(x), where f(x) is a function supplied by the user. The limits of integration are the finite interval [a,b].
The function f(x) to be integrated is supplied as the first argument to d01ajc, and is declared as
double (*f)(double x)i.e. a function of one double argument which returns a value of type double.
The arguments epsabs and epsrel are used to control the accuracy to which the integral is computed. Argument max_num_subint is the limit on the number of subintervals that d01ajc may split the interval [a,b] into during computation. The integral result is returned via argument result, and an estimate of the absolute error in the computed result is returned in abserr.
Argument qp, of type Nag_QuadProgress, is used to return further information about the computation, including the actual number of sub-intervals used and the number of calls made to f(x).
As with Example 1 and Example 2, we will not attempt to pass the contents of the NagError structure back to Java. In our Java program, we will declare the function like this:
// Declaration of the Native (C) function private native int d01ajc(String funName, double a, double b, double epsabs, double epsrel, int max_num_subint);i.e. a method with return type int. Since we are not bothering to use the fail argument, we will use the int return value to send back any error code.
Note that we cannot pass a function argument directly from Java to C, and so here we just pass the name of a function via the String argument funName. Later on we will see how we can use this name to get hold of the actual function f(x) written in Java.
Note also that our Java declaration does not contain any of the output arguments of d01ajc. Instead, we will need to find another way to pass the information that the output arguments contain back to 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(); } }Some points to note about this program:
We can compile our Java program with the following command:
% javac Quadrature.java
% javah -jni QuadratureThe generated header file, Quadrature.h, contains this function prototype:
JNIEXPORT jint JNICALL Java_Quadrature_d01ajc (JNIEnv *, jobject, jstring, jdouble, jdouble, jdouble, jdouble, jint);
#include <jni.h> /* Java Native Interface headers */ #include "Quadrature.h" /* Auto-generated header created 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 the Java code */ res = (*globalJavaEnv)->CallDoubleMethod(globalJavaEnv, globalJavaObject, globalMid, (jdouble)x); return (double)res; } /* Our C definition of the 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 function. */ 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" (i.e. 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 the NAG C 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" (which is of 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 the nagc.dll function d01ajc returned. */ return fail.code; }
As before, our C source file must include the appropriate NAG C Library header files. The function named Java_Quadrature_d01ajc is our C implementation of the Java-declared method d01ajc.
We cannot pass the Java method which evaluates f(x) directly to the NAG C Library function d01ajc, so we need to wrap it in a C function. This C function we name intFun:
double intFun(double x)and it has argument type and return type required by the NAG Library function. Inside intFun we do nothing but call the Java method to evaluate the function. The trick is in knowing how to make this call to Java.
We do this using the JNI function CallDoubleMethod, which is declared in jni.h (there are similar functions named CallVoidMethod, CallIntMethod and others, for methods with different return types).
CallDoubleMethod needs several arguments, including the JNIEnv pointer argument env and the Java object argument, both of which were passed to Java_Quadrature_d01ajc. It also needs an argument named methodID, the method id of the Java method to be called. All three of these arguments are known or can be obtained by our function Java_Quadrature_d01ajc, but are not directly known by our function intFun. Instead, we need to give these arguments to intFun via global variables, which are declared like this in our C source code:
JNIEnv *globalJavaEnv; jobject globalJavaObject; jmethodID globalMid;The three variables are global so that they can be accessed both by Java_Quadrature_d01ajc and by intFun.
Besides the three arguments mentioned above, intFun must also pass to CallDoubleMethod the actual arguments that the Java method needs to evaluate our quadrature function f(x) (and that is the function we are really interested in!). CallDoubleMethod can accept any number of these arguments, but in this case there is only one argument, x.
Note that instead of calling the Java method from intFun to evaluate the function f(x), we could have written the evaluation code in C. Then there would have been no need to use CallDoubleMethod and the other routines associated with it. There is an advantage to the method we used: once the interface library has been built, we never need to rebuild it even if our evaluation function changes – we only need to supply a different Java evaluation function.
Our function Java_Quadrature_d01ajc first copies its arguments env and obj to global variables globalJavaEnv and globalJavaObject.
Next, we take the name of the Java method passed as jstring argument funName and convert it into a method id. We use JNI function GetStringUTFChars to convert the jstring into a C char pointer named functionName because the jstring cannot be accessed safely directly. Then the JNI functions GetObjectClass and GetMethodID are used to get hold of the method ID of the Java evaluation function:
functionName = (*env)->GetStringUTFChars(env, funName, 0); ... cls = (*env)->GetObjectClass(env, obj); globalMid = (*env)->GetMethodID(env, cls, functionName, "(D)D");(recall that in our example program the evaluation function will be the method named "myFunction" or "myFunction2"). Notice in particular the arguments to GetMethodID. The second argument, of type jclass, is the class containing the method; the third argument is the name of the method, and the fourth argument is the signature of the method. In this case, the signature "(D)D" means a method with one double argument and which returns a value of type double.
Now that we have obtained the method ID to be used by intFun, we no longer need the C string functionName, so we free it via a call to the JNI function ReleaseStringUTFChars to avoid memory leaks.
At this point we have everything we need to call the NAG C Library function d01ajc. This is a diagram of what happens at run time:
After return from the NAG Library, we want to return the results to Java. Once again, we must call JNI functions to do it. Recall that our Java class contained properties (variables) named result, abserr, nFun and nSubInt. The JNI function GetFieldID, given the name and signature of one of these variables, will return its field ID which we can pass to another JNI function to set its value. For example, the function SetDoubleField sets the value of a double property given its field ID. The lines
fid = (*env)->GetFieldID(env, cls, "result", "D"); /* Set the result value via the ID */ (*env)->SetDoubleField(env, obj, fid, result);get the field ID of property result, and set its value to that of the variable contained in the C code which is also named result.
fid = (*env)->GetFieldID(env, cls, "nFun", "I"); (*env)->SetIntField(env, obj, fid, qp.fun_count);get the field ID of int property nFun, with signature "I", and set its value to qp.fun_count. (qp is a structure of type Nag_QuadProgress, and component fun_count is the number of calls made to the evaluation function).
% gcc -c -fPIC -I/opt/jdk1.6.0_11/include -I/opt/jdk1.6.0_11/include/linux \ -I/opt/NAG/cll6a09dhl/include QuadratureImp.c % ld -G -z defs QuadratureImp.o -o libnagCJavaInterface.so \ /opt/NAG/cll6a09dhl/lib/libnagc_nag.so -lm -lc -lpthread
Recall that on other UNIX machines it may be necessary to add further libraries at link time - see note.
C:\> cl -Ic:\jdk1.6.0_11\include -Ic:\jdk1.6.0_11\include\win32 -I"c:\Program Files\NAG\CL09\clw3209dal\include" /Gz -LD QuadratureImp.c "c:\Program Files\NAG\CL09\clw3209dal\lib\CLW3209DA_nag.lib" -FenagCJavaInterface.dll
The compiler flags used were described in Section 7 of Example 1.
% java QuadratureThe expected output looks like this:
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
(If you get an error message saying that a library cannot be located, see the tip given in Example 1).
% javac Quadrature.java
% javah -jni Quadrature
% gcc -c -fPIC -I/opt/jdk1.6.0_11/include -I/opt/jdk1.6.0_11/include/linux \ -I/opt/NAG/cll6a09dhl/include QuadratureImp.c % ld -G -z defs QuadratureImp.o -o libnagCJavaInterface.so \ /opt/NAG/cll6a09dhl/lib/libnagc_nag.so -lm -lc -lpthreadwhere /opt/jdk1.6.0_11/include, /opt/jdk1.6.0_11/include/linux, /opt/NAG/cll6a09dhl/include and /opt/NAG/cll6a09dhl/lib are directory names appropriate to your Java and NAG C Library installations.
C:\> cl -Ic:\jdk1.6.0_11\include -Ic:\jdk1.6.0_11\include\win32 -I"c:\Program Files\NAG\CL09\clw3209dal\include" /Gz -LD QuadratureImp.c "c:\Program Files\NAG\CL09\clw3209dal\lib\CLW3209DA_nag.lib" -FenagCJavaInterface.dllwhere c:\jdk1.6.0_11\include, c:\jdk1.6.0_11\include\win32, "c:\Program Files\NAG\CL09\clw3209dal\include" and "c:\Program Files\NAG\CL09\clw3209dal\lib" are directory names appropriate to your Java and NAG C Library installations.
% java Quadrature