|
||||||||
ProgrammingIn the previous chapter we learnt about the data-structures, mathematical expressions and basic language constructs available within numEclipse. In Chapter 2, we briefly looked at how to write and run simple programs in java and m-script. This chapter will go into further details about programming. It is divided into three major parts. In the first part, we will look at writing standalone programs and functions in m-script. We will also show how to deploy user-defined functions. In the second part of the chapter, we will show the development and deployment of java functions. Finally, in the last part, we will explain how to write and integrate functions in C/C++. These parts / sections are designed in increasing order of difficulty due to the nature of implementation. So a user not interested in Java or C/C++ coding could just stick to the first section of this chapter. An ambitious and experienced developer can skip the first section and jump to the second part of the chapter.
4.1 m-script ProgrammingThis section is intended for readers who are not familiar with programming in MATLAB or GNU Octave. You can skip to next section, if you are comfortable programming with either of these tools. There are basically two types of programs you could write using m-script. The first kind is a standalone program which runs within the interpreter. It uses the workspace variables. It is very much like a procedure which takes no argument and returns no parameter variables. The second kind is a function which you could either call directly from the interpreter, use within another function or standalone program. It can accept input parameters and return one or more parameters. All the variables used in the function are local and they are not exposed to workspace except the global variables.
4.1.1 Standalone programIn this section, we will show how to create and run a standalone program. Lets create a file "firstprog.m" in the source folder of a numEclipse project as described in chapter 2. Then add the following code to this file.
for i = 1 : 10 for j = 1 : 10 h (i, j) = 1 / (i + j + 1); end; end; plot(h);
Once it is saved, it can be used from any instance of the interpreter. Either open up the default interpreter or create a new interpreter as described in previous chapters. Enter the following command on the prompt
>> run firstprog.m
The GNU Plot window will show the following.
You can use either the memory view on the right-hand side of the perspective to examine the value of variable "h" used in the program or you can just enter h on the prompt to get the value.
>> h 0.3333 0.2500 0.2000 0.1667 0.1429 0.1250 0.1111 0.1000 0.0909 0.0833 0.2500 0.2000 0.1667 0.1429 0.1250 0.1111 0.1000 0.0909 0.0833 0.0769 0.2000 0.1667 0.1429 0.1250 0.1111 0.1000 0.0909 0.0833 0.0769 0.0714 0.1667 0.1429 0.1250 0.1111 0.1000 0.0909 0.0833 0.0769 0.0714 0.0667 0.1429 0.1250 0.1111 0.1000 0.0909 0.0833 0.0769 0.0714 0.0667 0.0625 0.1250 0.1111 0.1000 0.0909 0.0833 0.0769 0.0714 0.0667 0.0625 0.0588 0.1111 0.1000 0.0909 0.0833 0.0769 0.0714 0.0667 0.0625 0.0588 0.0556 0.1000 0.0909 0.0833 0.0769 0.0714 0.0667 0.0625 0.0588 0.0556 0.0526 0.0909 0.0833 0.0769 0.0714 0.0667 0.0625 0.0588 0.0556 0.0526 0.0500 0.0833 0.0769 0.0714 0.0667 0.0625 0.0588 0.0556 0.0526 0.0500 0.0476 There is no particular structure to an m-script program unlike C or Java programming languages. There is no beginning or end statement, you can use the variables without declaring them and there is no "main" procedure / function to initiate the program execution. So essentially it is a sequence of statements without any constraints other than the syntax of the statements.
4.1.2 m-script FunctionIn chapter 2, we showed how to write a simple function and call it from an interpreter. In the previous chapter, we presented the syntax and examples for function and procedure. We also covered automatic variables and cell arrays, nargin, nargout, varargin and varargout. These variables have special role in defining functions with variable number of input and output arguments. In this section, we will look at functions in further detail. Most mathematical functions have zero or more input arguments and a single output argument. A user of these functions will always pass the fixed number of arguments and expect single output. Here is an example,
//min.m function z = min(x, y) % This function returns the minimum of x or y. if (x <= y) z = x; else z = y; end;
This function accept two input arguments, x & y, and return the minimum of these two. There are a few points we should note here. This function should always be saved as a .m file otherwise the interpreter will not be able to recognize the function. The file name should be the same as the function name. This is just a convention, the interpreter treats the file name as a function name and ignores the function name 'min' in the file. But there will be no traceability if the two names were not the same. Note that the definition of a function starts with a key word 'function' whereas a standalone program has no begin or end keywords. The 'end' keyword in above function is the end of the 'if' statement rather than the function. In fact the 'end' keyword for the function is optional. You can also use 'endfunction' like in octave script. Unlike MATLAB or Octave, you can have only one function definition per .m file. At this point, numEclipse does not use the comment lines within the function to generate the help statement. But it is strongly advised to comment your functions judiciously, it is a good software engineering practice. Unlike 'C' or 'Java', you do not need to use 'return' statement to pass the output argument back to the caller program. You would notice that the output argument 'z' in the above example is directly assigned the result. You will use the return statement when you want to return the control back to the caller program without passing the output argument. In fact, the 'return' statement does not expect any argument to pass back. One most important point, there is no mention of input arguments' data type. This is really wonderful, this function will work for real numbers, complex numbers, row / column vectors and matrices. The relational operator '<=' automatically overload based on the type of operands. In the following we show some examples of the function call.
>> min(2, 3) ans = 2.0000
>> min([1 2], [3 4]) ans = 1.0000 2.0000
'min' function as defined above does not put any constraints on the type of the input arguments. This might be a problem in certain situations for example when somebody try to find a minimum of a string and a matrix. In that case, you will use the 'error' function as shown below.
//min.m function z = min(x, y) % This function returns the minimum of x or y. if type(x) ~= type(y) error('usage min(x, y): both x and y must be same type.'); end; if (x <= y) z = x; else z = y; end;
In this modified definition, we call an error function when the type of input arguments do not match. The 'error' function returns the control back to the caller program along with its string argument. An another function 'warning' could also be used in the situation where you want to send a string message back to the caller program without returning the control, so the function will keep executing.
The above function restricts the number of input arguments to two. Say, we want to modify it in a such way that it could accept any number of inputs and return the minimum of those input arguments. This is how we will do it.
//min.m function z = min(varargin) % This function returns the minimum of input arguments. z = varargin{1}; for i = 2 : nargin if (varargin{i} <= z) z = varargin{i}; end; end;
When the above function is called as shown below.
>> m = min(1, 2, 3, 4, 5) m = 1.0000
All the input arguments are packed into a cell array 'varargin' and an automatic variable 'nargin' is created with value 5 which reflects the number of inputs. The ith argument could be accessed as mentioned in the previous chapter using the curly bracket, varargin{i}.
Now, we want to modify this function to return more than one values. Say, we want it to return both the minimum and maximum values.
function [min max] = extreme(varargin) This function could be called as shown below.
>> [m n] = extreme(1, 2, 3, 4, 5) m = 1.0000 n = 5.0000
If we call the same function slightly differently, as follows.
>> [m n] = extreme(1, 2, 3, 4, 5)
We will get an array index out of range error. This situation could be avoided by using the 'nargout' as shown below.
function [min max] = extreme(varargin) error('usage extreme(varargin): exceeding number of output arguments'); end;
min = varargin{1};
The nargout contains the number of output variables requested by the caller program. Checking this variable enables the function to printout a meaningful error message. Lets give another twist to this function.
function m = extreme(s, varargin) And here is the call to this function.
>> extreme('min', 1, 2, 3, 4, 5) ans = 1.0000
>> extreme('max', 1, 2, 3, 4, 5) ans = 5.0000
In this function, the first input argument is a string which determines if the function should return the minimum or maximum of the rest of the input arguments. This example is presented to show that varargin could be used with other input arguments. Similarly, varargout could be used with other output arguments. But, we should always use these at the end of the list of the arguments, for obvious reason. Another important point to remember is that in this situation nargin and nargout will return the total number of input and output arguments and it might not be the same as the length of these cell arrays. So if you want to loop around these arrays then it is better to use the length function on these arrays rather than the automatic variables, nargin & nargout.
Once all the user defined functions are developed and tested, you might want to deploy them as a toolbox for other users. Unfortunately, numEclipse does not support compilation of m-script function to MEX files. But, it does have a neat mechanism of deployment. The deployment package is just a zip file containing the user-defined functions. So you compress the .m files containing the functions in a zip file and deliver to your end-users.
The end-user should take the following steps to include these functions into the interpreter.
This completes the deployment. You are ready to try it now. We deleted the "extreme.m" file from the workspace and deployed the zip file. Then we tried the function again and the result is same as before.
>> extreme('max', 1, 2, 3, 4, 5) ans = 5.0000
Say, you had two definitions of the same function. One in the workspace and other one deployed as a library. The interpreter will pick the definition from the workspace since it has higher precedence.
4.2 Java ProgrammingIn chapter 2, we gave a very simple example to write and execute a java function. In this section, we will go into further details about writing java functions for numEclipse. In order to develop complex functions it is important to understand some internals. In appendix C, we provide the interface classes (i.e., IComplex, IMatrix, IStruct, ICell) used to implement the data types complex, matrix, structure and cell. We also provide the interface class "LinearAlgebra" used by the interpreter to create objects of these types. We will build on top of the example in chapter 2 and show how to create a library (toolbox) of functions. Any java class that we want to deploy as a toolbox must implement an interface org.numEclipse.toolbox.Toolbox. If you do not plan to deploy then you do not have to implement this interface. Actually, there is not much to implement. You only declare that the class is implementing this interface, otherwise it is just an empty class. The sole purpose of the interface is to identify the classes implementing a toolbox. The library manager within the interpreter loads the classes using Java Reflection APIs. It uses this interface to identify the classes to load.
1 package test; 2 import org.jmatlab.toolbox.Toolbox; 3 public class MyJavaClass implements Toolbox { 4 private static LinearAlgebra alg;
5 public static void
setAlgebra (LinearAlgebra a) { 10}
This class must create a static field "alg" of type "LinearAlgebra" and it should also create a static setter method, as shown from line 4 to 7 in above code. At this point, you will get compilation errors. To remove the errors, you would need to add the library.jar file within the numEclipse plug-in to the Java Build Path of the java project. As shown in the following project properties.
The interpreter uses the static setter shown in previous code to set the implementation of the LinearAlgebra to the field alg. This enables the developer to create and manipulate the different data types within the toolbox class. In the chapter on architecture of this application, we will also show how to develop an implementation of the LinearAlgebra but at this point we will continue to use the default implementation. In fact you never have to see the default implementation or develop a new implementation unless you wish to redefine the basic arithmetic operations.
All user defined functions must be static and they should only use the following types for the input and output parameters. double, String, IComplex, IMatrix, IStruct, ICell and List. The interpreter converts a double and complex type to a matrix type in a function call. So there is no point in implementing a function with double and complex input argument. Here is an example,
1 public static IMatrix min(IMatrix m1,
IMatrix m2) { In lines 2, we compare the input matrices. The result of the comparison is a matrix of zeros and ones. One when the corresponding element is less than the element in m2 and zero otherwise. In line 3, we check if all elements of the resultant matrix are greater than zero. This function is similar to the one we developed in the previous section. The result of calling this function from the interpreter is the same.
>> min(2, 3) ans = 2.0000
>> min([1 2], [3 4]) ans = 1.0000 2.0000
It works for both the double and matrix input type. We can use any combination of double, complex and matrix input pair and it will still work. In the following we show how to raise an error or exception.
1 public static IMatrix min(IMatrix m1,
IMatrix m2) {
6
if (rows1 != rows2 || cols1 != cols2) In this modified function, we check the row and cols of the input matrices and throw "SemanticException". Here is an example to call this function,
>> min([1 2], 3) min(m1, m2): input argument size must be the same. >>
Now, we will modify this function in such a way that it will accept a variable number of input arguments. Here is a code listing,
1 public static IMatrix min
(List list) { In this example, we use a List instead of the IMatrix. Basically, when we call this function from the interpreter or any other function or program, the interpreter packs all the input arguments in a list. We extract the contents of the list to compute the result. Note that, we do not return an object passed to the function. We always create a new object out of the resultant matrix object. This ensures the integrity of the variables in the workspace.
We will change this function further to return multiple output parameters. Here is the code listing,
1 public static List extreme(List list)
{ This method could take variable number of arguments and it could return more than one output parameters. The input and output parameters are packed into lists by the interpreter as mentioned in the previous example. Here is an example to call this method.
>> extreme(1, 2, 3, 4, 5) ans = 1.0000
>> [m n] = extreme(1, 2, 3) m = 1.0000 n = 3.0000
The result is same as we saw in the last section. Essentially, you could achieve the same functionality either you write the function in m-script or java. Of course, the java functions will be faster than the m-script.
Deployment of java functions is very similar to the deployment of m-script. Here, we export the java functions to a jar file using the eclipse File → Export menu. Then add the jar file to the numEclipse library using the preferences as we did in the previous section. Make sure that the numEclipse project does not depend on the java project in the workspace. Let the workbench restart and test the functions from the interpreter.
4.3 C/C++ ProgrammingThis section is intended to show you how to write C/C++ functions for numEclipse. It is more difficult of the last two approaches but luckily eclipse comes with some tools which makes the development a rather smooth process. You would still need a good understanding of C/C++ and JNI (Java Native Interface). The intent here is not to teach you C/C++ or JNI but rather the development and deployment process. If you do not know how to setup and write C/C++ programs within eclipse, here is a link to a very good tutorial http://www.cs.umanitoba.ca/~eclipse/7-EclipseCDT.pdf. If you need a refresher on JNI, here is another tutorial from IBM developerworks http://www6.software.ibm.com/developerworks/education/j-jni/j-jni-a4.pdf.
The very first steps is to write a java class similar to the one developed in the previous section. Except the fact, that we are using native method over here. Here is an example,
1 import org.jmatlab.toolbox.Toolbox; Similar to the previous section, this class implements the interface "Toolbox", declares an attribute "alg" of type "LinearAlgebra" and defines a setter method for this variable. Here we declare two native functions on line 5 & 6. numEclipse cannot make a direct call to native functions so we define wrapper functions in the class with the names starting with keyword "native". This naming convention is used by the interpreter to determine the nature of the function. The notion of wrapper function is developed with the view that some conversion will be required for the arguments before and after calling a native method. We previously mentioned that a double or complex argument in a function call is converted to a matrix. In this case, when you name the function starting with native, it will not perform this conversion to matrix. This design decision is made with the view that a lot of time a developer will be more interested in integrating existing C/C++ libraries rather than writing new ones. In that case, it makes more sense to keep the function arguments as it is, because existing libraries are unlikely to use the matrix object defined in numEclipse.
Once the java class is completed, we follow the steps defined in the following article. http://www.cs.umanitoba.ca/~eclipse/8-JNI.pdf As a next step we generate the "C" header file "NativeToolbox.h" using javah utility. Here is the generated code.
/* DO NOT EDIT THIS FILE - it is machine generated */ #include <jni.h>/* Header for class NativeToolbox */ #ifndef _Included_NativeToolbox#define _Included_NativeToolbox#ifdef __cplusplusextern "C" {#endif /* * Class: NativeToolbox * Method: sqr * Signature: (D)D */ JNIEXPORT jdouble JNICALL Java_NativeToolbox_sqr (JNIEnv *, jobject, jdouble); /* * Class: NativeToolbox * Method: msg * Signature: (Ljava/lang/String;)Ljava/lang/String; */ JNIEXPORT jstring JNICALL Java_NativeToolbox_msg (JNIEnv *, jobject, jstring); #ifdef __cplusplus} #endif #endif
We write a .def file as follows.
EXPORTS Java_NativeToolbox_sqr Java_NativeToolbox_msg
And write a C program "NativeToolbox.c" implementing the above C functions.
//File: NativeToolbox.c #include <jni.h>#include "NativeToolbox.h"JNIEXPORT jdouble JNICALL Java_NativeToolbox_sqr (JNIEnv *env, jobject obj, jdouble d) { return d * d;} JNIEXPORT jstring JNICALL Java_NativeToolbox_msg (JNIEnv *env, jobject obj, jstring s) { return s;}
Finally, we write the following makefile to generate the dll file.
all : NativeToolbox.dll NativeToolbox.dll : NativeToolbox.o NativeToolbox.def gcc -shared -o NativeToolbox.dll NativeToolbox.o NativeToolbox.def NativeToolbox.o : NativeToolbox.c NativeToolbox.h gcc -I"C:\\Program Files\\Java\\jdk1.5.0_09\\include" -I"C:\\Program Files\\Java\\jdk1.5.0_09\\include\\win32" -c NativeToolbox.c -o NativeToolbox.o NativeToolbox.h : NativeToolbox.class C:\Program Files\Java\jdk1.5.0_09\bin\javah -jni NativeToolbox clean: -del NativeToolbox.h -del NativeToolbox.o
Then, we will follow the same steps as we did in the previous section. We will export the java class to a jar file and add the jar and dll file both to the library preference of numEclipse. You are now ready to test the native functions.
>> nativeSquare(10) ans = 100.0000
>> nativeMesg('Hello') ans = Hello
So this completes the chapter. Here we learnt how to develop and deploy a library of functions in m-script, java and C/C++.
|