Scilab : un logiciel libre pour le calcul
scientifique
troisième partie :
<<Structure de données et surcharge d'opérateurs.>>
troisième partie :
<<Structure de données et surcharge d'opérateurs.>>
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)
Un langage matriciel, la possibilité de définir de nouveaux objets et des mécanismes de surcharge des opérateurs et des fonctions font de Scilab un langage de programmation puissant permettant d'écrire des programmes complexes de façon lisible et compacte.
Nous avons évoqué dans les premiers articles les objets de base de Scilab (matrices de nombres, de polynômes, de chaînes de caractères et de booléens). Mais pour développer des programmes complexes avec un code lisible et concis, le programmeur a besoin de structures pouvant contenir des données hétérogènes. Scilab permet de définir et de manipuler ces nouveaux objets aisément. Par exemple :
-->tableau=list(['x';'y'],['a','b','c'],rand(2,3))
crée une structure de données représentant un tableau de nombres auquel est associé des noms de lignes et de colonnes. Les différents champs de cette structure sont accessibles par leur rang dans la structure.
-->tableau(2)
ans=!a b c !
-->sum(tableau(3)(1,:))
ans=1.1943134
On note ici l'usage du symbole << : >> qui désigne l'ensemble des lignes de la matrice.
Il est bien évidemment possible de changer la valeur d'un champ -->tableau(3)=[1 2 3;4 5 6];
-->tableau(3)
ans=! 1. 2. 3. ! ! 4. 5. 6. !
voire même d'une sous-matrice contenue dans l'un des champs -->tableau(3)(:,2)=[0;0];
-->tableau(3)
ans=! 1. 0. 3. ! ! 4. 0. 6. !
Ce type de structure peut être récursif définissant ainsi des structures de données arborescentes, comme dans l'exemple ci-dessous où l'on veut décrire l'arbre des descendants d'une personne. Un individu y est décrit par une liste contenant son état civil (prénom, date de naissance, liste des enfants). Cette structure est créee par la fonction etat_civil .
function individu=etat_civil(prenom,date_naissance,enfants)
[lhs,rhs]=argn()
if rhs==2 then enfants=list(),end
individu=list(prenom,date_naissance,enfants)
On note ici l'usage de la fonction argn qui permet de connaître le nombre effectif d'arguments de sortie et d'entrée passés lors de l'appel de la fonction.
Exemple d'utilisation : -->JeanPierre=etat_civil('Jean-Pierre',1908)
JeanPierre=
JeanPierre(1)
Jean-Pierre
JeanPierre(2)
1908.
JeanPierre(3)
()
On observe ci-dessus la visualisation par défaut des structures.
La fonction récursive nouveau ajoute un nouveau descendant à un ascendant spécifié par son prénom.
function l=nouveau(l,individu,ascendant)
if l(1)==ascendant then // L'ascendant designe a ete trouve
l(3)($+1)=individu // Extension de la liste des enfants
else
for k=1:size(l(3)) // Parcours de l'arbre des descendants
l(3)(k)=nouveau(l(3)(k),individu,ascendant)
end
end
Pour illustrer l'usage de ces fonctions, créons la descendance de Jean Pierre :
arbre=etat_civil('Jean-Pierre',1908);// l'ancetre
// et sa descendance ...
arbre=nouveau(arbre,etat_civil('Maurice',1928),'Jean-Pierre');
arbre=nouveau(arbre,etat_civil('Francois',1930),'Jean-Pierre');
arbre=nouveau(arbre,etat_civil('Marianne',1953),'Francois');
arbre=nouveau(arbre,etat_civil('Claude',1960),'Maurice');
arbre=nouveau(arbre,etat_civil('Serge',1965),'Maurice');
arbre=nouveau(arbre,etat_civil('Jean-Philippe',1985),'Marianne');
La visualisation par défaut de la structure arbre n'est pas très lisible, nous définissons donc la fonction récursive affiche_arbre pour réaliser une visualisation spécifique.
On peut noter ici l'usage du symbole <<+ >> pour la concaténation des chaînes, de la fonction string qui convertit un nombre en une chaîne de caractères et de la fonction exists qui permet de savoir si une variable existe.
Cette fonction génere le résultat suivant : -->affiche_arbre(arbre) Jean-Pierre 1908 Maurice 1928 Claude 1960 Serge 1965 Francois 1930 Marianne 1953 Jean-Philippe 1985 Avec de telles structures, le programmeur veut quelquefois désigner les champs par des noms symboliques plutôt que par un numéro. Cela peut être fait en utilisant les structures <<typées>> créees par la fonction tlist. Ce type de structure hérite de toutes les propriétés des structures précédentes mais il est de plus possible d'y attacher un type symbolique ainsi que de nommer les champs.
La fonction etat_civil peut être modifiée comme suit pour utiliser ce type de structure :
function individu=etat_civil(prenom,date_naissance,enfants) [lhs,rhs]=argn() if rhs==2 then enfants=list(),end individu=tlist(['etat','prenom','date','enfants'],.. prenom,date_naissance,enfants)Le premier champ de la nouvelle structure individu est un vecteur de chaînes de caractères. La première chaîne donne le type symbolique de la structure et les suivants les noms symboliques des champs. La fonction nouveau s'écrit alors de façon plus lisible :
function l=nouveau(l,individu,ascendant) if l.prenom==ascendant then // L'ascendant designe a ete trouve l.enfants($+1)=individu // Extension de la liste des enfants else for k=1:size(l.enfants) // Parcours de l'arbre des descendants l.enfants(k)=nouveau(l.enfants(k),individu,ascendant) end end
Et la fonction %etat_p ci-dessous adaptée de affiche_arbre permet de surcharger la visualisation par défaut des structure de type etat. Le nom la fonction (%etat_p) est construit à partir du nom symbolique du type etat et du symbole du display <<p>>.
function %etat_p(descendants)
if ~exists('shift') then shift='',end
// affiche le niveau courant
write(%io(2),shift+descendants.prenom+' '+string(descendants.date))
// accroit le decalage les descendants
shift=shift+' ';// decalage du texte relatif a ce niveau
for k=1:size(descendants.enfants) // boucle sur les descendants
// appel recursif de la fonction pour traiter les descendants
%etat_p(descendants.enfants(k))
end
Il n'est alors plus besoin d'appeler explicitement une fonction pour
réaliser la visualisation de la structure arbre :-->arbre Jean-Pierre 1908 Maurice 1928 Claude 1960 Serge 1965 Francois 1930 Marianne 1953 Jean-Philippe 1985De la même façon, la sémantique de l'insertion peut être surchargée en définissant la fonction %etat_i_etat comme ci-dessous. Le nom de la fonction est contruit à partir du nom symbolique des opérateurs (ici etat) et du symbole de l'opérateur (ici <<i>> pour l'insertion).
function l=%etat_i_etat(ascendant,individu,l) if l.prenom==ascendant then l.enfants($+1)=individu else for k=1:size(l.enfants) // boucle sur les descendants l.enfants(k)=nouveau(l.enfants(k),individu,ascendant) end endLa définition de la descendance se fait alors plus simplement :
arbre=etat_civil('Jean-Pierre',1908);// l'ancetre // et sa
descendance arbre('Jean-Pierre')=etat_civil('Maurice',1928);
arbre('Jean-Pierre')=etat_civil('Francois',1930);
arbre.Maurice=etat_civil('Claude',1960); arbre.Maurice=etat_civil('Serge',1965)
arbre.Francois=etat_civil('Marianne',1953)
arbre.Marianne=etat_civil('Jean-Philippe',1985)
On note ici l'usage de la syntaxe arbre('Jean-Pierre')
au lieu de la notation <<.>>
à cause du symbole opératoire <<-
>> inclut dans le prénom. Toujours plus fort! Le programmeur veut maintenant définir des matrices d'objets. Nous allons illustrer comment cela peut se faire sous Scilab pour le calcul automatique de la dérivée d'une fonction Scilab par surcharge d'opérateurs.
Étant donnée une fonction Scilab F réalisant y=F(x) où x et y sont des vecteurs ou des matrices, on veut pouvoir calculer la valeur de la fonction G
| G(x,z)= |
|
Cela peut se faire tout simplement en remplaçant x par une structure xdx contenant x et z et en surchargeant la définition des opérateurs et des fonctions de base de Scilab pour que les différents calculs définis dans la fonction F évaluent la valeur de la dérivée de leur résutat par rapport à l en même temps que la valeur elle-même.
La fonction der ci-dessous permet de créer un nouveau type de donnée qui contient la valeur d'une variable en un point x ainsi que la valeur de la dérivée directionnelle dx.
function [xdx]=der(x,dx) xdx=mlist(['der';'v';'dv'],x,dx)On note ici l'usage de la fonction mlist qui permet de créer des structures de données orientées matricielle (ce qui veut dire que les syntaxe xdx(i) et xdx(i,j) seront interprétées comme l'indexage de sous-matrice de xdx).
Nous souhaitons appliquer cette méthode à la fonction normalize :
function x=normalize(x) for k=1 : size(x,2) s=sum(x(:,k)) if s~=0 then x(:,k)=x(:,k)/s, end endPour ne pas compliquer l'exposé, nous ne montrerons que les fonctions de surcharges nécessaires. Commençons par les fonctions qui surchargent l'extraction de sous-matrice
function [z]=%der_e (varargin) if size(varargin)==2 then //extraction x(i) [i,x]=varargin(:) ; z=der(x.v(i),x.dv(i)) else // extraction x(i,j) [i,j,x]=varargin(:) ; z=der(x.v(i,j),x.dv(i,j)) endet l'insertion function y=%der_i_der(varargin) if size(varargin)==3 then // syntaxe y(i)=x [i,x,y]=varargin(:) y.v(i)=x.v ; // insertion pour le champ valeur y.dv(i)=x.dv ;// insertion pour le champ derivee else // syntaxe z(i,x)=y [i,j,x,y]=varargin(:) y.v(i,j)=x.v y.dv(i,j)=x.dv end On note ici l'usage du mot clé varargin qui permet de passer un nombre variable d'arguments d'entrée. La variable varargin est la structure (de type list) des arguments effectifs d'entrée.
Les fonctions de surcharge des fonctions size et sum s'écrivent comme suit :
function m=%der_size(a,flag) m=size(a.v,flag) function z=%der_sum(x) z=der(sum(x.v),sum(x.dv))Bien évidemment la dérivée de la somme est égale à la somme des dérivées.
En ce qui concerne les fonctions de surcharge des opérateurs << />> et <<=>> le nom de la fonction est construit à partir du nom de type de chaque opérande et du code de l'opérateur qui est <<r>> pour la division à droite et <<n>> pour la comparaison.
function z=%der_r_der (x,y) r=x.v / y.v z=der(r,(x.dv - r*y.dv)/ y.v )
|
= |
|
/y - x |
|
/y2 |
La fonction de surcharge de la comparaison est beaucoup plus simple :
function z=%der_n_s(x,y) z=x.v
yEt miracle, sans rien changer à la fonction normalize, on obtient
-->a=[1 2;3 4]; -->y=normalize(a) y= ! 0.25 0.3333333 ! ! 0.75 0.6666667 ! -->A=der(a,eye(a)); -->Y=normalize(A) Y= Y(1) !der ! ! ! !v ! ! ! !dv ! Y(2) ! 0.25 0.3333333 ! ! 0.75 0.6666667 ! Y(3) ! 0.1875 - 0.0555556 ! ! - 0.1875 0.0555556 ! -->norm(Y.dv-(normalize(a+1.d-9*A.dv)-y)/1.d-9) //test ans= 2.288D-08Cet exemple est extrait d'une boîte à outil de différentiation automatique de programmes en préparation. Par ailleurs, un certain nombre de types de variable définis dans la version 2.5 de Scilab (matrice de fractions rationnelles, systèmes dynamiques linéaires et matrices n-dimensionnelles sont codés par des structures Scilab et les opérations associées définies par surcharge d'opérateurs.
Par exemple, cela permet une syntaxe d'utilisation des matrices n-dimensionnelles très proche de celle des matrices ordinaires.
-->a=[1 2;3 4]; -->a(:,:,2)=eye(2,2) a=(:,:,1) ! 1. 2. ! ! 3. 4. ! (:,:,2) ! 1. 0. ! ! 0. 1. ! -->[a a] ans=(:,:,1) ! 1. 2. 1. 2. ! ! 3. 4. 3. 4. ! (:,:,2) ! 1. 0. 1. 0. ! ! 0. 1. 0. 1. !
Par ailleur, le traducteur Matlab -> Scilab, entièrement écrit en Scilab, utilise massivement ce type de structures. Pour ménager le suspense nous n'avons parlé ici que de la surcharge des opérateurs et fonctions par la définition de fonctions écrites en Scilab. Cela est aussi possible par la définition de nouvelles primitives écrites en C ou en Fortran.
Pour en apprendre d'avantage sur les capacités de Scilab, rendez-vous au prochain numéro...
Serge Steer
Scilab Group
scilab@inria.fr
http://www-rocq.inria.fr/scilab/
Newsgroups: comp.soft-sys.math.scilab
This document was translated from LATEX by HEVEA.
