http://www.saphir-control.fr/
|
strcpy(buf,"x=rand(1,2);");
C2F(scifun)(buf,strlen(buf));
On verra dans la suite que l'on encapsulera l'appel de scifun pour pouvoir
gérer les erreurs qui peuvent se produire en Scilab. On utilisera aussi
des fonctions internes Scilab nous permettant d'accéder à des
matrices ou des chaînes de caractères Scilab à partir de
leurs noms. Par exemple la macro (définie dans
routines/stack-c.h) :
GetMatrixptr("X", &m, &n, &lp);
permet d'obtenir un pointeur vers les données associées à
la matrice nommée X dans Scilab sous la forme
stk(lp) et la taille de la matrice est obtenue dans
(m,n). En cas d'echec GetMatrixptr effectue un return 0;. On
pourra se reporter au CD du magazine pour obtenir le source complet des
exemples que nous allons commenter. Le code se trouvera aussi dans le
répertoire examples/callsci de la prochaine version de
Scilab.
x solution
de Ax=b. La matrice A et le vecteur b
sont codés en C au moyen de tableaux unidimensionnels. Les
éléments Ai,j de la matrice sont stockés dans
un tableau C au position A[in*j]+ ce qui correspond au stockage
couramment utilisé dans les librairies numériques (et donc aussi
dans Scilab). On envoie les deux matrices dans Scilab au moyen de la macro
WriteMatrix (noter que cela implique une copie des objets), on
effectue ensuite le calcul au moyen de send_scilab_job (dont nous verrons le
code plus loin) et on récupère le résultat au moyen de
GetMatrixptr ce qui se fait cette fois sans copie. Voila le code complet de cet
exemple :
static int premier_exemple()
{
static double A[]={1,2,3,4}; int mA=2,nA=2;
static double b[]={4,5}; int mb=2,nb=1;
int m,n,lp,i;
WriteMatrix("A", &mA, &nA, A);
WriteMatrix("b", &mb, &nb, b);
fprintf(stdout,"premier exemple\n");
if ( send_scilab_job("A,b,x=A\\b;") != 0)
fprintf(stderr,"Erreur dans Scilab\n");
else
{
GetMatrixptr("x", &m, &n, &lp);
for ( i=0 ; i < m*n ; i++)
fprintf(stdout,"x[%d] = %5.2f\n",i,*stk(i+lp));
}
return 1;
}
L'éxecution de la routine précédente donnera l'affichage
suivant :
x[0] = -0.50 x[1] = 1.50et on vérifie bien (avec Scilab ou à la main !) que :
-->norm([1,3;2,4]*[-0.5;1.5]-[4;5])
ans =
0.
Dans un deuxième exemple on veut tracer une courbe à partir de la
donnée de deux vecteurs (x,y). Plutôt que d'allouer
les deux vecteurs dans le programme C on les <<alloue>> dans Scilab
et on récupère des pointeurs vers les données. Cela nous
évite d'avoir à effectuer des copies (WriteMatrix)
pour transférer les données vers Scilab. Les tableaux sont
ensuite remplis par notre programme C et la visualisation s'obtient grâce
à la commande :
send_scilab_job("plot(x,y);xclick();quit");
Dans cette commande on utilise xclick(). Ainsi le programme va
attendre un <<click>> de l'utilisateur pour continuer son
exécution. Pendant cette phase d'attente le gestionnaire
d'événements de Scilab va être actif et les menus de la
fenêtre graphique seront utilisables. Voila maintenant le code de ce
deuxième exemple :
static int deuxieme_exemple()
{
int m,n,lx,ly,i;
/* j'utilise scilab pour creer les abscisses
* et reserver de la place pour les ordonnees
*/
send_scilab_job("x=1:0.1:10;y=x;");
GetMatrixptr("x", &m, &n, &lx);
GetMatrixptr("y", &m, &n, &ly);
for ( i=0; i < m*n ; i++)
{
double xi = *stk(lx+i);
*stk(ly+i) = xi*sin(xi);
}
/* plot(x,y); */
send_scilab_job("plot(x,y);xclick();quit");
return 1;
}
Dans ce dernier exemple on appelle l'intégrateur d'équations
différentielles de Scilab (un article à déjà
été consacré a ce sujet). L'équation
différentielle est décrite en C et il faut donc en plus la
charger dans l'environnement Scilab. Le code est sans surprise, on transmet
à Scilab les conditions initiales et les temps pour lesquels on
désire la solution au moyen de la macro WriteMatrix. On charge
l'équation différentielle dans Scilab en utilisant la commande
link de Scilab à travers un send_scilab_job et
enfin on appelle l'intégrateur ode à nouveau au
travers de la fonction send_scilab_job.
int troisieme_exemple()
{
double x[]={1,0,0} ; int mx=3,nx=1;
double time[]={0.4,4}; int mt=1,nt=2;
/* chargement du code de l'équation différentielle */
send_scilab_job("link(''./mon_ode.o'',''mon_ode'',''c'');");
/* transmission des arguments */
WriteMatrix("x", &mx, &nx, x);
WriteMatrix("time", &mt, &nt,time);
/* appel de ode */
send_scilab_job("y=ode(x,0,time,''mon_ode''),");
}
Le code de l'équation différentielle donné dans le fichier
mon_ode.c est le suivant :
static double param[] ={0.04,10000,3.0e+7};
int mon_ode(neq, t, y, ydot)
int *neq;
double *t, *y, *ydot;
{
ydot[0] = - param[0]*y[0] + param[1] * y[1] * y[2];
ydot[2] = param[2] * y[1] * y[1];
ydot[1] = -ydot[0] - ydot[2];
return 0;
}
send_scilab_job(char *name) comme nous l'avons
évoqué plus haut ne fait qu'encapsuler la fonction de base et son
code est le suivant :
static int send_scilab_job(char *job)
{
static char buf[1024],
format[]="Err=execstr('%s','errcatch','n');quit;";
int m,n,lp;
sprintf(buf,format,job);
C2F(scirun)(buf,strlen(buf));
GetMatrixptr("Err", &m, &n, &lp);
return (int) *stk(lp);
}
Il n'y a donc rien de mystérieux la dedans, la fonction Scilab
Err=execstr(str,'errcatch','n')permet de faire exécuter une instruction Scilab donnée par une chaîne de caractères tout en bloquant le mode de gestion des erreurs par défaut. En cas d'erreur aucun message n'est imprimé et la fonction execstr renvoie le code de l'erreur dans son argument de retour sinon la valeur retournée est zéro. L'instruction GetMatrixptr("Err",m,n,lp) nous permet de récupérer ce code d'erreur au niveau C. On notera que si les chaînes de caractères transmises à
send_scilab_job contiennent
le caractère <<'>> il faudra le doubler dans le
code source, par exemple :
send_scilab_job("exists(''x'')");
(on évitera d'utiliser le caractère
<<">> car il faudrait alors le doubler pour
Scilab et le protéger pour le C !). Il nous reste à écrire
le programme principal qui va appeler les exemples précédents. Ce
programme principal doit initialiser Scilab et avant de terminer il doit
appeler la routine de sortie de Scilab sciquit. Comme certaines
fonctions de Scilab sont en Fortran (Eh oui ! mais comme on dit <<old
wine, old bottle>>) et qu'on les appelle à partir de C, on utilise
une macro définie dans Scilab qui permet de gérer les
différences de nommage entre C et Fortran. Ainsi sciquit
sera appelée sous la forme C2F(sciquit)() dans du code C.
L'initialisation de Scilab est résumée dans la fonction
Initialize. On initialise Scilab et on fait exécuter le
fichier de startup de Scilab. Le paramètre stacksize donne
la taille de la pile Scilab que l'on demande en au départ. Dans notre
version d'Initialize on fixe une valeur de la variable
d'environnement SCI (Il y a une valeur par défaut
../../ qu'une option de compilation
-DSCI=\"pathname\" peut écraser) pour pouvoir
appeler notre exécutable directement sans avoir à fixer la
variable SCI.
#include <math.h>
#include <stdio.h>
#include "machine.h"
#include "stack-c.h"
#ifndef SCI
#define SCI "../.." /* Une valeur par defaut */
#endif
static void Initialize()
{
static char initstr[]="exec(\"SCI/scilab.star\",-1);quit;";
static iflag=-1, stacksize = 1000000, ierr=0;
setenv("SCI",SCI,1);
C2F(inisci)(&iflag,&stacksize,&ierr);
if ( ierr > 0 )
{
fprintf(stderr,"Scilab initialization failed !\n");
exit(1);
}
C2F(settmpdir)();
C2F(scirun)(initstr,strlen(initstr));
}
Si on ne veut pas voir s'afficher la bannière de Scilab lors de
l'initialisation, il suffit de redéfinir la fonction
banier. Par exemple, de la façon suivante :
void C2F(banier)(int *x)
{
fprintf(stdout,"Ma banniere a moi\n");
C2F(storeversion)("scilab-2.5.1",12L);
}
Reste le programme principal. On se rappelle à nouveau que Scilab
contient une bonne part de code numérique Fortran et que lors de sa
compilation il est plus simple d'obtenir l'exécutable final en appelant
le compilateur Fortran (g77). Il se trouve que le runtime du
Fortran fournit un programme main et que ce programme
main attend de la part de l'utilisateur un point d'entrée
nommé MAIN__ (disons que c'est au moins vrai pour
g77). On écrit donc dans notre programme une fonction
MAIN__ :
int MAIN__(void)
{
Initialize();
premier_exemple();
deuxieme_exemple();
troisieme_exemple();
C2F(sciquit)();
}
examples/callsci que nous avons
organisé de façon légèrement différente.
Pour adapter le code proposé, il suffit d'éditer le fichier
Makefile et de renseigner les champs SCIDIR, PROG et
OBJS. Puis un make distclean suivit de make
all devrait effectuer la compilation et la création de
l'exécutable PROG.
SCIDIR=/usr/local/lib/scilab-2.5.1
PROG=myprog
OBJS=myprog.o
#--------------------------------------------------------
# do not modify below
#--------------------------------------------------------
all : .init
make -f config/Makefile.all \
PROG=$(PROG) OBJS="$(OBJS)" SCIDIR=$(SCIDIR)
.init : Makefile
config/Init $(PROG) $(SCIDIR)
touch .init
clean :
rm -f $(PROG) $(OBJS)
distclean :
rm -f $(PROG) $(OBJS) $(PROG).sh .init config/Makefile
rm -f config/Makefile.Top
Le Makefile précédent commence par lancer un script
Init dont le rôle est d'aller copier dans les
répertoires Scilab les Makefile dont on a besoin pour
linker les librairies Scilab à notre application. L'un des fichiers
créé par Init est config/Makefile.incl.
C'est une copie du fichier correspondant de Scilab et on pourra avoir besoin de
l'éditer par exemple si on veut changer les options de compilation.
Noter que les fichiers crées par Init ne sont
recréés qu'après un make distclean. La
compilation s'effectue en utilisant la commande make qui
exécute Init que nous venons de voir puis qui lance un
nouveau make du fichier config/Makefile.all. Le
fichier config/Makefile.all qui préexiste dans le
répertoire config utilise les information des fichiers
créés et les variables PROG et OBJS
pour effectuer la compilation. Pour conclure l'utilisateur n'a besoin de donner
que la liste des fichiers à compiler et il n'a pas en principe à
se préoccuper d'options particulières de compilation. Par exemple
le fichier d'include de Scilab "machine.h" sera
trouvé. Si l'utilisateur a besoin d'une option de compilation
particulière (par exemple pour débuger) il peut aller la rajouter
dans le fichier config/Makefile.all (ou comme on l'a
déjà vu dans le fichier généré
config/Makefile.incl).
SHELL = /bin/sh
include config/Makefile.incl
CFLAGS = $(CC_OPTIONS) -I$(SCIDIR)/routines -DSCI=\"$(SCIDIR)\"
FFLAGS = $(FC_OPTIONS)
CPPFLAGS= $(CC_OPTIONS) -I$(SCIDIR)/routines -DSCI=\"$(SCIDIR)\"
all:: $(PROG)
world:: $(PROG)
include config/Makefile.Top
include config/Makefile
distclean::
$(RM) $(PROG)
$(RM) -f -r config ./configs
JavaSciMatrix dont les objets sont des matrices
implémentées au moyen de tableaux de double unidimensionnels (
ceci pour avoir une implémentation voisine de celle de Scilab).
L'interaction avec Scilab se fait au moyen de la méthode
scilabJob qui a un unique argument à savoir une
chaîne de caractères Java (un objet de la classe
String). La méthode scilabJob doit
<<envoyer>> la matrice de type JavaSciMatrix à
Scilab, faire exécuter le job Scilab décrit par la chaîne
scilabJob et retransferer les données en sens inverse de
Scilab vers l'instance de JavaSciMatrix qui a appelée la
méthode scilabJob. Pour que l'appel à la
méthode scilabJob ait une chance de changer les
données de l'objet qui l'invoque il faut bien sûr que le
<<job>> Scilab contienne des références au nom de la
matrice. Le lien entre la matrice JavaSciMatrix et sa
correspondance en Scilab se faisant au travers de leur nom (champ
name). Par exemple si on veut remplir une matrice avec des valeurs
aléatoires calculées par Scilab on pourra utiliser :
JavaSciMatrix p = new JavaSciMatrix("A",4,4);
p.scilabJob(p.name + "= rand(" + p.m +"," + p.n +");");
Ainsi Scilab exécutera l'instruction "A=rand(4,4)"; Comme la
construction de la chaîne est un peu laborieuse on pourra construire des
méthodes enveloppantes pour rendre les choses plus lisibles. C'est ce
que nous faisons dans le code qui suit pour les méthodes
rand, show et inv. Par exemple la
méthode rand :
public void rand() {
scilabJob( name + "=rand(" + m +"," + n +");");
}
permet de simplifier l'appel précédent en p.rand();.
On notera que dans le code proposé, l'utilisateur doit respecter dans
scilabJob les dimensions des matrices qui sont transmises. Nous ne
vérifions pas cette cohérence. D'autre part notre code pourrait
être optimisé : ici les transferts entre Java et Scilab sont
effectués dans les deux sens et à chaque invocation de
ScilabJob ce qui n'est pas forcément optimal. Voilà
maintenant l'exemple complet coté Java :
class JavaSciMatrix {
private double [] x ; // Tableau pour les données
private int m,n; // taille de la matrice mxn
String name; // nom donné a la matrice
// constructeurs
JavaSciMatrix(String name,int m,int n)
{
x = new double[m*n];
this.m = m ; this.n = n;
this.name = name;
}
JavaSciMatrix(String name,int m,int n,double []x )
{
this.x = x ; this.m = m ; this.n = n;
this.name = name;
}
// méthode <<native>> que nous implémenterons en C
public native void scilabJob(String job) ;
// méthodes enveloppantes pour se faciliter la vie
public void inv() { scilabJob( name + "=inv(" + name +");");}
public void show() {
System.out.println("Matrix "+ name +"=");
scilabJob( "disp(" + name +");");
}
public void rand() {
scilabJob( name + "=rand(" + m +"," + n +");");
}
// A l'initialisation de la classe il faut charger le code externe
static
{
System.loadLibrary("javasci");
}
// Un test de tout cela
public static void main(String[] args) {
JavaSciMatrix m = new JavaSciMatrix("A",4,4);
m.rand(); // on remplit m de nombres aléatoires (loi uniforme)
m.show(); // un display fait par Scilab
JavaSciMatrix n = new JavaSciMatrix("B",2,2, new double [] {1,2,3,4} );
n.show();
n.inv(); // Une inversion de matrice faite par Scilab
n.show();
}
}
Le plus dur reste à faire. Implémenter la méthode
<<native>> scilabJob. Comme le but de l'article n'est
pas d'apprendre JNI on ne rentrera pas dans les détails. Un
appel à javac pour compiler le code
précédent, suivi d'un appel à javah permet
d'obtenir un fichier d'entête JavaSciMatrix.h. On
connaît ainsi le nom de la fonction C à implémenter. On
écrit alors JavaSciMatrix.c :
#include "JavaSciMatrix.h"
#include <math.h>
#include <stdio.h>
#include "machine.h"
#include "stack-c.h"
static int send_scilab_job(char *job) ;
static void Initialize() ;
void C2F(banier)(int *x) ;
JNIEXPORT void JNICALL Java_JavaSciMatrix_scilabJob
(JNIEnv *env , jobject obj_this, jstring job)
{
static int init = 0;
int i,cm,cn,lp;
const char *cname,*cjob;
/* get the class */
jclass class_Mine = (*env)->GetObjectClass(env, obj_this);
/* get the fields i.e x,m,n,name */
jfieldID id_x = (*env)->GetFieldID(env, class_Mine, "x", "[D");
jfieldID id_name = (*env)->GetFieldID(env, class_Mine, "name",
"Ljava/lang/String;");
jfieldID id_m = (*env)->GetFieldID(env, class_Mine, "m", "I");
jfieldID id_n = (*env)->GetFieldID(env, class_Mine, "n", "I");
/* get the field value */
jdoubleArray jx = (*env)->GetObjectField(env, obj_this, id_x);
jstring jname = (jstring) (*env)->GetObjectField(env, obj_this, id_name);
jint jm = (*env)->GetIntField(env, obj_this, id_m);
jint jn = (*env)->GetIntField(env, obj_this, id_n);
double *cx = (*env)->GetDoubleArrayElements(env,jx,NULL);
cname = (*env)->GetStringUTFChars(env, jname, NULL);
cjob = (*env)->GetStringUTFChars(env, job, NULL);
if ( init == 0) { init++; Initialize();}
cm=jm;cn=jn;
if (! C2F(cwritemat)((char *)cname,&cm,&cn,cx,strlen(cname)))
fprintf(stderr,"erreur lors de l'envoi a scilab \n");
else {
if ( send_scilab_job((char *)cjob) != 0)
{
fprintf(stderr,"Bug in scilab \n");
}
else
{
if ( ! C2F(cmatptr)((char *)cname, &cm, &cn, &lp,strlen(cname)))
fprintf(stderr,"erreur lors de la reception \n");
else
for (i=0 ; i < cm*cn ; i++) cx[i] = *stk(lp+i);
}
}
(*env)->ReleaseStringUTFChars(env, jname , cname);
(*env)->ReleaseStringUTFChars(env, job , cjob);
(*env)->ReleaseDoubleArrayElements(env,jx,cx,0);
}
On reconnaît dans le code précédent la fonction
send_scilab_job que l'on a déjà utilisée et
on trouve aussi C2F(cwritemat) et C2F(cmatptr) qui
ont les mêmes fonctions que les macros WriteMatrix et
GetMatrixptr déjà vues. Les seules
difficultés nouvelles sont donc de récupérer des pointeurs
de chaîne C cname et cjob (nom de la matrice et
job Scilab) des entiers jm et jn qui sont des champs
de l'objet qui a invoqué la méthode et enfin un pointeur de
double cx qui permettra d'accéder au vecteur des
données. On accède aux champs d'un objet avec la fonction
GetFieldID qui demande de connaître le nom du champ, la
classe de l'objet (que l'on a obtenu avec GetObjectClass) et la
signature du champ qui est une chaîne qui code le type du champ. La
signature peut s'obtenir avec la commande javap -s JavaSciMatrix
et on obtient par exemple la signature d'un tableau de double
"[D". Une fois obtenu les champs de l'objet et
connaissant les arguments (ici jstring job) il faut convertir ces
arguments dans un format compris par C. Pour les chaînes de
caractères la conversion est faîte au moyen de
GetStringUTFChars :
cname = (*env)->GetStringUTFChars(env, jname, NULL);et il ne faut pas oublier de <<libérer>> la zone utilisée au moyen de
ReleaseStringUTFChars. Pour le tableau
de double GetDoubleArrayElements et
ReleaseDoubleArrayElements jouent un rôle équivalent.
Noter qu'en cas d'erreur Scilab on imprime juste un message d'erreur. Pour
être plus propre, il faudrait renvoyer une Exception Java.
JNI rend cela possible et nous renvoyons le lecteur à la
doc
http://java.sun.com/j2se/1.3/docs/guide/jni/index.htmlLe code complet de
JavaSciMatrix.c que l'on trouvera sur le CD
contient aussi le code des fonctions déjà vues
Initialize send_scilab_job et
C2F(banier) et le code de la fonction MAIN__ que l'on
réduit a sa plus simple expression.
int MAIN__() {};
Il nous faut maintenant compiler le code C. Cette fois, on ne veut pas
créer une application mais plutôt une librairie partagée
qui sera ensuite chargée par Java. On reprend le fichier
Makefile précédent et cette fois on
écrit :
SCIDIR=/usr/local/lib/scilab-2.5.1
PROG= libjavasci.so
OBJS= JavaSciMatrix.o
#--------------------------------------------------------
# do not modify below
#--------------------------------------------------------
all :: .init
make -f config/Makefile.all FC="g77 -Wl,-shared" \
PROG=$(PROG) OBJS="$(OBJS)" SCIDIR=$(SCIDIR)
..........
On a écrit dans PROG le nom de la librairie
partagée. Noter que c'est le nom que l'on avait écrit dans
System.loadLibrary("javasci");
précédé de lib et suffixé par
.so. On rajoute également FC="g77
-Wl,-shared" (oui, c'est dans la partie do not modify
below!) pour que le linker fasse une librairie partagée et pas un
exécutable. Et maintenant c'est facile, on tape :
make distclean
javac JavaSciMatrix.java
make all
setenv LD_LIBRARY_PATH `pwd`
java JavaciMatrix
et on obtient :
ekdahl% java JavaSciMatrix Et Hop .... Startup execution: loading initial environment Fin de Init Matrix A= ! 0.2113249 0.6653811 0.8782165 0.7263507 ! ! 0.7560439 0.6283918 0.0683740 0.1985144 ! ! 0.0002211 0.8497452 0.5608486 0.5442573 ! ! 0.3303271 0.6857310 0.6623569 0.2320748 ! Matrix B= ! 1. 3. ! ! 2. 4. ! Matrix B= ! - 2. 1.5 ! ! 1. - 0.5 !Comme on est d'un naturel méfiant on vérifie le résultat :
-->B=[1,3;2,4] ; Binv=[-2,1.5;1,-0.5];
-->norm(B*Binv-eye(B))
ans =
0.
Et on conclut: pas si mal finalement l'inversion de matrices en Fortran !
This document was translated from LATEX by HEVEA.