Scilab : un
logiciel libre pour le calcul scientifique
7e Partie - Introduction à
Scicos (2/2)
Tous ces articles en ligne sur Scilab ont été écrits pour
LINUX MAGAZINE FRANCE par les
développeurs du Scilab Group. Les articles sont sous
licence FDL (Free Documentation
Licence)
On a vu dans le numéro précédent l'utilisation de
Scicos pour la construction de systèmes dynamiques sous forme
schéma-blocs à partir des blocs existants. Dans ce numéro,
on présente une façon de constuire de nouveaux blocs.
Les blocs prédéfinis dans les palettes standard de Scicos
permettent de construire des schémas très divers mais, dans
certains cas, une fonctionnalité décrite à partir de
plusieurs blocs peut être réalisée par un seul bloc ce qui
donne souvent une simulation plus efficace. On a déjà vu une
façon de remplacer plusieurs blocs par un seul en utilisant le
<<Super Block>>. Cela permet de construire des schémas
hiérarchiques plus lisibles mais n'améliore en rien
l'efficacité de la simulation car la première étape de la
compilation dans Scicos consiste à mettre à plat le diagramme. En
fait, un <<Super Block>> n'est qu'une facilité graphique
pour masquer des sous-diagrammes ; ce n'est pas un bloc de base.
Un nouveau bloc de base peut être défini de plusieurs
façons, mais dans tous les cas on a besoin de deux fonctions. Une
fonction d'interface, presque toujours écrite en langage Scilab, pour
gérer l'interface avec l'éditeur Scicos, et une fonction de
simulation réalisant le comportement dynamique du bloc. Cette
dernière est utilisée par le simulateur et doit donc être
rapide. Elle est souvent en C ou Fortran, mais elle peut aussi être en
langage Scilab, en particulier dans la phase de mise au point.
Dans cet article on ne considère que les blocs
<<réguliers>> ; il existe dans Scicos d'autres types de
bloc, par exemple les blocs Zero-crossing et les blocs
utilisés pour le conditionnement d'événements. On a
rarement besoin de définir de nouveaux blocs de tels types.
La fonction d'interface
La fonction d'interface d'un bloc détermine non seulement sa
géométrie, sa couleur de fond, son icône, les nombres et
les tailles de ses ports d'entrées-sorties, etc..., mais aussi ses
états initiaux et ses paramètres. De plus, elle gère le
dialogue qui permet de modifier ses propriétés ; ce que
l'utilisateur peut faire en cliquant sur le bloc.
Si le bloc qu'on veut définir n'a pas un aspect très particulier
(c'est-à-dire s'il est rectangulaire, avec des ports placés aux
endroits habituels et une icône ne contenant qu'un texte), il est assez
facile de construire sa fonction d'interface en copiant celle d'un bloc
prédéfini. La seule partie qu'il faut adapter est la gestion de
dialogue avec l'utilisateur, utilisé pour modifier les paramètres
du bloc.
Dans la palette Others on trouve le bloc GENERIC. Ce
bloc a une fonction d'interface générique qui peut être
utilisée dans la plupart des cas pour tester et mettre au point une
fonction de simulation. Cette fonction d'interface suppose cependant que le
dialogue avec l'utilisateur (suite au clic sur le bloc) est
générique : elle propose la possibilité de modifier
tous les paramètres du bloc, même ceux qui ne sont pas
utilisés dans le bloc concerné, et ne fournit presqu'aucun test
de validité sur les réponses de l'utilisateur. Cette fonction
doit être modifiée en particulier pour protéger et informer
les autres utilisateurs de ce bloc qui ne connaîtraient pas
forcément son fonctionnement interne. Nous verrons plus tard sur un
exemple comment cela peut se faire.
La fonction de simulation
Cette fonction est appelée au cours de la simulation pour effectuer les
tâches suivantes :
- Initialisation : Le simulateur appelle cette fonction une fois
tout au début de la simulation pour lui permettre d'initialiser ses
états initiaux (si nécessaire car les états initiaux sont
aussi initialisés par la fonction d'interface). À cette occasion,
la fonction peut effectuer d'autres tâches comme par exemple ouvrir et
initialiser une fenêtre graphique ou ouvrir un fichier. À ce stade
les entrées du blocs ne sont pas disponibles.
- Ré-initialisation : Dans ce cas, les valeurs des
entrées sont disponibles et la fonction doit initialiser les sorties du
bloc. À cette occasion, la fonction peut aussi ré-initialiser ses
états initiaux.
- Calcul des sorties : La fonction calcule ses sorties en
fonction des valeurs de ses entrées et de ses états.
- Mise à jour des états : La fonction met à
jour son état discret en fonction de ses états courants et de ses
entrées. Elle peut faire de même avec son état continu
(pour modéliser des sauts éventuels).
- Calcul de la dérivée de l'état continu :
Si le bloc contient un état continu, dans ce cas la fonction de
simulation calcule la dérivée de cet état.
- Calcul des dates des événements de sortie : Si le
bloc a des ports d'événements de sortie, la fonction fournit
l'information concernant les dates des prochaines générations de
ces événements.
- Terminaison : Tous les blocs sont appelés une fois
à la fin de la simulation pour qu'ils effectuent des tâches comme
la fermeture des fichiers, si nécessaire.
Un <<flag>> dans la liste d'appel de la fonction de simulation
indique la tâche à effectuer :
| Flag |
Tâche |
| 0 |
Calcul de la dérivée de
l'état continu |
| 1 |
Calcul des sorties |
| 2 |
Mise à jour des états |
| 3 |
Calcul des dates des événements
de sortie |
| 4 |
Initialisation |
| 5 |
Terminaison |
| 6 |
Ré-initialisation |
Les autres arguments de la liste d'appel de la fonction de simulation sont des
pointeurs sur les états, les entrées, les sorties, les
paramètres, etc..., et leurs dimensions. On y trouve aussi un entier
(indicateur d'activation) indiquant dans quelle condition la fonction a
été appelée (le bloc correspondant a été
activé).
La liste d'appel de la fonction de simulation d'un bloc régulier n'a pas
une syntaxe unique ; Scicos accepte plusieurs types de fonctions de
simulation ayant des listes d'appel différentes.
| Type de fonction |
Scilab |
Fortran |
C |
Commentaires |
| 1 |
non |
oui |
oui |
liste d'appel de longueur variable |
| 2 |
non |
non |
oui |
liste d'appel de longueur fixe |
| 3 |
oui |
non |
non |
les entrées-sorties sont des
list Scilab |
On ne va pas faire ici une présentation exhaustive de tous les types de
fonctions de simulation ; on ne considérera que le type 1 qui
est utilisée dans 90% des blocs prédéfinis de Scicos. Les
fonctions de simulation associées à ce type de bloc ont une liste
d'appel de longueur variable en fonction des nombres d'entrées et de
sorties. Par exemple un bloc ayant deux entrées et une sortie aura une
fonction de simulation en C qui serait comme suit :
void mafonc(int* flag,int* nevprt,double* t,double* xd,
double* x,int* nx,double* z,int* nz,double* tvec,
int* ntvec,double* rpar,int* nrpar,int* ipar,int* nipar,
double* u1,int* nu1,double* u2,int* nu2,double* y1,int* ny1)
{
................
}
Pour plus d'information sur les arguments de cette fonction voir
Tableau 1.
| I/O |
Args. |
Description |
| I |
flag |
0,1,2,3,4,5 ou 6 |
| I |
nevprt |
indicateur d'activation |
| I |
t |
temps |
| O |
xd |
dérivée de l'état continu
|
| I/O |
x |
état continu |
| I |
nx |
taille de x |
| I/O |
z |
état discret |
| I |
nz |
taille de z |
| O |
tvec |
dates des événements de sorties
(pour flag=3) |
| I |
ntvec |
taille de tvec |
| I |
rpar |
paramètres |
| I |
nrpar |
taille de rpar |
| I |
ipar |
paramètres |
| I |
nipar |
taille de ipar |
| I |
ui |
iième entrée,
i=1,2,.. |
| I |
nui |
taille de ui |
| O |
yj |
jième sortie, j=1,2,..
|
| I |
nyj |
taille de uj |
Table 1:
La fonction de simulation est appelée quand le bloc correspondant est
activé. Après les initialisations et au cours de la simulation,
cela se produit parce que le bloc reçoit un événement sur
l'un de ses ports d'entrées d'événement ou dans le cas
d'absence de ce type de port, il hérite d'un événement par
l'une ou plusieurs de ses entrées. L'indicateur d'activation indique par
quelle voie le bloc a été activé (1 si
l'événement d'activation a été reçu sur le
premier port d'entrée d'événement, 2 si sur le
deuxième, 3 si sur le premier et sur le deuxième
simultanément, 4 si sur le troisième, etc...). Le bloc peut aussi
être activé sans qu'il reçoive d'événement
d'activation s'il a été déclaré comme étant
<<temps-dépendant>>, c'est-à-dire actif en
permanence. Dans ce cas, l'indicateur d'activation vaut 0.
À chaque activation du bloc en cours de simulation, la fonction de
simulation peut être appelée une ou plusieurs fois. Dans le cas
d'un simple bloc réalisant une fonction immédiate, par exemple le
bloc sinus, la fonction n'est appelée qu'une seule fois
avec flag 1. Un bloc contenant un état discret sera aussi
appelé avec flag 2, et si de plus le bloc a des ports de sorties
d'événement, il sera appelé aussi avec flag 3.
L'ordre des appels, dans le cas d'une activation par événement,
est flag 1 suivi par flag 3, et enfin flag 2. Dans le cas
d'activation continue, c'est 1 puis 0.
Exemple
Dans l'article précédent on a construit un système
dynamique modélisant la régulation de la population des requins
et des sardines (voir Figure 1).
Figure 1: Le modèle de la régulation de la population des requins
et des sardines de l'article précédent (Linux Magazine 18).
Dans ce cas, presque la moitié des blocs sont utilisés pour
implanter le modèle de la population des requins et des sardines :
{
| x0'(t)=a x0(t)-b x0(t)x1(t)+f x0(t) a,b
> 0, f<0, |
| x1'(t)=c x0(t)x1(t)-d x1(t) c,d
> 0 |
. (noter qu'il y a un petit changement de notation par rapport à
l'article précédent : ici x0(t) représente le nombre de sardines et
x1(t) représente le
nombre de requins).
Les autres blocs sont dédiés à la partie régulation
et à la visualisation. Il serait donc interessant de définir avec
un seul bloc le modèle de la population. Ce bloc aurait comme
entrée f (l'effort de pêche), et le vecteur
x=(x0,x1) comme état continu et sortie. Les
paramètres seraient
rpar=(a,b,c,d). La fonction de
simulation (en C) pourrait être écrite comme suit :
void pred_prey(int* flag,int* nevprt,double* t,double* xd,
double* x,int* nx,double* z,int* nz,double* tvec,
int* ntvec,double* rpar,int* nrpar,int* ipar,int* nipar,
double* u1,int* nu1,double* y1,int* ny1,double* y2,int* ny2)
{
if (*flag == 1 || *flag == 6) {
/* y=x */
*y1=x[0];
*y2=x[1];
} else if (*flag == 0) {
/* x0d = a*x0-b*x0*x1+f*x0 */
/* x1d = c*x0*x1-d*x1 */
xd[0]=rpar[0]* x[0]-rpar[1]* x[0]* x[1] + *u1* x[0];
xd[1]=rpar[2]* x[0]* x[1]-rpar[3]* x[1];
}
return ;
}
Ce bloc ne sera appelé qu'avec les flags 0, 1, 4, 5 et 6. Pour les
flags 4 et 5, il n'y a clairement rien à faire. Pour les
flags 1 et 6, il faut calculer les sorties, c'est-à-dire
recopier l'état dans les sorties. Enfin, pour le flag 0, il faut
calculer la dérivée de x que l'on place dans
xd (c'est-à-dire x').
Une fois cette fonction compilée (et mise par exemple dans le fichier
pred_prey.o), on peut la linker dans Scilab et l'utiliser dans
Scicos. Pour la linker, on utilise la fonction link :
-->link('pred_prey.o','pred_prey','C')
La fonction de simulation pred_prey est maintenant utilisable dans
Scicos pour la construction du bloc Pred-Prey. Il ne nous reste
donc qu'à écrire la fonction d'interface.
Dans un premier temps, on utilise la fonction d'interface canonique
GENERIC. On commence par supprimer tous les blocs qu'on veut
remplacer dans le diagramme. Puis on copie le bloc GENERIC qui se
trouve dans la palette Others et on le place dans le diagramme. En
cliquant dessus, on ouvre une fenêtre de dialogue qui permet de choisir
ses paramètres (voir les Figures 2 et 3).
Figure 2: Le dialogue du bloc GENERIC : le nom de la fonction de
simulation, son type, les tailles et les nombres d'entrées-sorties,
etc...
Figure 3: Le dialogue du bloc GENERIC (suite).
Noter les deux dernières questions. La première concerne
l'existence d'une dépendence directe entrée-sortie. Dans ce cas,
les sorties x0 et x1 ne dépendent pas directement de l'entrée
f (seulement la dérivée de x0 en dépend, on considère cela comme une
dépendance indirecte). La deuxième question concerne la
propriété de temps-dépendance. La réponse est oui
car ce bloc doit être actif en permanence, il n'est pas activé par
des signaux d'activation.
Après avoir cliqué sur OK, on constate que le bloc
GENERIC se redessine avec un port d'entrée et deux ports de
sortie. On peut alors brancher les entrées et les sorties pour obtenir
le schéma de la Figure 4.
Figure 4: L'utilisation du bloc GENERIC.
La simulation du nouveau schéma montre qu'il est identique au
précédent en fonctionnalité et un peu plus efficace en ce
qui concerne la vitesse de simulation.
Ce bloc peut maintenant être copié, placé dans une palette,
etc..., comme n'importe quel autre bloc Scicos. Mais l'utilisation de ce bloc
n'est pas conviviale. On peut bien entendu adapter le bloc en changeant sa
couleur et son icône, mais quand on clique dessus, on a toujours le
même dialogue canonique dont la moitié des questions ne sont pas
pertinentes.
Il faut donc construire une fonction d'interface pour présenter un
dialogue spécifique avec éventuellement des tests de
validité (par exemple ne pas accepter des paramètres a,
b, c, d négatifs). Une fonction d'interface qui
permet de faire cela est la suivante :
function [x,y,typ]=P_Prey(job,arg1,arg2)
x=[];y=[];typ=[]
select job
case 'plot' then
standard_draw(arg1)
case 'getinputs' then
[x,y,typ]=standard_inputs(arg1)
case 'getoutputs' then
[x,y,typ]=standard_outputs(arg1)
case 'getorigin' then
[x,y]=standard_origin(arg1)
case 'set' then
x=arg1
graphics=arg1(2);model=arg1(3);label=graphics(4)
while %t do
[ok,a,b,c,d,x0,label]=getvalue('Definissez les parametres du bloc',..
['a';'b';'c';'d';'Etat initial'],..
list('vec',1,'vec',1,'vec',1,'vec',1,'vec',2),label)
if ~ok then break,end
if a<0 | b<0 | c<0 | d<0 then
message('les parametres doivent etre positifs')
else
if ok then
graphics(4)=label;
model(6)=x0(:);model(8)=[a;b;c;d];
x(2)=graphics;x(3)=model
break
end
end
end
case 'define' then
x0=[2;1];a=2;b=1;c=.3;d=1; //default
model=list(list('pred_prey',1),1,[1;1],[],[],x0,[],[a;b;c;d],[],..
'r',[],[%f %t],' ',list())
label=[string(a);string(b);string(c);string(d);strcat(sci2exp(x0))]
gr_i=['xstringb(orig(1),orig(2),''Pred_prey'',sz(1),sz(2),''fill'');']
x=standard_define([3 2],model,label,gr_i)
end
On a utilisé ici les fonctions standard pour les cas plot
(dessiner le bloc), getinputs (retourner les coordonnés des
entrées), getoutputs (retourner les coordonnés des
sorties), getorigin (retourner les coordonnés du bloc dans
le schéma). Cela peut se faire pour tous les blocs rectangulaires. Les
cas set et define sont par contre spécifiques.
Le premier gère le dialogue avec l'utilisateur et le deuxième
définit les valeurs initiales des paramètres.
Pour comprendre cette fonction, il faut savoir qu'un bloc Scicos est
défini par une list Scilab comme suit :
list('Block',graphics,model,unused,interf_function)
Les variable graphics et model sont aussi des
list. La structure graphique graphics contient des
informations sur l'aspet graphique du bloc comme sa taille, son emplacemement,
son orientation, etc..., et model des informations
nécessaires pour la simulation comme le nom de la fonction de simulation
et son type, les nombres et les tailles des ports d'entrées-sorties, les
valeurs des états, des paramètres, etc... Enfin,
interf_function est une chaîne de caractères Scilab
contenant le nom de la fonction d'interface associée à ce bloc.
La variable model est initialisée dans le cas
define. On constate alors que le nom de la fonction de simulation
est pred_prey, qui est de type 1, que le bloc a une seule
entrée de taille 1 et deux sorties de taille 1 chacune, qu'il
n'a ni entrée, ni sortie de type événement, que son
état continu initial est [2;1], qu'il n'a pas d'état discret,
qu'il a comme paramètre réel le vecteur [2;1;.3;1] et pas de
paramètre entier et qu'il s'agit d'un bloc
<<régulier>>. À noter aussi l'élément
[%f %t] du model de notre exemple qui indique que ce
bloc ne contient pas de dépendence directe entrée-sortie, mais
qu'il est temps-dépendant. Les autres champs sont utilisés pour
la documentation et identification.
Pour utiliser ce bloc, il suffit maintenant d'utiliser le bouton
AddNewBlock du menu Edit. On peut alors remplacer le
bloc GENERIC par ce bloc (Pred_prey). La simulation
donne alors exactement le même résultat qu'avant, mais maintenant
en cliquant sur le bloc on obtient :
Pour construire des blocs plus sophistiqués, on peut toujours chercher
un bloc prédéfini ayant des caractéristiques semblables
à ce qu'on cherche à construire et examiner les sources de ses
fonctions correspondantes. Les fonctions d'interfaces des blocs
prédéfinis se trouvent dans le répertoire
<SCI>/macros/scicos_blocks et les fonctions de simulation
dans <SCI>/routines/scicos. Pour plus d'information, en
particulier sur le formalisme de base et le fonctionnement interne de Scicos,
voir http://www-rocq.inria.fr/scilab/doc/scicos_html/.
Ramine Nikoukhah
Scilab Group
scilab@inria.fr
This document was translated from LATEX
by HEVEA.