Découvrir Scilab/Programmation
6. Programmation
Scilab est un langage de programmation, il accepte un certain nombre d’instructions autres que mathématiques, permettant l'exécution d'algorithmes.
L'écriture de programmes se fait idéalement avec l'éditeur de texte SciNotes ; celui-ci met en exergue les instructions en couleurs, les parenthésages (correspondance entre les paires de parenthèses et de crochets), et surligne les lignes continuées avec un fond jaune. On peut aussi utiliser un autre éditeur de texte en sauvegardant le fichier avec l'extension .sce
ou .sci
. Lorsque l'environnement le permet, on peut faire du copier-coller depuis l'éditeur de texte externe vers SciNotes ou bien l'éditeur de ligne de commande.
Différence entre les extensions .sce
et .sci
modifier
Les extensions de nom de fichier .sce
et .sci
sont toutes deux utilisées pour désigner des fichiers texte contenant des scripts Scilab. D'un point de vue de l'utilisateur, il y a peu de différences fonctionnelles entre les deux. Toutefois, la logique voulue par les concepteurs est la suivante[1] :
- les fichiers dont le nom porte l'extension
.sci
ne devraient contenir que des définitions de fonctions ; ce sont des bibliothèques ; en particulier, seuls les fichiers.sci
sont chargés par la commandegetd()
(voir Syntaxe pour définir une fonction > Fonction définie dans un fichier extérieur) ; - les fichiers dont le nom porte l'extension
.sce
devraient contenir le reste du script, et quelques fonctions locales.
Voir les sections suivantes :
Interaction avec l'utilisateur
modifierInteragir avec l'utilisateur, c'est :
- lui permettre d'entrer des données, par exemple des valeurs, paramètres, faire des choix, indiquer un fichier de données, … ce qui évite d'avoir à modifier le code à chaque utilisation spécifique et rend le script plus « universel » ;
- lui permettre de récupérer les résultats.
Entrées
modifierNous nous contentons dans cette section des entrées au clavier par le biais de la console Scilab. Les interactions plus complexes sont présentées dans le chapitre suivant Créer une interface graphique GUI.
La fonction input
permet à l'utilisateur de rentrer une valeur. la syntaxe est :
x = input("message")
où message est une chaîne de caractères qui s'affiche et x est la variable dans laquelle sera mise la valeur (ou la matrice) entrée par l'utilisateur. Si l'utilisateur doit entrer une chaîne de caractères, il faut écrire (deux possibilités) :
x = input("message", "string")
x = input("message", "s")
Lorsqu'une variable nom_de_variable existe, alors
editvar nom_de_variable
ouvre une fenêtre permettant de modifier la contenu de cette variable. On peut également définir la valeur d'une variable en affichant une boîte de dialogue
a = x_dialog("message", "a0")
où a0
est la valeur initiale de a. Si l'on veut un message sur plusieurs lignes, on utilise une matrice de chaînes de caractères, par exemple
a = x_dialog(["Entrez un" ; "nombre entier"], "1")
On peut aussi faire cliquer l'utilisateur et récupérer les coordonnées de l'endroit cliqué :
[bouton, x, y] = xclick
la valeur de bouton correspond à l'action menée par l'utilisateur.
La fonction halt
arrête l'exécution du programme jusqu'à ce que l'utilisateur appuie sur une touche.
Sorties
modifierNous avons vu jusqu'ici l'affichage graphique et la génération de fichiers d'image et de son (voir le chapitre précédent Graphiques et sons). Nous présentons ici l'affichage dans la console ; d'autres méthodes plus avancées sont présentées dans le chapitre suivant Créer une interface graphique GUI.
Afficher un message
modifierLa fonction
print(%io(2),a)
affiche le contenu de la variable a à l'écran. On peut aussi utiliser
write(%io(2),a)
La fonction
disp(a)
affiche le contenu de a sans faire figurer « a =
» devant. On peut afficher un message dans la barre d'information située en bas de la fenêtre graphique courante, avec
xinfo("message")
La fonction warning(message)
affiche la chaîne de caractère message sous la forme d'un avertissement, c'est-à-dire précédé de « WARNING:
». La fonction error(message)
affiche la chaîne de caractère message sous la forme d'un message d'erreur, c'est-à-dire précédé de « !--error 9999
».
On peut utiliser la manière printf()
du langage C pour inclure des variables mises en forme :
a = 1/6
mprintf("La valeur vaut : %.2f", a)
va afficher « La valeur vaut : 0.17 »
Lancer une impression
modifierPour lancer une impression, on peut utiliser la fonction toprint()
, qui accepte comme paramètre :
- pour imprimer le contenu d'un fichier texte : un nom de fichier (et éventuellement son chemin d'accès) sous la forme d'une chaîne de caractères ;
- pour imprimer des lignes de texte : une matrice de chaînes de caractères (une entrée par ligne de texte), avec éventuellement une chaîne additionnelle pour l'en-tête ;
- pour imprimer une figure : le numéro de la figure (défini par
scf()
ou récupéré parget(gcf(), "figure_id")
[1]) ;
la fonction retourne un booléen indiquant si l'impression a réussi ou pas.
Par exemple :
status = toprint("monfichiertexte.txt"); // impression du contenu du fichier
scf(0);
plot2d();
toprint(0); // impression de la fenêtre graphique
toprint(["ligne 1", "ligne 2", "ligne 3"], "en-tête");
Pour les figures, on peut aussi utiliser
scf(0);
plot2d();
printfigure(0); // impression de la fenêtre graphique
qui, en outre, ouvre la boîte de dialogue de configuration de l'impression.
On peut ouvrir cette boîte de configuration pour modifier les paramètres par défaut, avec la commande
printsetupbox
Interaction avec le système d'exploitation
modifierIl est possible de demander au système d'exploitation (SE) d'effectuer des actions.
Déterminer le SE
La commande
SE = getos()
permet de connaître le système d'exploitation courant.
Sous Microsoft Windows
La commande
winopen("nom_de_fichier")
demande au SE d'ouvrir un fichier.
Pour cela, l'extension de nom de fichier doit être associée à un programme extérieur. On peut connaître ce programme avec la commande findfileassociation()
, par exemple
findfileassociation(".txt")
Sous les Unix et Microsoft Windows
La commande unix()
permet de faire effectuer une commande au SE, que ce soit un unix (dont un Linux, BSD ou MacOS X) ou bien Microsoft Windows. La commande renvoie un entier : si l'exécution est possible, le résultat est le code de l'interpréteur de commande, et si elle est impossible, le résultat est -1.
On peut rediriger le contenu de la sortie standard vers la fenêtre Scilab avec unix_w()
, ou bien vers une variable avec la commande unix_g()
, par exemple
if getos() == "Windows" then
A = unix_g("dir "+SCIHOME);
else
A = unix_g("ls "+SCIHOME);
end
Structures de contrôle (boucles et branchements conditionnés)
modifierExécution conditionnelle
modifierL'exécution conditionnelle se fait de manière classique avec les commandes if condition then, …, else, …, end
if condition1 then
instructions
...
elseif condition2 then
instructions
...
else
instructions
...
end
Si une variable peut prendre plusieurs valeurs, on peut utiliser la structure select … case … else … end
:
select nom_variable
case valeur1 then
instructions
case valeur2 then
instructions
...
case valeurn then
instructions
else
instructions
end
Boucle itérative
modifierLa syntaxe d'une boucle for
est la suivante :
for variable=expression
do
instructions
…
end
Le mot clef do
est optionel. Il est possible d'écrire la boucle sur une seule ligne :
for variable=expression, instruction, …, instruction, end
L'expression
est un vecteur ou une liste contenant les différentes valeurs qui seront successivement prises par variable
.
Exemples :
- la variable
for i=1:10 ...
i
prend successivement les valeurs 1, 2, ..., 10. - la variable
for i=list (1, 2, "a") ...
i
prend successivement les valeurs 1, 2 et "a".
Boucle itérative antéconditionnée
modifierwhile condition
instructions1
…
[else
instructions2
…]
end
while condition, instructions1, … [, else instructions2, …], end
Le bloc d'instructions instructions1
est exécuté tant que condition
est vraie. Le bloc d'instructions instruction2
n'est exécuté qu'une seule fois dès que condition
est fausse.
Interruption d'une boucle
modifierLorsque le mot clé break
[2] est rencontré dans une boucle for
ou while
, l'exécution de la boucle est interrompue, et l'exécution du programme se poursuit par les instructions suivants immédiatement le mot clé end
terminant la boucle.
Tri, recherche et sélection
modifierDans un certain nombre de cas, il faut trier les éléments d'un ensemble, ou bien rechercher des éléments répondant à certaines conditions au sein d'un ensemble. Par « ensemble », nous entendons ici un vecteur ou une matrice.
Tri
modifierLa fonction gsort
permet de trier ; elle utilise l'algorithme de tri rapide (quick sort, diviser pour régner). Si V est un vecteur, alors
A = gsort(V)
trie les éléments par ordre décroissant. Pour trier par ordre croissant, il faut ajouter l'option "i"
(increasing ordre) :
A = gsort(V, "i")
Si M est une matrice, alors gsort(M)
trie la matrice en classant les éléments de haut en bas puis de gauche à droite. Si M est une matrice m×n (m lignes, n colonnes), alors l'élément M(i, j) (i-ème ligne, j-ème colonne) donnera l'indice de classement
- I = i + (j - 1)×m
Si l'on veut trier les colonnes, c'est-à-dire que les éléments restent dans la même colonne mais changent de ligne (row), on utilise l'option "r"
. Et si l'on veut trier les lignes, on utilise l'option "c"
(column, les éléments changent de colonne, mais pas de ligne). Par exemple
M = rand(3,3)
A = gsort(M, "r", "i")
On peut aussi vouloir conserver les lignes intègres, ou les colonnes intègres. Pour cela, on utilise le tri « lexicographique » (tri par le premier élément, puis en cas d'égalité par le deuxième éléments, …), avec les options "lr"
ou "lc"
:
A = gsort(M, "lr", "i")
Recherche
modifierLa recherche se fait avec la fonction find
.
Par exemple, pour cherche une valeur a au sein d'un vecteur V :
I = find(V == a);
la variable I retournée est alors un vecteur contenant les numéros d'indices i tels que V(i) = v. On peut ensuite extraire les valeurs
A = V(I);
voire les supprimer du vecteur initial
V(I) = [];
Pour être un peu plus précis : V == a
est un vecteur de booléens. On peut fabriquer un vecteur de booléens avec tous types de conditions, par exemple (V > a & V ~= b)
…
Avec une matrice booléenne M, la commande
I = find(M);
renvoit un vecteur d'indice obtenu en parcourant la matrice par colonnes, de haut en bas, puis de gauche à droite. Comme pour le tri, si M est une matrice m×n (m lignes, n colonnes), alors l'élément M(i, j) (i-ème ligne, j-ème colonne) donnera l'indice
- I = i + (j - 1)×m
Si l'on veut des indices « classiques », il faut utiliser la syntaxe
[I, J] = find(M);
alors I(k), J(k) sont les indices du k-ième élément trouvé. La valeur de l'élément est extraite par M(I(k), J(k))
.
Dans l'exemple suivant, les deux matrices A et B sont identiques
M = rand(10, 10);
M > 0.5
[I, J] = find(M > 0.5);
for k = 1:size(I, "*")
A(k) = M(I(k), J(k));
end
L = find(M > 0.5);
B = M(L);
[A, B]
La commande find()
travaille uniquement sur des matrices de booléens. Cela explique que la commande ne marche pas avec les listes (voir Structures de données avancées).
La commande dsearch()
regarde si chaque élément d'une matrice est dans un intervalle donné ; l'intervalle est donné sous forme de matrice. Par exemple
dsearch([1 3 5 7], [2 4])
va regarder quels sont les éléments du vecteur (1 ; 3 ; 5 ; 7) qui sont dans l'intervalle [2 ; 4]. La réponse est
0 1 0 0
car seul le second élément est dans l'intervalle. On peut indiquer des intervalles conjoints, par exemple [2 4 6]
correspond aux intervalles I1 = [2 ; 4] et I2 = ]2 ; 6], et l'on a
-->dsearch([1 3 5 7], [2 4 6])
ans =
0. 1. 2. 0.
indiquant que le deuxième élément est dans l'intervalle 1, et le troisième éléments est dans l'intervalle 2.
Avec l'option "d"
, dsearch()
considère que le deuxième argument est un ensemble de nombres disjoints. Il cherche donc si chaque élément du premier argument est un élément du second :
-->dsearch([1 3 5 7], [3 7], "d")
ans =
0. 1. 0. 2.
indique que le premier élément de (1 ; 3 ; 5 ; 7) est le premier élément de (3 ; 7), et que le quatrième élément de (1 ; 3 ; 5 ; 7) est le second élément de (3 ; 7).
On peut aussi localiser un vecteur-ligne ou un vecteur-colonne V au sein d'une matrice M :
vectorfind(M, V, "r") // vecteur-ligne
vectorfind(M, V, "c") // vecteur-colonne
la fonction retourne un vecteur d'indices correspondant aux lignes ou colonnes trouvées.
Scilab[2] propose la fonction members()
pour chercher une « aiguille dans un botte de foin » (needle in a haystack), c'est-à-dire une hypermatrice dans une hypermatrice d'hypermatrices. Bien sûr, on peut s'en servir pour chercher un scalaire dans une matrice de scalaires, mais aussi une ligne ou une colonne particulière :
nb = members(A, BF)
[nb, pos] = members(A, BF)
cherche l'aiguille A dans la botte de foin BF, qui doit être une matrice ou hypermatrice d'éléments du même type que A ; la variable nb
est le nombre d'occurrences et pos
les indices de la première position trouvée. Par exemple[3]
mois = ["jan", "fév", "mar", "avr", "mai", "jun", "jul", "aou", "sep", "oct", "nov", "déc"];
dates = ["déc", "avr" ; "fév", "déc" ; "jun", "jul"];
[tmp, datesnum] = members(dates, mois);
disp(datesnum)
-->12. 4.
2. 12.
6. 7.
Ici, le fait d'aller cherche la position des aiguilles dans la botte permet de transformer les noms des mois en numéros.
On peut ajouter l'option "last"
pour avoir la dernière occurrence :
[nb, pos] = members(A, BF, "last")
Si l'on veut chercher une ligne, alors A doit être une matrice ligne, et l'on utilise
members(A, BF, "rows")
on peut aussi chercher les permutations de A avec l'option "shuffle"
:
members(A, BF, "rows", "shuffle")
On peut chercher des colonnes avec l'option "cols"
.
Opérations sur les ensembles
modifierCertaines matrices contiennent des éléments dupliqués. La commande unique()
permet de supprimer les doublons, ou bien, vu d'une autre manière, détermine l'ensemble des valeurs dont est constituée la matrice.
Dans sa syntaxe la plus simple
unique(M)
renvoie une matrice colonne contenant chacun des éléments, classés par ordre croissant. On peut obtenir l'indice de la première occurrence de chaque éléments avec la syntaxe
[N, k] = unique(M)
Par exemple
A = [0, 0, 1, 1;
0, 1, 1, 1;
2, 0, 1, 1;
0, 2, 2, 2;
2, 0, 1, 1;
0, 0, 1, 1];
-->unique(A)
ans =
0.
1.
2.
-->[N, k] = unique(A)
k =
1.
8.
3.
N =
0.
1.
2.
Nous voyons que la matrice A, de dimension 6×4, comporte les valeurs 0, 1 et 2 — donc qu'elle est construite sur l'ensemble {0 ; 1 ; 2} —, et que la première occurrence de 0 est à l'indice 1, la première occurrence de 1 est à l'indice 8, et la première occurrence de 2 est à l'indice 3 (comptés comme d'habitude de haut en bas, et de gauche à droite).
On peut aussi considérer les lignes uniques, avec l'option "r"
(rows), ou les colonnes uniques, avec l'option "c"
.
-->unique(A, "r")
ans =
0. 0. 1. 1.
0. 1. 1. 1.
0. 2. 2. 2.
2. 0. 1. 1.
La matrice A comporte donc quatre lignes originales, et deux lignes qui sont des répétitions.
La commande union()
fait la même opération, mais sur deux matrices : elle détermine la réunion des ensembles des valeurs des deux matrices, et le cas échéant indique l'indice de première occurrence pour chacune des matrices.
Par exemple :
A = [0 1 2
0 2 1];
B = [3 2 4;
4 3 2];
-->union(A, B)
ans =
0. 1. 2. 3. 4.
-->[N, ka, kb] = union(A, B)
kb =
1. 2.
ka =
1. 3. 4.
N =
0. 1. 2. 3. 4.
On voit donc que la réunion des ensembles est {0 ; 1 ; 2 ; 3 ; 4}. On peut de même utiliser les options "c"
ou "r"
.
Temporisation
modifierDans certains cas, il est nécessaire de suspendre l'exécution du script, par exemple pour laisser des opérations extérieures (appels système, …) s'exécuter. On peut utiliser les commandes suivantes :
sleep(n)
: suspend le processus en cours pendant n millisecondes ;xpause(n)
: suspend Scilab pendant n millisecondes ;realtimeinit(u) ; realtime(t0) ; realtime(t1)
: la première commande,realtimeinit(u)
, indique une unité en nombre de secondes u ;
la deuxième commande,realtime(t0)
, indique que l'instant actuel est t0, selon l'unité précédemment définie (habituellement, t0 = 0) ;
la troisième commande,realtime(t1)
, met Scilab en attente jusqu'à ce que la date t1 soit atteinte, selon l'unité précédemment définie .
Analyse et construction de chaînes de caractères
modifierLes chaînes de caractères sont un moyen pratique et robuste d'échanger des informations, entre Scilab et l'utilisateur (clavier, écran) ou entre logiciels (fichiers de texte) : les codes ASCII ou UTF-8 sont normalisés et reconnus sous toutes les plateformes par la plupart des logiciels.
Nous avons déjà vu précdemment l'utilisation des commandes :
ascii()
(etchar()
), pour créer une chaîne de caractères à partir de codes ASCII et vice versa ;string()
, pour transformer des nombre ou matrices de nombres en chaînes de caractères ;length()
, pour déterminer la longueur d'une chaîne ;+
etstrcat()
, pour concaténer des chaînes ;strsubst()
qui permet de « rechercher-remplacer » ;evstr()
qui permet d'exécuter une commande Scilab contenue dans une chaîne de caractères.
- Pour plus de détails voir : Découvrir Scilab/Calculs élémentaires#Chaînes de caractères.
Localiser une sous-chaîne
modifierLa commande strindex()
permet de localiser la position (le n-ième caractère) où se trouve une sous-chaîne :
-->strindex("bonjour Marcel", "r M")
ans =
7.
car la sous-chaîne « r M » commence au 7e caractère. La sous-chaîne peut également être décrite par une expression régulière (voir ci-après), il faut alors rajouter le fanion "r"
; on peut également utiliser la commande regexp()
dans ce contexte. Par exemple
-->strindex("bonjour Marcel", "/r\ M/", "r")
ans =
7.
-->regexp("bonjour Marcel", "/r\ M/")
ans =
7.
Couper à des positions données
modifierLa commande strsplit()
découpe une chaîne et crée un vecteur-colonne des sous-chaînes. Sans autre argument que la chaîne elle-même, elle la découpe en caractère individuels :
-->strsplit("abcd")
ans =
!a !
! !
!b !
! !
!c !
! !
!d !
Si l'on indique un vecteur de valeurs, il coupe après les endroits indiqués :
-->A = strsplit("aei ou y", [4, 7])
A =
!aei !
! !
!ou !
! !
!y !
-->length(A(1))
ans =
4.
La première sous-chaîne fait 4 caractères (l'espace en position 4 est inclus) ; la deuxième sous-chaîne fait trois caractères (l'espace en position 7 est inclus).
Couper à des délimiteurs
modifierEnfin, on peut indiquer des points de découpe soit sous la forme d'une chaîne de caractères, dite « délimiteur » ; il s'agit typiquement de virgules (le délimiteur décimal étant le point), points-virgules, d'espaces, de tabulations ou d'un caractère qui n'est pas utilisé dans les expressions (point d'exclamation, arobase, …). Les parties de la chaîne de caractère entre les délimiteurs sont appelés « jetons » (tokens).
Les délimiteurs par défaut sont l'espace et la tabulation, mais on peut définir soi-même le délimiteur. La commande tokens()
cherche les délimiteurs dans une chaîne de caractères, découpe la chaîne en jeton1 — délimiteur1 — jeton2 — délimiteur2… et crée une matrice colonne avec chaque sous-chaîne.
Par exemple, la commande
t = tokens("a = 5", "=")
crée la matrice ["a " ; "=" ; " 5"]
. La commande tokenpos()
indique la position des jetons, sous la forme d'une matrice : la première colonne indique la position du premier caractère des jetons, la seconde la position du dernier caractère.
-->tokenpos("a = 5", "=")
ans =
1. 2.
4. 5.
-->tokenpos("a=5", "=")
ans =
1. 1.
3. 3.
La commande strplit()
fait le même travail de découpe, mais avec plus de possibilités ; le point de découpe peut être une chaîne de caractères — on coupe là où se trouve la chaîne —, mais aussi un vecteur de chaînes — on coupe là où se trouve un des éléments du vecteur —, ou bien une expression régulière (voir ci-après).
-->strsplit("aei ou y", " ")
ans =
!aei !
! !
!ou !
! !
!y !
Ici, les caractères de séparation — les espaces — sont supprimés. La première chaîne fait 3 caractères et la deuxième en fait 2.
Les expressions régulières permettent une plus grande souplesse. Par exemple, sil l'on veut couper un nombre indéterminé d'espaces :
-->strsplit("aei ou y", "/\ +/")
ans =
!aei !
! !
!ou !
! !
!y !
Là encore, les caractères de séparation sont supprimés.
Mentionnons encore les commandes :
strchr()
, qui permet d'extraire la fin d'une chaîne à partir d'un délimiteur ;strtok()
, qui permet d'extraire le début d'une chaîne jusqu'à un délimiteur.
--> strchr("une chaîne ; un délimiteur", ";")
ans =
; un délimiteur
--> strtok("une chaîne ; un délimiteur", ";")
ans =
une chaîne
Expressions régulières
modifierUne expression régulière est une description d'une chaîne de caractères[4]. C'est elle-même une chaîne, qui commence et finit par une barre de fraction /
. Par exemple, l'expression régulière qui décrit « blabla » est /blabla/
.
Il existe un certain nombre de caractères réservés, outre la barre de fraction :
{}[]()^$.|*+?\
On les appelle « métacaractères ». Si l'on veut utiliser un de ces caractères, il faut le faire précéder d'une oblique inversée. Par exemple, la chaîne « 2+2 » est décrite par /2\+2/
, et « /slash » par /\/slash/
.
L'oblique inversée permet également de représenter des caractères d'échappement. Par exemple, une tabulation est représentée par \t
, une nouvelle ligne par \n
et un retour-chariot par \r
.
Si un caractère est répété, on peut le faire suivre par :
- un point d'interrogation
?
: il est présent au plus une fois (0 ou 1 fois) ;/a?/
décrit «» ou bien « a » ; - un astérisque
*
: il est absent, ou présent un nombre indéterminé de fois ;/a*/
décrit «», « a », « aa », « aaa », … ; - un plus
+
: il est présent au moins une fois :/a+/
décrit « a », « aa », « aaa », … ; - une paire d'accolades :
/a{5}/
décrit « aaaaa »,/a{2,4}/
décrit « aa », « aaa » ou « aaaa »,/a{3,}
décrit « aaa », « aaaa », « aaaaa », …
Si une portion de chaîne doit se trouver en début de ligne, l'expression régulière commence par ^
; si elle doit se trouver en fin de ligne, elle se termine par $
. Par exemple, /^bon/
et /jour$/
sont tous les deux vrais pour « bonjour ».
On peut placer plusieurs caractères substituables entre crochets ; on parle de classe de caractères. Par exemple, /[bt]éton/
décrit les chaînes de caractère « béton » et « téton ». L'expression /[0-9a-fA-F]/
correspond à un chiffre hexadécimal (chiffre décimal de 0 à 9, et lettre de a à f, en minuscule et en majuscule). On peut aussi interdire une classe en la commençant par l'accent circonflexe : /[^0-9]/
est tout sauf un chiffre décimal.
Enfin, il existe des classes toutes faites :
/\d/
est un chiffre, l'équivalent de/[0-9]/
;/\D/
est tout sauf un chiffre, l'équivalent de/[^0-9]/
;/\s/
est un caractère d'espacement : blanc, tabulation, nouvelle ligne ou retour-chariot ;\S
est la négation de\s
;\w
est un caractère alphanumérique (lettre ou chiffre, ainsi que le tiret de soulignement_
) ;\W
est la négation de\w
;- le point
.
correspond à n'importe quel caractère, sauf une nouvelle ligne\n
.
Ainsi, /\s+
décrit un ou plusieurs espaces.
On peut avoir des chaînes alternatives : elles sont séparées par un tube |
, et en cas d'ambiguïté, elles sont entre parenthèses : /(bonjour|au\ revoir)\ Marcel/
décrit les chaînes « bonjour Marcel » et « au revoir Marcel ».
Fonctions extérieures
modifierSyntaxe pour définir une fonction
modifierComme indiqué dans le chapitre Calcul numérique, la définition d'une fonction commence par le mot clé function
et se termine par le mot clé endfunction
, selon la syntaxe suivante :
function [<arguments de sortie>] = <nom de la fonction>(<arguments d'entrée>)
<instructions>
endfunction
Les instructions pouvant comporter des branchements conditionnels (tests), des boucles, … ce qui recoupe la notion de sous-programme.
Par exemple, la fonction calculant la puissance n-ième peut s'écrire :
function [y] = puissance(x,n)
// calcule la puissance n-ieme
y = 1 // valeur de y^0, et valeur initiale si n>0
if n > 0 then
for i = 1:n
y = y*x // on multiplie n fois x par lui-même
end
end
endfunction
Cette fonction peut ensuite être utilisée en mode interactif ou à l'intérieur d'un programme :
-->puissance(2,3)
ans =
8.
Le résultat d'un calcul à l'intérieur d'une fonction n'est jamais affiché, l'utilisation de virgules ou de points-virgules est donc indifférente.
Fonction définie dans un fichier extérieur
Lorsque la fonction est définie dans un fichier texte, celui-ci peut être chargé au moyen de la commande exec()
:
exec("fichier.sci")
Un fichier peut contenir plusieurs fonctions. Le nom de fichier peut contenir un chemin d'accès si celui-ci n'est pas dans le répertoire (dossier) courant.
La commande getd()
permet de charger tous les fichiers .sci
d'un répertoire, avec la syntaxe :
getd("chemin/")
où "chemin/"
est le chemin d'accès au répertoire.
Les commandes permettant de manipuler les répertoires sont décrites au chapitre Gestion des fichiers > Manipulation des répertoires.
Fonction définie en ligne de commande
On peut définir une fonction en mode interactif, en ligne de commande. La syntaxe est identique, il suffit d'entrer les différentes lignes dans l'interpréteur. Toutes les instructions avant le mot clef endfunction
sont considérées comme appartenant à la fonction et ne sont pas interprétées immédiatement.
Passage d'une fonction en paramètre
d'un point de vue de la représentation des données, une fonction est une variable. On peut donc la passer en paramètre. Par exemple :
function [y] = ce_que_je_veux(action, parametre)
y = action(parametre)
endfunction
peut ensuite s'utiliser ainsi :
-->ce_que_je_veux(sqrt, 2)
ans =
1.4142136
-->ce_que_je_veux(max, [2 -1 3 0])
ans =
3.
Et l'on peut ainsi renommer des fonctions, par exemple :
-->racine_carree = sqrt
racine_carree =
-->racine_carree(2)
ans =
1.4142136
On voit là l'importance des parenthèses vide lorsque l'on utilise une fonction qui n'a pas de paramètre. Par exemple
a = gce
crée une copie de la fonction gce()
, tandis que
a = gce()
met dans la variable a le résultat de la fonction gce()
(voir Graphiques et sons > Organisation des entités graphiques).
Définition compacte d'une fonction
modifierUne fonction peut aussi se définir en une ligne à l'aide de l'instruction deff
:
deff ("[y] = f(x)", "y=2*x")
Cette définition est équivalente à :
function [y] = f(x)
y = 2*x
endfunction
Cette syntaxe permet également de définir des fonctions comportant plusieurs instructions :
deff ("[y] = puissance(x, n)", "y = 1, if n > 0 then, for i = 1:n, y = y*x, end, end")
Cette écriture, bien que compacte, rend difficile la compréhension de la fonction, et ne devrait être réservé qu'aux fonctions simples.
De même qu'avec le mot clef function
, le résultat des calculs n'est pas affiché à l'intérieur d'une fonction.
Gérer le nombre de paramètres transmis
modifierLorsqu'une fonction est créée pour un seul programme, on peut facilement gérer le nombre de paramètres : le programmeur sait ce qu'il doit transmettre lors de l'appel de la fonction. Mais une fonction peut être amenée à être utilisée dans d'autres programmes, par d'autre programmeurs, par exemple en étant intégrée à une bibliothèque (voir Bibliothèque de fonctions et Recommandations. Il faut alors envisager
- que certains paramètres soient optionnels ;
- que le programmeur oublie de transmettre certains paramètres (voir Gestion des erreurs).
Si un paramètre p
peut avoir une valeur par défaut, on peut tester son existence avec exists()
:
function [y] = monproduit(x, p)
// paramètre optionnel
if exists("p", "l") == 0...
then p = 1; end // valeur par défaut
y = x*p;
endfunction
On peut définir une fonction avec un nombre variable d'arguments. Le dernier argument doit alors être varagin
(variable arguments input) ; dans la fonction, varagin
est une liste, varagin(i)
donne donc le i-ème argument optionnel. Le nombre total d'arguments est donné par la commande argn(2)
(le « 2 » est un paramètre imposé). Par exemple
function [y] = produit_multiple(x, varargin)
// paramètre optionnel
n = argn(2);
y = x;
for i = 1:n - 1
y = y*varargin(i);
end
endfunction
que l'on peut tester avec
produit_multiple(5) produit_multiple(5, 4) produit_multiple(5, 4, 2)
Si l'on veut utiliser la totalité des paramètres, il faut utiliser varagin(:)
. Par exemple, si l'on ne désire tracer que les dix premiers points d'un jeu de données mais en permettant de passer des paramètres pour modifier le tracé :
function [y] = trace_10_premiers(x, y, varargin)
plot(x(1:10), y(1:10), varargin(:))
endfunction
que l'on peut tester par exemple avec
x = rand(1, 100) y = rand(1, 100) scf(0); plot(x, y) scf(1); trace_10_premiers(x, y, "r", "thickness", 2)
De la même manière, on peut utiliser varargout
(variable arguments output) dans les paramètres de sortie, et « remplir » la liste varargout
dans la fonction. Le nombre total de paramètres de sortie est donné par argn(1)
. Si varargout
est le seul paramètre de sortie, alors les éléments de la liste sont « égrénés », par exemple
function [varargout] = test_varargout()
varargout = list(1, 2, 3, 4);
endfunction
donne :
--> a = test_varargout()
a =
1.
--> [a, b] = test_varargout()
b =
2.
a =
1.
S'il y a d'autres arguments de sortie que varargout
, alors la liste est transmise comme une variable unique. Par exemple :
function [n, varargout] = test_varargout1()
varargout = list(1, 2, 3, 4);
n = argn(1);
endfunction
donne :
--> a = test_varargout1()
a =
1.
--> [N, L] = test_varargout1()
L =
L(1)
1.
L(2)
2.
L(3)
3.
L(4)
4.
N =
2.
Notons que dans ce cas-là, on a un nombre de paramètres de sortie fixe, pas variable, puisque varargout
est un paramètre unique. On pourrait utiliser n'importe quel nom de liste.
Informations sur une fonction
modifierLa commande macrovar(f)
donne la liste des paramètres d'entrée et sortie de la fonction f
, sous la forme d'un vecteur :
- première composante : paramètres d'entrée ;
- deuxième composante : paramètres de sortie ;
- troisième composante : variables utilisées mais qui ne sont pas définies à l'intérieur de la fonction ;
- quatrième composante : fonctions appelées à l'intérieur de cette fonction ;
- cinquième composante : variables locales.
La commande fun2string(f)
renvoie un vecteur de chaînes de caractères contenant la définition de la fonction.
La commande head_comments f
renvoie les commentaires situés au début de la fonction f
.
On peut aussi vouloir caractériser un script complet. Une manière de faire consiste à mettre le script dans une fonction et à utiliser la commande macrovar()
. On peut aussi comparer, avec setdiff()
, la liste des variables, obtenue avec who()
, avant et après le chargement du script. Par exemple
variables_avant = who("local");
exec("monscript.sce");
variables_du_script = setdiff(variables_avant, who("local"));
Variables locales ou globales
modifierPar défaut, toutes les variables sont locales, c'est-à-dire qu'elle sont définie dans un environnement d'exécution (workspace). Ainsi, une variable dans une fonction peut avoir le même nom qu'une variable dans le programme principal ou dans une autre fonction, ce seront des variables distinctes ; modifier la valeur d'une variable dans une fonction ne modifie pas la valeur de cette variable dans le programme principal.
On peut modifier ce comportement avec la commande global()
; c'est une manière inélégante — voire risquée, car on ne maîtrise pas totalement la valeur de la variable — de gérer une valeur sans avoir à la passer en paramètre à une fonction. Cela peut être utile lorsqu'une valeur est utilisée et modifiée par de nombreuses fonctions — cela allège la syntaxe des fonctions —, mais cela rend le code moins portable (voir la section Du code… Général), et il faut être sûr de quelle fonction modifie la valeur et quand, voir par exemple sur Wikipédia Parallélisme (informatique) > Dépendance des données.
Lorsque l'on veut partager une variable entre plusieurs environnements, il faut déclarer la variable comme globale dans chaque environnement. Par exemple, le script suivant :
clear()
// fonctions
function modifie()
global("a") // déclare la variable comme globale dans l'environnement de la fonction
a = 5
endfunction
// programme principal
global("a") // déclare la variable comme globale dans l'environnement de base (programme principal)
a = 1;
disp("Valeur initiale : "+string(a)) // affiche la valeur initiale
modifie();
disp("Valeur finale : "+string(a)) // affiche la nouvelle valeur
donne
Valeur initiale : 1 Valeur finale : 5
On peut partager une variable entre fonctions sans qu'elle soit partagée avec l'environnement du programme principal, par exemple le script
clear()
// fonctions
function initialisation()
global("a")
a = 1
disp("a = "+string(a))
endfunction
function utilisation()
global("a")
disp("a = "+string(a))
endfunction
// programme principal
errcatch(4, "continue") // n'interrompt pas le programme lorsqu'une variable
// n'est pas définie
initialisation()
disp("a = "+string(a))
errclear(4) // apurement des erreurs de type 4
utilisation()
donne
a = 1 disp("a = "+string(a)) !--error 4 Variable non définie : a a = 1
ce qui montre que la variable existe dans les environnements d'exécution des fonctions, mais pas dans l'environnement d'exécution du programme principal.
On peut tester si une variable a est globale avec la commande
isglobal(a)
Gestion des erreurs
modifierL'exécution d'une fonction, et en particulier un calcul, peut générer une erreur, ce qui va interrompre le déroulement du programme. On peut éviter ceci de plusieurs manières :
- « Blinder » le programme : faire des vérifications de la validité des données à certains instants critiques.
- Prévoir les exceptions : si l'on doit diviser par un nombre, soit on s'assure que ce nombre ne peut pas être nul, soit on teste la nullité et le programme agit en conséquence.
- Intercepter les erreurs : on peut laisser se produire les erreurs, et éviter l'interruption du programme, avec les risques que cela comporte : résultat incohérent, voire pire, résultat faux mais paraissant cohérent.
- Renvoyer des valeurs particulières : la norme IEEE 754 définit la valeur « NaN » (not a number, « ceci n'est pas un nombre ») et « infini », représenté dans Scilab par
%nan
et%inf
.
Blinder un programme, cela consiste essentiellement à vérifier que le contenu des variables est conforme à leur utilisation. C'est en particulier important lorsqu'il y a une entrée de données : lecture d'un fichier ou bien saisie au clavier. Par exemple, lorsque l'on demande à l'utilisateur d'entrer un nombre entier, on vérifie qu'il s'agit bien d'un entier, et si ce n'est pas le cas, on le signale et on redemande une nouvelle saisie.
On peut donc vérifier
- Si la variable existe.
- Si la variable est au bon format.
- Si le contenu est dans une plage correcte (est dans l'ensemble de définition des fonctions qui s'en servent).
Vérifier l'existence de la variable
modifierLa commande
exists(nom_de_variable)
où nom_de_variable
est une chaîne de caractères (par exemple exists("X")
), renvoie 1
si la variable existe, 0
sinon. Si l'on précise le paramètre "l"
, la commande vérifie alors si la variable existe en local. Par exemple :
exists("X", "l")
placé au sein d'une fonction permet de vérifier si la variable X
existe au sein de cette fonction ; s'il s'agit d'une variable passée en paramètre, cela permet de vérifier que l'appel de la fonction contient bien cette variable (faute de quoi la fonction risquerait d'utiliser la valeur d'une variable globale de même nom).
On peut aussi utiliser la fonction
isdef("X")
isdef("X", "l")
où X
est la variable ; cette fonction renvoie un booléen.
Dans une fonction, on peut récupérer le nombre d'arguments d'entrée avec la commande
nb_arg_entree = argn(2)
puis à tester la valeur de nb_arg_entree
. On pourra par exemple voir la définition de la fonction sind
en tapant edit sind
dans la ligne de commande.
Une autre manière de faire consiste à initialiser la variable avec un contenu vide — a = []
— ou bien particulier — a = %nan
—, puis à tester la valeur de la variable avant de l'utiliser (voir plus loin).
Vérifier le format de la variable
modifierScilab propose des fonctions de test is...
(« est-ce… ? »), c'est-à-dire des fonctions renvoyant des valeurs booléennes %t
(vrai) ou %f
(faux) :
isglobal(X)
: vrai si la variable X est une variable globale ;isvector(X)
: vrai si la variable X est un vecteur (faux si c'est un scalaire ou une matrice de plus d'une ligne et une colonne) ;issparse(X)
: vrai si X est une matrice creuse ;isnum(X)
: vrai si la chaîne de caractères X représente un nombre ;isalphanum(X)
: renvoit un vecteur de booléens : la i-ème composante est vraie si le i-ème caractère de la chaîne X est une lettre ou un chiffre ;isascii(X)
: idem, mais vérifie s'il s'agit d'un caractère ASCII 7 bits ;isdigit(X)
: idem, mais vérifie s'il s'agit d'un chiffre ;isletter(X)
: idem, mais vérifie s'il s'agit d'une lettre ;isreal(X)
: vrai si la matrice X ne contient que des nombres réels ;isdir(X)
: vrai si la chaîne de caractères X est un chemin d'accès à un répertoire existant ;isfile(X)
: vrai si la chaîne de caractères X est un nom de fichier existant ;is_handle_valid(h)
: vrai si h est un pointeur vers un objet graphique existant (par exemple obtenu par unget
, ou bien généré par unscf
) ;iscell(X)
,iscellstr(X)
,isstruct(X)
: vrai si la variable X est respectivement une cellule, une cellule de chaîne de caractères ou une structure (voir Structures de données avancées) ;isfield(S, nomduchamp)
vérifie si le champ nomduchamp (chaîne de caractères) existe dans la structure S,isparam(nomliste, nomparam)
vérifie si le paramètre nomparam (chaîne de caractères) existe dans la liste nomliste.
Vérifier le contenu de la variable
modifierRappelons les opérations de comparaison de valeurs : ==
, <
, <=
, >
, >=
. La commande isequal(X1, X2, …, Xn)
est vraie si les n valeurs sont toutes égales.
Scilabe propose également les commandes :
isempty(X)
: vrai si la matrice ou la liste X est vide ;isinf(X)
,isnan(X)
: vérifie si la matrice X a des composantes infinies ou NaN.
Générer un message d'erreur
modifierLorsque l'on détecte un problème, on peut générer soi-même une erreur, avec la commande error
(déjà présentée), ou bien un message d'avertissement, avec warning('message')
.
On peut personnaliser le message en indiquant la valeur de la variable illégale. Pour cela, il faut soit composer une chaîne de caractères contenant la variable, par exemple
texte_erreur = "fonction f : valeur de l''argument "+string(X)+" illégale.";
error(texte_erreur)
ou bien utiliser les commandes de type « langage C », mais il faut alors être sûr du format de la variable :
texte_erreur = "fonction f : valeur de l'argument %d illégale.";
error(msprintf(texte, X))
Notons qu'un certain nombre de termes standard peuvent être traduits automatiquement selon la langue définie sur le système (paramètres de régionalisation, localisation) ; la commande getext()
permet de traduire automatiquement les termes, et d'adapter la typographie (par exemple, mettre une espace[5] devant les deux-points et points-virgules en français).
Dérouter une erreur
modifierPour « dérouter » une erreur, c'est-à-dire éviter que le programme ne s'interrompe, on utilise la fonctions errcatch(-1)
. La fonction iserror(-1)
permet ensuite de tester si une erreur est survenue. On peut ne s'intéresser qu'à un type d'erreur : errcatch(n)
et iserror(n)
(n > 0) pour l'erreur n (voir la table des erreurs).
On peut indiquer à errcatch
une action faire en cas d'erreur :
errcatch(n, "action")
où action vaut :
pause
: s'arrête et reste dans le contexte courant ; on peut utiliserwhereami()
pour connaître le contexte ; c'est le comportement le plus utile en phas ede développement/déverminage ;continue
: l'erreur est ignorée et le programme continue ; il faut effacer l'erreur avecerrclear
le plus tôt possible (aprèsiserror
) ;kill
: arrête l'exécution du programme et revient au niveau 0 (comportement normal) ;stop
: quitte Scilab.
Enfin, on peut indiquer ce que doit faire Scilab en cas d'erreur de calcul, avec la commande ieee
:
ieee(0)
: génère une erreur (comportement par défaut) ;ieee(1)
: renvoit une valeur Inf ou Nan, et génère un avertissement ;ieee(2)
: renvoit une valeur Inf ou Nan sans générer d'avertissement.
Encapsuler le code problématique
modifierCertaines partie de code sont plus susceptibles que d'autre de produire des erreurs. C'est par exemple le cas d'une entrée-sortie hasardeuse — saisie au clavier, lecture d'un fichier volumineux —, ou bien de l'utilisation d'une opération instable — valeur pouvant provoquer un dépassement ou un soupassement… Pour gérer cette situation, on peut utiliser une structure try-catch-end
:
- Scilab exécute le code entre les commandes
try
etcatch
; si aucune erreur ne se produit, il va tout de suite après leend
; - si une erreur se produit entre le
try
et lecatch
, il exécute directement les instructions entre lecatch
.
try
<instructions "normales">
catch
<instructions exécutées en cas d'erreur>
end
[message_erreur, numero_erreur] = lasterror(%t) // enregistrement de l'erreur
<suite du script>
Si le code problématique est court, on peut également l'encapsuler dans une comande execstr()>
avec l'option "errcatch"
.
numero_erreur = execstr("<instructions normales>", "errcatch", "message d.erreur")
S'il n'y a pas d'erreur, le numéro d'erreur est 0.
Bibliothèque de fonctions
modifierIl est possible de créer une bibliothèque de fonctions (library). Cela permet à plusieurs programmes de partager les mêmes fonctions. L'avantage est double :
- on n'écrit qu'une seule fois les fonctions, ce qui réduit la taille du code source, et donc facilite sa lecture et sa maintenance ;
- lorsque l'on corrige une erreur ou que l'on améliore la fonction, cela profite à tous les programmes.
L'inconvénient est qu'une modification a un impact sur tous les programmes, ce qui peut obliger à une gestion des différentes versions d'une fonction. Cet inconvénient est aussi un avantage : une erreur dans une fonction va avoir un impact sur tous les programmes l'utilisant, donc va être détectée plus rapidement.
La bibliothèque peut aussi contenir des variables.
On peut simplement définir un fichier .sci
et l'appeler avec exec()
ou getd()
comme indiqué précédemment. Mais Scilab fournit également une gestion spécifique des bibliothèques.
Le fait de recourir à une bibliothèque plutôt qu'à un fichier .sci
permet entre autres de gérer des fonctions ayant le même nom, par exemple des fonctions réalisant la même chose mais avec un algorithme différent — on peut raisonnablement se demander l'intérêt de la chose, et souligner les problèmes que cela pose (est-on sûr de savoir exactement quelle fonction l'on utilise ?), mais c'est une possibilité. Par exemple, si l'on a une fonction f()
définie dans deux bibliothèques bib1
et bib2
, on peut alors charger les deux bibliothèques et utiliser bib1.f()
ou bib2.f()
; si l'on utilise f()
tout court, c'est alors a priori la dernière bibliothèque chargée qui est utilisée.
Structure d'une bibliothèque
modifierPour Scilab, une bibliothèque est :
- un répertoire (ou dossier, directory), contenant :
- un fichier
names
(sans extension de nom de fichier), qui est un fichier texte (ASCII) contenant le nom des différentes fonctions ; c'est en quelques sortes l'index de la bibliothèque ; ce fichier peut être créé par un éditeur de texte, ou bien par Scilab avec la fonctionmputl
; - un fichier pour chaque fonction ; le fichier porte le nom de la fonction auquel on ajoute l'extension
.bin
; c'est un fichier binaire (compilé) créé par la fonctionsave
de Scilab.
Création d'une bibliothèque
modifierLe programme suivant :
- se place dans le répertoire
persolib
(déjà existant), les trois petits points désignant le début du chemin d'accès ; - définit les fonctions
gauss
etlorentz
, ainsi que la variableunsurpi
; - crée les fichiers
gauss.bin
,lorentz.bin
etunsurpi.bin
dans ce répertoire, à partir des fonctions définies ; - crée le fichier d'index
names
.
// Création de la bibliothèque persolib
clear();
// **********
// Paramètres de la bibliothèque
// **********
cheminbiblio = ".../persolib/";
// **********
// Fonctions
// **********
function [y] = gauss(mu, sigma, x)
// fonction gaussienne
// mu : espérance
// sigma : écart type
y = 1/(sigma * sqrt(2*%pi))*exp(-1/(2*sigma^2)*(x - mu).^2)
endfunction
function [y] = lorentz(x0, Gamma, x)
// fonction lorentzienne
// x0 : position du sommet
// mu : gamma : largeur à mi-hauteur (FWHM)
HWHM = Gamma/2; // facilite l'expression
y = ((HWHM)/%pi)*((x - x0).^2 + HWHM^2).^(-1)
endfunction
// **********
// Constantes
// **********
unsurpi = 1/%pi;
// **********
// Création de la bibliothèque
// **********
chdir(cheminbiblio)
// compilation et enregistrement des fonctions
save("gauss.bin", gauss)
save("lorentz.bin", lorentz)
save("unsurpi.bin", unsurpi)
// creation de l'index
mputl(["gauss" ; "lorentz" ; "unsurpi"], "names")
Utilisation d'une bibliothèque
modifierLe programme suivant :
- s'assure que toutes les variables sont effacées (
clear()
) ; - déclare l'utilisation de la bibliothèque
.../persolib/
, et l'affecte à la variablemabibliotheque
(fonctionlib
) ; - utilise les fonctions et variables de la bibliothèque.
// Utilisation de la bibliothèque persolib
clear();
// **********
// Paramètres de la bibliothèque
// **********
cheminbiblio = ".../persolib/";
// **********
// Déclaration et chargement de la bibliothèque
// **********
mabibliotheque = lib(cheminbiblio);
// **********
// Utilisation des fonctions
// **********
X = -3:0.1:3;
Yg = gauss(1,1,X);
Yl = lorentz(1,1,X);
plot(X,Yg)
plot(X,Yl)
disp(unsurpi)
Commandes sur les bibliothèques
modifierSi l'on tape simplement le nom de la variable dans laquelle on a déclarée la bibliothèque (ici mabibliotheque
), Scilab retourne :
- le chemin d'accès ;
- la liste des fonctions.
La fonction libraryinfo
a le même effet, mais permet de mettre les valeurs retournées dans des variables, par exemple avec
[fcts, chem] = libraryinfo(mabibliotheque)
la variable fcts
est une matrice de chaînes de caractères contenant les noms des fonctions, et la variable chem
est une chaîne de caractères contenant le chemin d'accès.
La fonction isdef(mabibliotheque)
teste si la bibliothèque est déclarée.
La fonction clear mabibliotheque
efface les fonctions et variables définies par la bibliothèque.
La fonction whereis
indique dans quelle bibliothèque se trouve une fonction (mais pas une variable). Par exemple, whereis(gauss)
retourne mabibliotheque
.
La fonction librarieslist
indique la liste des bibliothèques chargées.
Lancement d'un programme
modifierUn programme (fichier .sce
) peut être lancé depuis Scinotes, en cliquant sur le menu Execute puis en choisissant Load into Scilab. On peut aussi utiliser la combinaison de touches CTRL+l
.
On peut aussi l'exécuter à partir de l'interpréteur de commandes, avec exec('nomprogramme.sce', -1)
. Le paramètre « -1 » permet de ne pas afficher les lignes exécutées.
Optimisation d'un programme
modifierSi un programme est destiné à traiter des données volumineuses, il devient important de s'intéresser à la durée d'exécution. Plusieurs outils permettent de comparer les durées d'exécution et ainsi de faire des choix entre plusieurs solutions possibles pour effectuer une tâche (réaliser une fonction, au sens large de fonctionnalité).
Tout d'abord, Scilab peut lire l'horloge du système. La commande clock
retourne un vecteur ligne de 6 éléments indiquant :
- L'année.
- Le mois (1–12).
- Le jour du mois (1—31).
- L'heure (1–24).
- Les minutes dans l'heure (0–59).
- Les secondes dans la minute (0–59)[6].
Mais cela ne permet d'avoir une précision que de la seconde. On peut avoir les millisecondes avec :
instant = getdate();
instant(9) + instant(10)/1000
en effet, la commande getdate()
renvoie un vecteur dont la neuvième composante est la seconde (nombre entier entre 0 et 59) et la dixième composante est la milliseconde (nombre entier entre 0 et 999).
Scilab dispose également d'un chronomètre :
- la commande
tic()
déclenche le chronomètre ; - la commande
toc()
indique la valeur du chronomètre.
L'exécution d'une fonction externe peut être caractérisé par son « profil ». Le profil est une matrice ayant le même nombre de lignes que la fonction (la 1re ligne est celle contenant la commande function
). Pour chaque ligne, le profil contient 3 colonnes :
- Nombre de fois que la ligne exécutée.
- Durée d'exécution de la ligne, en seconde.
- « Effort » requis par l'interpréteur pour exécuter la ligne.
La commande add_profiling("nom_fonction")
« prépare » la fonction : cette commande est nécessaire pour extraire ensuite le profil. La commande profile(nom_fonction)
indique le « profile de la fonction ».
La commande showprofile(nom_fonction)
met en correspondance ces données et les instructions de la fonctions. La commande plotprofile(nom_fonction)
synthétise le profil sous la forme d'un histogramme.
Conseils
Un programme rapide est un programme qui effectue peu d'opérations. Le premier point est donc d'utiliser un algorithme « économe ». Par exemple, pour le calcul de la valeur d'un polynôme, on utilisera la méthode de Horner ; dans le même ordre d'idées, pour calculer les carrés des éléments d'une matrice réelle, il vaut mieux utiliser M.*M
que M.^2
. L'algorithme le plus économe peut dépendre de la structure des données, c'est le cas notamment des algorithmes de tri — voir par exemple le tri par tas et le tri rapide.
Certaines opérations sont plus gourmandes que d'autres ; pour de gros volumes de données, il peut être intéressant de ne pas utiliser l'écriture « naïve » de ce que l'on veut calculer. Par exemple, la division utilise plus d'opérations que la multiplication, ainsi, si l'on remplace[7]
par
on remplace deux divisions par une division et cinq multiplications. Ceci est favorable pour de gros volumes de données, comme le montre l'exemple suivant
// Génération des données
n = 9e5;
a = rand(n, 1);
b = rand(n, 1) + 1; // +1 garantit que l'on n'a pas de zéro
c = rand(n, 1);
d = rand(n, 1) + 1;
// Calcul naïf
tic();
e = a./b;
f = c./d;
t1 = toc();
// Calcul optimisé
tic();
dn = 1 ./(b.*d);
E = a.*d.*dn;
F = c.*b.*dn;
t2 = toc();
// Comparaison
disp(t1, t2);
Par contre, les erreurs d'arrondis ne sont pas les mêmes, les résultats peuvent donc être légèrement différents, et il faut vérifier que cela ne cause pas d'erreur de dépassement.
Lorsque Scilab effectue un calcul, il commence par vérifier le type de données et les dimensions de la matrice sur laquelle il travaille — rappelons qu'un nombre est une matrice 1×1 —, afin de savoir comment il va mener le calcul (arbre de décision). Par exemple, pour effectuer une addition A+B
, Scilab effectue une dizaine d'opérations préalables[8] :
- Allocation de mémoire pour
A
. - Allocation de mémoire pour
B
. - Détermination du type de données de
A
: matrice réelle ou complexe (type 1), matrice polynomiale (2), matrice booléenne (3), matrice creuse (4), matrice creuse booléenne (5), matrice de chaînes de caractères (10), … - Détermination du type de données de
B
. - Détermination de la dimension de la matrice
A
. - Détermination de la dimension de la matrice
B
. - Si
A
est une matrice réelle ou complexe, s'agit-il de nombre réels ou de nombres complexes ? - Idem pour
B
. - La mémoire libre est-elle suffisante pour contenir le résultat ?
Si l'on a des calculs répétitifs à faire, il vaut donc mieux mettre les données dans une matrice et effectuer le calcul sur la matrice : ainsi, les opérations de contrôle et de décision ne seront faites qu'une seule fois.
De manière générale, un calcul est plus long qu'une opération de lecture/écriture en mémoire. Ainsi, si un calcul est répété, on a intérêt à stocker son résultat dans une variable, en particulier s'il s'agit d'un calcul long (par exemple sur une matrice). Si par exemple on utilise plusieurs fois sin(π/3), on a intérêt à créer la variable
sinPiSurTrois = sin(%pi/3)
et à utiliser cette variable, plutôt que de calculer à chaque fois la valeur.
Dans un tableau (matrice), si l'on s'attend à ce que des valeurs volumineuses (par exemple des nombre à virgule flottante à double précision) soient déplacées plusieurs fois, on peut créer une matrice de correspondance entre indices (donc ne contenant que des entiers), et faire les transferts uniquement à la fin ; ainsi, les opérations de lecture/écriture fréquentes se font sur des données peu volumineuses.
On voit que l'optimisation consiste fréquemment à trouver un équilibre entre volume de données et durée des opérations. Si l'on génère trop de données intermédiaires (dépassant la capacité de la mémoire vive), cela oblige le système d'exploitation à avoir recours à de la mémoire virtuelle (swap) et donc à faire des opérations de lecture et écriture sur disque dur (ou mémoire flash) qui ralentissent l'exécution.
Déverminage
modifierPour déverminer (« débugger ») un programme, on peut :
- mettre des fonctions
halt
dans le code, afin d'interrompre l'exécution à certain endroit pour effectuer des vérifications ; on relance le programme en appuyant sur la touche[entrée]
dans l'invite de commande ; - lancer le programme en mode 4 :
exec('nomprogramme.sce', 4)
; le programme s'arrête à chaque ligne ; on relance le programme en appuyant sur la touche[entrée]
dans l'invite de commande.
Recommandations
modifierLes « bonnes pratiques de programmation » ont pour but de faciliter la conception et la maintenance des programmes. Cela inclue la possibilité de reprendre des programmes plusieurs années après, de les adapter aux nouvelles versions des logiciels, de réutiliser des bouts de code dans d'autres programme, que d'autres personnes réutilisent le code, l'adapte, … bref créer du code directement réutilisable sans modification (boilerplate code). Rappelons que 80 % du coût d'un bout de programme réside dans la maintenance et qu'au bout d'un certain temps, cette maintenance n'est en général plus faite par l'auteur original du code[9].
Scilab est un langage de calcul ; ces calculs s'appliquent à divers domaines : mécanique, optique, thermique, électronique, traitement du signal, biologie, géophysique, économie, … En général, les utilisateurs sont des spécialistes du domaine d'application (physiciens, biologistes, économistes, …) mais ne sont pas des informaticiens. Cette section a donc pour but de leur apporter quelques outils simples facilitant la programmation.
- Pour plus de détails voir : Conseils de codage en C.
Exemple
Pour illustrer les concepts, nous allons prendre un exemple : vous êtes chargé par une entreprise de mécanique, faisant de l'usinage, d'évaluer les défauts lors de la réalisation d'une forme circulaire. Le client de l'entreprise demande à ce que l'arête d'une pièce forme un cercle, et vous êtes chargé de valider le fait que les imperfections sont dans les tolérances admises. Vous avez pour cela un nuage de points (x, y, z) relevé sur la pièce réalisée.
Démarche projet
modifierUn programme est un projet. On doit donc lui appliquer les outils classiques d'un projet. Cela ne signifie pas nécessairement qu'il faille produire une documentation — notons que les commentaires d'un programme (voir ci après) sont une documentation, et que certains outils permettent de les extraire —, mais simplement qu'il faut se poser des questions et y répondre avant de commencer.
Définir les objectifs
modifierUn programme a un but, un objectif : il doit répondre aux attentes d'un utilisateur, qui est souvent le programmeur lui-même. Cette identité utilisateur = programmeur évite les incompréhensions liée à la transmission d'informations, mais peut mener à un manque de rigueur et donc à des difficultés.
La première étape consiste donc à recenser (par écrit ou dans sa tête) les besoins et l'objectif à atteindre. Pour un projet de calcul, c'est essentiellement :
- quelles sont les données d'entrée ;
- quels sont les options possibles ;
- quel est le résultat attendu.
Cette étape doit inclure des données de test permettant de valider le programme : on a des données pour lesquelles on connaît le résultat, ce qui permettra de vérifier que le programme fonctionne bien.
Dans des cas limites — par exemple gros volumes de données, ou bien si le programme doit communiquer avec d'autres dispositifs (comme des capteurs) —, il est nécessaire de prendre en compte l'environnement du programme :
- ordinateur : par exemple parallélisation sur plusieurs processeurs, gestion des limitations de la mémoire ;
- système d'exploitation : par exemple lecture et écriture de fichiers, appel à des fonctions système.
Exemple
Pour notre exemple, nous avons donc :
- données d'entrée :
- nuage de points (x, y, z),
- tolérances, à définir avec le client ;
- options : aucune a priori ;
- sortie : validation oui/non.
Cette petite étude formelle met une chose en évidence : la demande est incomplète, il faut connaître la nature des tolérances.
Après dialogue avec le chargé d'affaire de l'entreprise et/ou le client, la forme circulaire sert à positionner une pièce mâle de forme cylindrique. Ce qui importe pour le client est que :
- l'axe du cylindre soit « bon », c'est-à-dire que l'angle que fait l'axe du cylindre avec la surface soit « à peu près » 90 °, avec une tolérance d'orientation εo = 1 ' ;
- le cylindre soit « bien positionné », c'est-à-dire que la distance entre le centre réel et le centre théorique soit inférieur à une valeur εp = 0,1 mm (tolérance de position) ;
- l'appuis du cylindre sur le cercle « soit bon », c'est-à-dire que la forme ne soit « pas trop éloignée » d'un cercle parfait (quel qu'il soit) afin d'assurer un bon contact ; l'écart entre chaque point et le cercle théorique doit être inférieur à une valeur εf donnée (tolérance de forme) à la fois axialement (écart perpendiculairement au plan) et radialement (écart dans le plan).
Le cahier des charges est validé par les parties (programmeur, chargé d'affaire, client).
Pour les tests, nous prévoyons de créer des données factices correspondant à un cercle parfait auquel on ajoute des défauts maîtrisés. Nous prévoyons un exemple devant passer le test en ayant des défauts justes inférieurs à la limite (mettons à 1 %), trois exemples ayant des défauts justes supérieurs à la limite (1 % également), chacun pour un type de défaut (orientation du plan, position du centre, écart à la circularité) et un exemple ayant les trois défauts.
Chercher des solutions
modifierLa deuxième étape consiste à rechercher les solutions. Cela peut être une recherche bibliographique : trouver un algorithme adapté, ou un programme déjà existant et faisant globalement la même chose. Cela peut aussi être le développement de solutions originales. À l'issue de cette étape, il faut choisir la solution appliquée.
Une méthode consiste à partir du besoin exprimé sous la forme de fonctions, une fonction étant un verbe à la forme active avec un sujet et un complément. Cette fonction peut être découpée en sous-fonctions, et l'on continue ce découpage jusqu'à arriver à des fonctions élémentaires faciles à coder (que l'on sait réaliser d'une ou plusieurs manières). Voir Analyse fonctionnelle.
Le choix de cette solution est critique car il conditionne tout le reste. Il ne peut être pertinent que si les objectifs sont clairs. Notons que l'on peut dans un premier temps retenir une solution peu performante mais facile à programmer, ce qui permet d'avoir rapidement un résultat et avec moins de risques d'erreur ; puis, par la suite, développer une autre solution plus performante, la première solution servant de référence. La programmation modulaire (fractionnement en fonctions) facilite cette démarche : il suffit d'écrire une nouvelle fonction (sous-programme) ayant le même nom que l'ancienne, et de renommer l'ancienne fonction pour se laisser la possibilité de revenir en arrière.
Exemple
Les fonctions que l'on peut dégager sont :
- f1 : le programme lit les données ;
- f1a : le programme lit les données dans un fichier,
- f1a.1 : l'utilisateur indique quel fichier lire,
- f1a.2 : le programme ouvre le fichier et importe les données,
- f1a.3 : si nécessaire, le programme transforme les données en nombres ;
- f1b : l'utilisateur saisit manuellement les données,
- f1c : le programme lit les données directement de l'appareil de mesure ;
- f1a : le programme lit les données dans un fichier,
- f2 : le programme prend une décision :
- f2.1 : le programme représente le cercle sous une forme exploitable numériquement,
- f2.2 : le programme traite les données,
- f2.2.1 : le programme détermine les écarts par rapport à la géométrie idéale,
- f2.2.2 : le programme détermine si les écarts sont compatibles avec les tolérances ;
- f3 : le programme édite un rapport ;
- f4 : le programme est ergonomique : il est accessible aux connaissance de l'utilisateur,
On s'aperçoit que la fonction f1 peut être réalisée de trois manières très différentes (f1a, f1b et f1c) et que la fonction f4 dépend de qui va être chargé de mettre en œuvre le programme, il faut donc éclaircir ces points avec les personnes responsables des mesures.
Concernant la fonction f2.1 « le programme représente le cercle sous une forme exploitable numériquement » : dans l'espace, une courbe simple peut être définie par une équation ƒ(x, y, z) = 0. Il faut donc chercher dans la bibliographie (ou dans ses souvenirs) quel type de fonction décrit un cercle. Deux solutions sont possibles :
- chercher une fonction dans l'espace ;
- décomposer le problème en deux parties, la première consistant à cherche le « plan le plus proche » du nuage de point, et ensuite travailler dans ce plan avec une fonction plane ƒ(x', y').
Concernant la première solution, il apparaît que le cercle est l'intersection d'une sphère et d'un plan, qui dans l'idéal passe par le centre de la sphère, on peut donc définir assez facilement le cercle par un système d'équations : si l'on appelle (x0, y0, z0) les cordonnées du centre et R le rayon, alors
Pour la deuxième solution, il faut déterminer un plan
puis un repère dans ce plan, et donc l'équation du cercle
- .
Concernant la fonction f2.2.1 « le programme détermine les écarts par rapport à la géométrie idéale », il apparaît que le problème présente deux « échelles » :
- une échelle globale : la forme usinée est « globalement » un cercle, qui impose une position et une direction à l'axe du cylindre ;
- une échelle locale : la forme usinée présente des écarts « locaux » avec un cercle idéal.
On voit donc qu'il faut déterminer dans un premier temps le cercle « parfait » le plus proche possible des points mesurés, et :
- vérifier que les caractéristiques de ce cercle global soient compatibles avec les tolérances d'orientation et de position ;
- vérifier que l'écart entre les points et le cercle global soient compatibles avec la tolérance de forme.
Il faut donc passer par une étape de régression pour déterminer le cercle global. Dans le cas d'une description par l'intersection d'une sphère et d'un plan, la régression consiste à déterminer huit facteurs (x0, y0, z0, R, a, b, c, d), mais on a une équation liant trois facteurs, ce qui ramène à sept l nombre de facteurs indépendants. Dans le cas de la seconde description, on n'a que sept facteurs à déterminer (x'0, y'0, R, a, b, c, d), mais on rajoute une étape de projection des points sur le plan.
Nous nous orientons vers la seconde description,en deux étapes, qui permet de séparer la résolution en deux étapes élémentaires.
La recherche bibliographique sur la régression circulaire nous indique qu'il y a deux types d'algorithmes :
- la régression par la méthode des moindres carrés, qui impose une régression non-linéaire et qui est sensible aux points aberrants ;
- des méthodes de régression linéaire, mais qui n'appliquent pas la méthode des moindres carrés.
Il y a peu de risque d'avoir des points aberrants(sauf erreur de mesure), mais les méthodes linéaires sont plus rapides et plus stables : les méthodes non-linéaires sont itératives, donc gourmandes en ressources et peuvent éventuellement diverger. Comme nous ne cherchons pas à déterminer un loi statistique, le fait que l'on ne minimise pas l'écart quadratique (donc l'écart type empirique) n'a pas d'importance. Nous retenons donc une méthode linéaire, celle de Kåsa et Coope.
Déterminer la structure du programme
modifierLa troisième étape consiste à déterminer la structure du programme, son architecture. L'organisation des modules (fonctions) est obtenue à partir de l'analyse fonctionnelle. Il faut également choisir la structure des données (vecteur, matrice, liste, …) et déterminer des test permettant de savoir si chaque module fonctionne.
Les modules doivent être hiérarchisés, ainsi :
- on sait quoi développer en premier, ce qui permet de fournir un programme partiel mais fonctionnel (délais courts, version de test, mode dégradé) ;
- on sait sur quels modules on doit concentrer ses moyens (temps passé en programmation et tests).
Typiquement, l'interface avec l'utilisateur peut être importante, en particulier si le programme doit être utilisé par quelqu'un « n'y connaissant rien », mais doit être développée à la fin, lorsque le programme fonctionne.
Rappelons-le, ces trois étapes ne sont pas nécessairement formalisées. Elles ne se font pas non plus nécessairement d'un bloc avant de commencer à programmer : les besoins exprimés peuvent changer en cours de projet, on peut découvrir de nouvelles solutions, … C'est donc un processus dynamique.
Exemple
Ici, nous avons une structure assez simple, linéaire :
- chargement des donnée (f1, f4),
- structure des données :
- entrée : pour le fichier lu, à voir avec l'utilisateur et en particulier le logiciel générant les données ; dans un premier temps, nous utiliserons un format
.csv
(format texte facile à manipuler, de nombreux logiciels permettent l'export vers ce format), mais attention au séparateur utilisé ; - sortie : en interne, les données sont stockées dans un vecteur de réels codés en double précision,
- entrée : pour le fichier lu, à voir avec l'utilisateur et en particulier le logiciel générant les données ; dans un premier temps, nous utiliserons un format
- test : créer un fichier afficher les données
- structure des données :
- détermination du plan global par régression linéaire (f2.2.1),
- structure des données :
- entrée : une matrice de réels n × 3, la ligne i correspondant au point i et la colonne j à sa coordonnée (1 → x, 2 → y, 3 → z),
- sortie : le plan est représenté par son équation cartésienne ax + by + cz + d = 0, donc par un vecteur A = (a, b, c, d),
- test : créer des points suivant un plan connu et en ajoutant des variations (du « bruit ») de faible amplitude, et vérifier que la procédure retrouve des valeurs proches des valeurs initiales pour A ;
- structure des données :
- détermination d'un repère orthonormé direct lié au plan (f2.2.1),
- structure des données :
- entrée : un vecteur colonne de réels de dimension 3, composantes d'un vecteur normal au plan, N,
- sortie : le R.O.N.D. est constitué de trois vecteurs colonne réels de dimension 3, X1, X2 et X3, le vecteur X3 étant normal au plan,
- test : à partir des données précédentes, vérifier que le repère est bien orthonormé direct et que X3 est bien normal au plan ;
- structure des données :
- changement de repère des points expérimentaux (f2.2.1) ;
- détermination du cercle global par régression linéaire (méthode de Kåsa et Coope) (f2.1, f2.2.1) ;
- évaluation de l'écart angulaire de l'axe du cercle (f2.2.1) ;
- évaluation de l'écart de la position du centre du cercle (f2.2.1) ;
- évaluation de l'écart des points par rapport au cercle global (f2.2.1) ;
- décision par rapport aux tolérances (f2.2.2) ;
- création du rapport (f3, f4).
Les modules 2 à 8 sont incontournables. Par contre, les modules 1 et 9, correspondant aux interaction avec l'utilisateur, peuvent être réduit au minimum dans une première phase.
Écrire le programme, le tester et le maintenir
modifierLa quatrième étape consiste à faire du codage unitaire : on code chaque fonction, chaque module. On vérifie que chaque fonction remplit bien son rôle, avec des données de test unitaire.
La cinquième étape consiste à intégrer les fonctions, à les mettre dans un même programme. On vérifie que l'on transmet bien les bonnes données aux bonnes fonctions.
La sixième et dernière étape consiste à vérifier que le programme fini fonctionne bien, grâce au données de test établies à la première étape, et qu'il remplit bien les besoins de l'utilisateur (qu'il suit le cahier des charges).
Là encore, ces trois étapes se font souvent de manière dynamique voire simultanée : au sein du programme, on écrit une nouvelle fonction, on la teste (test unitaire et test d'intégration), puis on passe à la fonction suivante. Mais on n'oubliera pas la hiérarchisation : les premières fonctions développées ne sont pas les premières à être utilisées chronologiquement dans le programme, mais les fonctions les plus importantes (le moteur avant l'interface).
Dernière étape ? En fait non, il reste l'étape la plus longue et la plus difficile : la maintenance, la correction des erreurs qui se révèlent lors des utilisations (déverminage). Cela commence par le nettoyage du code : on retire les morceaux de code obsolètes (« bouts d'essai »).
Bilan
modifierLa démarche globale est donc :
- Définition des besoins de l'utilisateur (cahier des charges) : données d'entrée, option, résultat attendu. Choix de données de test.
- Analyse fonctionnelle : découpage en fonctions. Recherche de solutions (sélection d'algorithmes).
- Organisation des modules du programme. Hiérarchisation. Choix de la structure de données.
- Codage et test unitaire des fonctions, en suivant l'ordre hiérarchique.
- Intégration des fonctions au sein du programme et tests d'intégration (échange de données entre les fonctions)
- Test du programme fini avec les données de test. Vérification que le programme répond bien aux besoins (cahier des charges).
- Nettoyage du code. Maintenance.
Pour des projets importants et devant être formalisés, on pourra par exemple s'intéresser au modèle en V.
Structure générale du script
modifierOn recommande de mettre la plupart du code dans des fonctions ; le « programme principal », c'est-à-dire ce qui n'est pas dans les fonctions, devrait essentiellement se réduire à la définition des données de base et à l'appel des différentes fonctions. Un script a typiquement la forme suivante
Bloc de commentaires :
nom du script
versions du script, dates, auteurs
version de Scilab utilisée, modules Atoms utilisés
Objectif du script : fonctions qu'il rempli, son but, son rôle
entrées et sorties : saisies clavier, affichages, fichiers, formats
Commandes préliminaires
préparation de l'environnement
p. ex. clc(), imports (input()) …
Constantes
définition de variables constantes
Fonctions
définition des fonctions
Programme principal
appel des fonctions
Par ailleurs, les fonctions ne devraient pas être trop longues, typiquement une page d'écran maximum (environ 80 lignes) ; au-delà, il convient de scinder la fonction en plusieurs sous-fonctions. Elles ne devraient pas utiliser plus de 5 ou 10 variables locales[10] ; au-delà, il faut repenser la fonction, par exemple la scinder.
Le code doit être
- explicite ;
- aéré ;
- en alinéa ;
- abondamment commenté ;
- fractionné ;
- général.
Voir Analyse statique de programmes.
Du code explicite …
modifierIl faut utiliser les noms de fonctions et de variable voulant dire quelque chose. Les noms ne doivent contenir que des caractères alphanumériques (en particulier pas de tiret de soulignement « underscore »), et si possible même que des lettres non accentuées (les chiffres sont déconseillés) ; ils doivent commencer par une lettre en minuscule[11].
On peut utiliser des noms « bidons » (comme toto
, foo
) pour des variables utilisées sur de petites portions de programme. On utilise aussi fréquemment des noms « standard » : x
(abscisse, inconnue d'une équation, variable d'une fonction), i
(nombre entier évolutif : indice, compteur), n
(nombre entier constant : quantité), …
Si l'on travaille en équipe, il est recommandé d'utiliser une nomenclature systématique (voir Convention de nommage). D'un point de vue de la forme, on peut par exemple recommander de former les noms de variable à partir de mots ; chaque changement de mot est indiqué par une capitale, les autres lettres étant en minuscules (maVariableAvecUnNomExplicite
). Concernant la composition du nom, on peut si nécessaire spécifier le type de variable (« matrice de réels », « vecteur ligne de chaînes de caractères », …) en début ou en fin de nom (mais toujours au même endroit), selon une convention commune (voir Notation hongroise).
… Aéré …
modifierIl faut se limiter à une seule commande par ligne, voire plusieurs lignes pour une commande si la syntaxe est longue (utilisation de trois points ...
), la coupure se faisant après une virgule ou avant un opérateur. La longueur d'une ligne ne devrait pas dépasser 80 caractères (l'éditeur SciNotes matérialise d'ailleurs cette limite). Mais l'on peut à l'inverse envisager de mettre deux commandes courtes et s'articulant sur une même ligne.
Par ailleurs, on laisse un ligne vide au sein d'une fonction pour séparer les grands blocs, et on laisse deux lignes vides entre les fonctions.
… En alinéa (« indenté ») …
modifierDes blancs en début de ligne marquent l'imbrication des commandes, en particulier à l'intérieur des délimiteurs de fonctions (function … endfunction
), de boucles (for … end
, while … end
) et des exécutions conditionelles (if … then … else … end
). Par défaut, l'éditeur de Scilab (SciNotes) crée des alinéas de quatre espaces.
… Abondamment commenté …
modifierChaque fonction doit comprendre une introduction (commentaires) expliquant ce qu'elle fait, la nature des variables qui lui sont passées et la nature des variable en sortie — ce qu'elles signifient et le type qu'elles doivent avoir (précondition à l'exécution de la fonction, voir Programmation par contrat). Les grandes étapes de la fonctions doivent être indiquées par des commentaires.
De manière générale, les commentaires doivent donner un aperçu du code, les informations nécessaire à l'utilisation du code ainsi que celle relatives à la lecture et à la compréhension du code, et les raisons du choix d'une solution si son choix n'est pas trivial. Par contre, on évite de commenter ce qui est évident dans le code, ainsi que ce qui risque de devenir obsolète lorsque le code évolue.
Une ligne de commentaire commence par //
(deux barres de fraction). Depuis la version 6.0.0, il est possible d'avoir des blocs de commentaire, c'est-à-dire un commentaire écrit sur plusieurs lignes sans avoir à commencer chaque ligne par la double barre de fraction : un bloc de commentaire commence par /*
(barre de fraction-astérisque) et se termine par */
(astérisque-barre de fraction).
// Voici une ligne de commentaire
/* Voici un bloc de commentaire
c'est-à-dire un commentaire
écrit sur plusieurs lignes */
… Fractionné …
modifierComme signalé plus haut, le code doit être découpé en fonction de moins de 80 lignes. Le programmeur doit tant que faire se peut utiliser des variables locales. À l'exception de quelques variables très générales, essentiellement des constantes, toutes les variables utilisées par une fonction doivent être définies dans la fonction, ou bien transmises comme paramètre.
Outre la facilité de maintenance, le fait de mettre du code dans des fonctions accélère l'exécution d'un script. En effet, les fonctions sont compilée en code intermédiaire (bytecode) ; cette opération — automatique — de compilation consiste d'une part à vérifier que la syntaxe est correcte, et d'autre part réorganise les commandes en notation polonaise inverse, mieux adaptée à l'exécution (qui passe par une pile)[8].
Si certaines fonctions sont très générales, elle méritent de figurer dans un fichier .sci
à part, appelé par la commande exec()
, voire dans une bibliothèque (voir ci-dessus).
… Et général
modifierDe manière plus spécifique, pour Scilab, on peut recommander de faire des fonctions les plus générales possibles. Par exemple, une fonction numérique devrait être capable de traiter un nombre, mais aussi une matrice ; on peut ainsi définir un ensemble de définition de type X = 0:0.01:1;
et appliquer directement f(X)
. Si l'on a une fonction de deux variables X et Y, on peut générer deux matrices « grille » et ainsi avoir une carte de résultats f(X, Y), avec la fonction ndgrid()
(grille à n dimensions) :
X = vx1:pasx:vx2;
Y = vy1:pasy:vy2;
[X_matrice, Y_matrice] = ndgrid(X, Y);
f(X_matrice, Y_matrice)
On a ainsi
X_matrice = [X(1) X(1) … X(1) X(2) X(2) … X(2) … … … … X(m) X(m) … X(m)]
Y_matrice = [Y(1) Y(2) … Y(n) Y(1) Y(2) … Y(n) … … … Y(n) Y(1) Y(2) … Y(n)]
f(X_matrice, Y_matrice) = [f(X(1),Y(1)) f(X(1),Y(2)) … f(X(1),Y(n)) f(X(2),Y(1)) f(X(2),Y(2)) … f(X(2),Y(n)) … … … … f(X(m),Y(1)) f(X(m),Y(2)) … f(X(m),Y(n))]
À titre d'exercice, on peut chercher comment soi-même cette fonction.
Considérons que l'on a deux vecteurs ligne X = [X(1), X(2), …] et Y = [Y(1), Y(2), …], obtenus avec la commande linspace()
ou la syntaxe v1:p:v2
. On peut utiliser le programme suivant :
taille_X = size(X, "c");
taille_Y = size(Y, "c");
X_matrice = X'*ones(taille_Y, 1);
Y_matrice = ones(1, taille_X)*Y;
f(X_matrice, Y_matrice)
S'il n'est pas possible de créer une fonction gérant les matrices (ou si l'on n'y arrive pas), on peut toujours utiliser « l'évaluation de groupe » feval(X, f)
à la place de f(X)
.
La manière la plus simple de créer une fonction pouvant agir sur une matrice consiste à faire défiler les indices, par exemple :
function [y]=carre(x)
// calcule le carré des composantes d'une matrice
// entrées : x : matrice de nombres
// sorties : y : matrice de nombres
[imax, jmax] = size(x);
for i = 1:imax
for j = 1:jmax
y(i,j) = x(i,j)^2;
end
end
endfunction
ainsi, si l'on transmet un vecteur, imax
ou jmax
vaudra 1, et si l'on transmet un nombre unique (scalaire), les deux vaudront 1. Mais l'utilisation de boucle ralentit considérablement l'exécution du programme. On peut souvent utiliser le fait que Scilab est fait pour travailler avec des matrices, par exemple utiliser les versions « pointées » des opérateurs :
function [y]=carre(x)
// calcule le carré des composantes d'une matrice
// entrées : x : matrice de nombres
// sorties : y : matrice de nombres
y = x.^2;
endfunction
Rappelons de manière utile que l'inverse peut s'obtenir par .^(-1)
ou par 1 ./
(bien respecter l'espace entre le 1 et le point), la racine carrée par .^(0.5)
ou sqrt()
; les racines n-ièmes par .^(1/n)
ou nthroot(x, n)
. D'autre part, la plupart des fonctions primitives s'appliquent aux matrices (par exemple exp()
, sqrt()
, …). Par ailleurs, nous avons vu précédemment qu'un calcul effectué sur une matrice est plus rapide qu'un calcul effectué successivement sur chacun de ses éléments (en raison des opérations préliminaires au calcul). Pour une question de rapidité, il vaut donc mieux favoriser les calculs globaux sur des matrices que des calculs en faisant défiler les indices.
On peut utiliser de manière « intelligente » les raccourcis d'indice (:
, $
), ainsi que la possibilité d'extraire certains indice en utilisant une matrice booléenne. Dans l'exemple suivant[12], nous définissons une fonction ayant une formule différente à gauche et à droite d'une valeur x0 ; il s'agit d'un pic dissymétrique, formé de deux demies gaussienne de même moyenne et même hauteur, mais de largeurs différentes.
function [y] = gauss_dissym(A, x)
// génère un pic gaussien dissymétrique
// entrées : A(1) : position du pic
// A(2) : hauteur de la courbe
// A(3) : largeur de la courbe à gauche
// A(4) : largeur de la courbe à droite
// x : vecteur de nombres
// sorties : y : vecteur de nombres
indice = (x < A(1)); // vecteur de %t à gauche, %f à droite
y = zeros(x); // initialisation
y(indice) = A(2)*exp(-(x(indice) - A(1)).^2/A(3)); // profil gauche
y(~indice) = A(2)*exp(-(x(~indice) - A(1)).^2/A(4)); // profil droit
endfunction
On peut ainsi l'utiliser avec un nombre unique
A = [3, 2, 1.5, 2.5];
y = gauss_dissym(A, 2.5)
ou bien avec un vecteur
A = [3, 2, 1.5, 2.5];
X = 0:0.1:6;
plot(X, gauss_dissym(A, X))
De manière plus générale, considérons une fonction de deux variables, définie de manière différente à gauche et à droite pour x ; donc concrètement deux fonctions fg(x, y) et fd(x, y). Nous voulons l'évaluer sur deux vecteurs à partir desquels on crée deux matrices grille (voir plus haut). On peut utiliser la solution suivante :
function [z] = f(A, x, y)
indice = (x < A); // vecteur de %t à gauche, %f à droite
z = zeros(x, y); // initialisation
z = fg(x, y)*bool2s(indice)
z = z + fd(x, y)*bool2s(~indice)
Enfin, on peut transmettre une fonction en paramètre à une autre fonction. Dans l'exemple ci-dessous, nous appliquons la fonction derivation
à deux fonctions différentes (il existe une fonction derivative()
qui remplit parfaitement ce rôle ; le code suivant n'est donné qu'à titre d'illustration).
//============================================================================
// nom : carre_inverse_derivee.sce
// auteur : Christophe Dang Ngoc Chan
// date de création : 2012-10-20
// dates de modification :
// 2013-07-08 : ajout de xtitle dans les tracés
// guillemets ' -> "
// 2018-05-28 : ajout de scf(0), suppr. de clear
//----------------------------------------------------------------------------
// version de Scilab : 5.3.1
// module Atoms requis : aucun
//----------------------------------------------------------------------------
// Objectif : Tracer les fonction carré et inverse, ainsi que leurs dérivées
// Entrées : aucune (paramètres en dur)
// Sorties : fenêtre graphique avec 4 tracés
//============================================================================
// **********
// Initialisation
// **********
scf(0);
clf;
// **********
// Constantes
// **********
deltax = 1e-6; // précision pour la dérivation
// **********
// Fonctions
// **********
function [y]=carre(x)
// calcule le carré des composantes d'une matrice
// entrées : x : matrice de nombres
// sorties : y : matrice de nombres
y = x.^2;
endfunction
function [y]=inverse(x)
// calcule l'inverse des composantes d'une matrice
// entrées : x : matrice de nombres
// sorties : y : matrice de nombres
y = x.^(-1);
endfunction
function [yprime] = derivation (f, x, epsilon)
// calcule la dérivée aprochée de la fonction f
// entrées : f : fonction
// x : ensemble de définition (matrice de nombres)
// epsilon : nombre positif, normalement petit,
// mais pas trop pour éviter les erreurs de soupassement
// sorties : yprime : matrice de nombres
yprime = 1/(2*epsilon)*(f(x+epsilon) - f(x-epsilon));
endfunction
// **********
// Programme principal
// **********
// Génération des données
X = [1:0.1:10];
Y1 = carre(X);
Yprime1 = derivation(carre, X, deltax);
Y2 = inverse(X);
Yprime2 = derivation(inverse, X, deltax);
// Tracé
subplot(2,2,1)
plot(X, Y1, "b")
xtitle("$f(x) = x^2$", "x", "y")
subplot(2,2,3)
plot(X, Yprime1, "b")
xtitle("$f''(x)$", "x", "y")
subplot(2,2,2)
plot(X, Y2, "r")
xtitle("$y = g(x) = 1/x$", "x", "y")
subplot(2,2,4)
plot(X, Yprime2, "r")
xtitle("$y = g''(x)$", "x", "y")
Relecture de code
modifierUne manière simple de s'assurer de la rigueur dans l'application des règles consiste à relire le code, voire à le faire relire par un tiers (voir Revue de code). La relecture de code consiste à s'assurer que les règles énoncées — découpage en fonctions de moins de 80 lignes et ayant moins de 10 variables locales, non recours à des variables globales, respect identations, commentation du code, … — sont bien vérifiées et c'est tout (mais c'est déjà beaucoup) !
En particulier, il serait illusoire, et contre-productif car décourageant, d'espérer comprendre rapidement un code qu'un auteur a mis plusieurs jours voire semaines à écrire. Par contre, on peut relever les endroits où l'auteur a été peu rigoureux — typiquement, des endroits où il y a eu beaucoup d'essais-erreurs, des modifications vites faites (correction de petites erreurs, petits ajouts).
Distribuer un exécutable
modifierUn auteur peut vouloir distribuer un exécutable, par exemple pour avoir une utilisation transparente de Scilab, ou encore pour masquer un script afin que l'on ne puisse pas le lire, pour garder confidentiel un algorithme. On peut pour cela utiliser la fonction SendScilabJob
au sein d'un programme C/C++. Cela fait appel à l'API call_scilab
.
Au sein d'un programme C/C++, le code Scilab est alors mis dans une chaîne de caractères, puis envoyé au moteur Scilab par la commande SendScilabJob
. Le programme C/C++ peut ensuite être compilé.
On peut également utiliser SWIG[13] (Simplified Wrapper and Interface Generator) pour interfacer C/C++ avec Scilab.
Le module Atoms scetoexe
fournit une commande scetoexe_generate()
permettant d'encapsuler un script Scilab dans un exécutable Microsoft Windows. Cependant, l'exécution fait appel à Scilab, il ne s'agit pas d'un programme indépendant.
Le module Atoms Scilab 2 C
permet de convertir le script Scialb en C. Ce code peut ensuite être compilé.
La société Equalis distribue l'extension (payante) WildCruncher pour son logiciel (payant) Pro Plus, qui permet de générer du code C à partir du script Scilab puis de le compiler pour obtenir un exécutable [3].
Exemples
modifierVoici quelques exemples de programmes complets mettant en œuvre des algorithmes simples.
Crible d'Ératosthène
modifierLe programme suivant détermine la liste des nombres premiers inférieurs à une valeur donnée, appelée ici maximum
, en utilisant l'algorithme du crible d'Ératosthène.
//============================================================================
// nom : crible_eratosthene.sce
// auteur : Christophe Dang Ngoc Chan
// date de création : 2012-10-16
// dates de modification :
// 2013-07-08 : floor -> uint32 (pour diminuer la taille nécessaire)
// guillemets ' -> "
// 2014-05-14 : uint8 (pour diminuer la taille nécessaire)
// 2018-01-02 : indices() booléen
// imax évalué à la volée et non pas par la racine carrée
// (plus respectueux de l'algorithme initial)
// élimination d'une boucle
// 2018-02-04 : correction : 1 n'est pas premier
//----------------------------------------------------------------------------
// version de Scilab : 6.0.0
// module Atoms requis : aucun
//----------------------------------------------------------------------------
// Objectif : détermine la liste des nombres premiers inférieurs à un nombre
// entier donné par le crible d'Ératosthène
// Entrées : nombre entier (scalaire)
// Sorties : vecteur de nombres entiers
//============================================================================
// ***** fonctions et procédures *****
function [liste] = eratosthene(limite)
// renvoit la liste (vecteur) des nombres premiers inférieurs à la limite
indices = (ones(limite, 1)==1);
// indices(i) = %t si i est premier, %f sinon
indices(1)=%f; // 1 n'est pas premier
imax = uint32(limite);
i = uint32(2);
while i <= imax
if indices(i) then
jmax = uint32(limite/i);
j=uint32(2:jmax);
indices(i*j)=%f; // élimination des multiples de i
test = (i*jmax == imax);
while test // recul de imax si le dernier nombre n'est pas premier
imax = imax - 1;
test = ~indices(imax);
end
end
i = i + 1;
end
// constitution de la liste
liste0 = uint32(1:limite);
liste = liste0(indices);
endfunction
// ***** programme principal *****
// accueil
disp("Crible d''Ératosthène")
// entrer le plus grand nombre que l'on veut tester
maximum=input("nombre entier maximum : ");
// exécuter l'algorithme
lst = eratosthene(maximum);
// afficher le résultat
disp("Liste des nombres premiers entre 1 et "+string(maximum)+" : ");
disp(lst);
Algorithme d'Euclide
modifierLe programme suivant permet de déterminer le PGCD et le PPCM de deux nombres entiers, en utilisant l'algorithme d'Euclide.
//============================================================================
// nom : pgcd_ppcm_euclide.sce
// auteur : Christophe Dang Ngoc Chan
// date de création : 2012-10-16
// dates de modification :
// 2013-07-08 : guillemets ' -> "
//----------------------------------------------------------------------------
// version de Scilab : 5.3.1
// module Atoms requis : aucun
//----------------------------------------------------------------------------
// Objectif : détermine le PGCD et le PPCM par l'algorithme d'Euclide
// Entrées : deux nombre entier (scalaires)
// Sorties : deux nombres entiers (scalaires)
//============================================================================
// ***** Fonctions et procédures *****
function [resultat] = euclide(a, b)
// détermine le PGCD de a et de b (deux entiers)
r = modulo(a,b); // initialisation du reste
while r <> 0
a = b;
b = r;
r = modulo(a,b); // calcul du reste
end
resultat = b;
endfunction
// ***** Programme principal *****
// accueil
disp("Calcul du PGCD et du PPCM de deux nombres par l''algorithme d''Euclide")
// valeurs
n1 = input("Premier nombre : ");
n2 = input("Second nombre : ");
// exécution de l'algorithme
pgcd = euclide(n1, n2);
ppcm = n1*n2/pgcd;
// affichage du résultat
print(%io(2),pgcd)
print(%io(2),ppcm)
Courbe de Koch
modifierLe programme suivant trace la courbe du flocon de Koch de manière récursive.
//============================================================================
// nom : von_koch.sce
// auteur : Christophe Dang Ngoc Chan
// date de création : 2012-10-23
// dates de modification :
// 2013-07-08 : guillemets ' -> "
// 2013-07-2 : vectorisation du calcul
// 2016-06-21 : ajout de commentaires
// 2018-05028 : ajour de scf, suppr. de clear
//----------------------------------------------------------------------------
// version de Scilab : 5.3.1
// module Atoms requis : aucun
//----------------------------------------------------------------------------
// Objectif : trace la courbe du "flocon de neige" de von Koch
// Entrées : aucun (paramètres codés en dur)
// Sorties : fenêtre graphique avec une courbe ; fichier SVG
//============================================================================
scf(0);
clf;
// **************
// * constantes *
// **************
n = 6; // nombre d'étapes ;
// limité à 9 (262 145 points), sinon il faut changer la taille de la pile (stacksize)
// 6 etapes (4 097 points) sont suffisantes pour un bon rendu
sin_soixante = sqrt(3)/2; // sin(60°)
l = 1; // longueur du segment initial (unité arbitraire)
// ******************
// * initialisation *
// ******************
// *************
// * fonctions *
// *************
function []=vonkoch(A, B, i)
u = (B - A)/3 ; // tiers du vecteur AB
v = [0.5*u(1) - sin_soixante*u(2) ; sin_soixante*u(1) + 0.5*u(2)] ;
// vecteur tourné de +60°
C = A + u ;
D = C + v ;
E = B - u ;
if i == 1 then
// tracé des segments si l'on est au dernier niveau
x = [A(1) ; C(1) ; D(1) ; E(1) ; B(1)] ;
y = [A(2) ; C(2) ; D(2) ; E(2) ; B(2)] ;
xpoly(x, y, "lines")
else
// sinon, appel récursif
j = i - 1 ;
vonkoch(A, C, j);
vonkoch(C, D, j);
vonkoch(D, E, j);
vonkoch(E, B, j);
end
endfunction
// ***********************
// * programme principal *
// ***********************
debut = [0;0] ;
fin = [l;0];
vonkoch(debut, fin, n) // tracé du flocon
isoview(0, l, 0, l*sin_soixante/3) // dimensions de l'image
// enregistrement du fichier
nom = "von_koch_"+string(n)+"_etapes.svg" ;
xs2svg(0, nom)
Lancer un programme sans ouvrir directement Scilab
modifierIl est possible de lancer un programme Scilab sans ouvrir l'interface graphique Scilab. Cela se comporte comme s'il s'agissait d'un exécutable.
Pour cela, il faut exécuter la commande suivante dans la ligne de commande du système d'exploitation (shell) :
scilab -nw -f monprogramme.sce
Si le système d'exploitation ne trouve pas lui-même Scilab, il faut lui indiquer l'emplacement. Par exemple sous Unix
/bin/scilab -nw -f monprogramme.sce
ou bien sous Microsoft Windows :
"C:\Program Files\scilab-5.3.3\bin\Scilex.exe" -nw -f monprogramme.sce
Si l'on veut rediriger la sortie de Scilab vers la sortie standard (fenêtre système), on peut « tuber » Scilab, par exemple sous Unix
/bin/scilab -nwni < monprogramme.sce
echo "disp('Hello world')" |scilab -nwni
On peut mettre ces commandes de système d'exploitation dans un script.
On peut enfin appeler le moteur Scilab à partir d'un programme écrit dans un autre langage, voir les rubriques d'aide :
call_scilab
: C/C++ ;javasci
: Java.
Voir aussi
modifier- Dans Wikipédia
- Sur le Web
- (en) « Programming in Scilab », sur Scilab.org (consulté le 13 février 2019)
- [ConvScilab 2010] (en) « Code Conventions for the Scilab Programming Language », sur Scilab Wiki (consulté le 21 juin 2016)
- (en) « Interfacing C or Fortran programs with Scilab », sur Introduction To Scilab User's Guide (Scilab Group, INRIA Metalau Project/ENPC Cermics) (consulté le 21 juin 2016)
Notes
modifier- ↑ ou bien
fig = gcf() ; fig.figure_id
- ↑ À partir de la version 5.5.
- ↑ Samuel Gougeon, « Re: [Scilab-users] Converting string months to month numbers », Scilab users mailing list , 21 octobre 2016
- ↑ voir anglais Perl regular expressions quick start
- ↑ En typographie, le terme « espace » est au féminin lorsqu'il désigne un caractère « blanc ».
- ↑ les secondes sont un nombre entier, mais en raison de la représentation utilisée, il est possible que Scilab affiche un nombre décimal, avec des 9 ou des 0 jusqu'à la cinquième décimale.
- ↑ voir le chapitre « Optimisation » (transparents 35 et suivants) de Patrick Bousquet-Mélou, « Utilisation du cluster Antares » [PDF], sur CRIHAN, (consulté le 23 septembre 2016)
- ↑ 8,0 et 8,1 voir le anglais message de Serge Steer (INRIA), 20 mars 2013 à 14h38, sur la discussion Regarding simple numerical operations result display de la liste de diffusion Scilab users.
- ↑ Code Conventions for the Java Programming Language, Oracle
- ↑ le cerveau humain est capable d'appréhender 5 à 9 objets en même temps, selon l'état de fatigue, de concentration, … voir anglais George Armitage Miller, « The magical number seven, plus or minus two: Some limits on our capacity for processing information », dans Psychological Review, vol. 63, no 2, 1956, p. 81–97 [texte intégral]
- ↑ ConvScilab 2010
- ↑ le code a été amélioré grâce à Stefan Du Rietz
- ↑ http://swig.org/