GNU Octave — Fonctions et scripts

Gazette Linux n°112 — Mars 2005

Joëlle Cornavin

Adaptation française  

François Poulain

Relecture de la version française  

Article paru dans le n°112 de la Gazette Linux de mars 2005.

Cet article est publié selon les termes de la Open Publication License. La Linux Gazette n'est ni produite, ni sponsorisée, ni avalisée par notre hébergeur principal, SSC, Inc.


Table des matières

1. Introduction
2. Les fonctions dans Octave
3. Test de l'efficacité de la fonction
4. Scripts Octave
5. Scripts Octave exécutables
6. En-têtes conventionnels et notes de copyright
7. Le mot de la fin

1. Introduction

Dans ce second article sur GNU Octave, je me fonderai sur les bases que j'ai abordées dans le précédent article, GNU Octave — Une introduction en présentant les fonctions et les scripts à l'aide d'un certain nombre d'exemples. L'obtention et l'installation de GNU Octave, ainsi que les sources de documentation officielle ont été étudiées dans le premier article. Référez-vous à celui-ci pour plus d'informations.

2. Les fonctions dans Octave

Comme n'importe quel autre langage de programmation, Octave offre une prise en charge complète pour créer des fonctions. Les fonctions sont un outil essentiel qui permet de fractionner de gros problèmes en un certain nombre de petites tâches. Une fonction doit effectuer une tâche spécifique et doit l'exécuter correctement. Ces critères sont très importants. Plus la tâche qu'une fonction exécute est spécifique, plus elle sera réutilisable ; bien que vous puissiez l'écrire pour vous aider à résoudre votre problème actuel, si elle est bien définie, vous pourrez vous en servir dans de nombreux problèmes futurs. Par « exécuter correctement », j'entends que la fonction devra donner la réponse correcte à une entrée valide tout en signalant une erreur en cas d'entrée invalide ; ce devrait être un véritable « boîte noire ». Une fois écrite et testée, elle devra être de qualité suffisante pour que vous puissiez lui faire confiance sans avoir à la revérifier dans tous vos problèmes futurs.

Une fonction mathématique extrêmement simple qui fait défaut à Octave et dont j'ai eu besoin récemment est la fonction factorielle. Elle est définie ainsi :

 
n! = n(n-1)...2·1

Donc, par exemple, 5! = 5·4·3·2·1 = 120. Il existe un certain nombre d'algorithmes pour implémenter cette fonction dont je décrirai certains, pour donner une bonne introduction aux fonctions dans Octave. Commençons par la solution d'une itération :

function answer = lg_factorial1( n )

    answer = 1;

    for i = 2:n
        answer = answer * i;
    endfor

endfunction

Listing 1 : lg_factorial1.m

Par conséquent, une définition de fonction dans Octave est :


function name
    body
endfunction

Les fonctions devront être enregistrées à part dans un fichier texte ayant un nom identique à celui de la fonction elle-même, avec l'extension .m. Le fichier ci-dessus serait donc enregistré sous lg_factorial1.m. Si vous essayez maintenant d'appeler la fonction lg_factorial1(), Octave cherche dans la liste des répertoires spécifiés par la variable LOADPATH les fichiers se terminant par .m qui ont le même nom de base que le nom de la fonction. Si vous voulez créer un référentiel de fonctions sur votre ordinateur et faire inclure à LOADPATH ce répertoire automatiquement, vous pouvez ajouter la ligne suivante :


LOADPATH = "/chemin/vers/vos/fichiers/:"

au fichier de configuration d'Octave, ~/.octaverc. Octave vérifie également le chemin d'accès spécifié dans la variable intégrée DEFAULT_LOADPATH qui inclut le répertoire de travail actuel par défaut.

Une fonction peut admettre un certain nombre d'arguments sous la forme d'une liste entre parenthèses séparée par une virgule après le nom de la fonction. Des valeurs de renvoi multiples peuvent également être définies :


function [retval1, retval2, etc] = name( arg1, arg2, etc )
    body
endfunction

Il existe deux règles supplémentaires dans la définition mathématique de la fonction factorielle :


0! = 1

n >= 0

Incorporons ces règles dans notre définition de fonction :


function answer = lg_factorial2( n )

    if( n < 0 )
        error( "il n'y a pas de définition pour les factorielles négatives" );
    endif

    answer = 1;

    if( n == 0 )
        return;
    else
        for i = 2:n
            answer = answer * i;
        endfor
    endif

endfunction

Listing 2 : lg_factorial2.m

La fonction teste d'abord pour s'assurer que l'entrée est valide (non négative). Si elle ne l'est pas, elle renvoie une erreur à l'aide de la fonction intégrée error(). Elle ne se contente pas d'afficher le message d'erreur, elle affiche également une trace (traceback) de toutes les fonctions conduisant à l'erreur. C'est très utile pour les programmeurs qui résolvent des problèmes complexes avec beaucoup de fonctions car ils peuvent résoudre le problème incriminé très rapidement.

Si l'entrée est valide, nous testons le cas zéro et nous employons la commande de retour pour terminer la fonction si elle est vraie. Contrairement à de nombreux autres langages de proogrammation, l'instruction de retour d'Octave n'admet aucun argument, donc il est essentiel que la(les) valeur(s) de retour soi(en)t définie(s) avant de rencontrer un appel de retour comme je l'ai fait avec l'instruction answer = 1 ci-dessus.

Maintenant, qu'arrive-t-il si lg_factorial2() est appelée sans aucun argument ?


octave:1> lg_factorial2()
error: `n' undefined near line 3 column 9
error: evaluating binary operator `<' near line 3, column 11
error: if: error evaluating conditional expression
error: evaluating if command near line 3, column 5
error: called from `lg_factorial2' in file `/home/user/lg/lg_factorial2.m'
octave:1>

Nous pouvons également vérifier pour nous assurer qu'un nombre valide d'arguments a été passé à la fonction à l'aide de la variable intégrée nargin. Cette variable est automatiquement initialisée au nombre d'arguments passés. Pendant que nous y sommes, nous devrions également essayer de nous assurer qu'un type de données valide est passé. Malheureusement, Octave n'a pas de fonction isinteger() mais nous pouvons vérifier que c'est un nombre réel et non un vecteur. Si un non entier réel est passé, il est arrondi automatiquement à la valeur inférieure par l'opérateur interval (2:n).


function answer = lg_factorial3( n )

    if( nargin != 1 )
        usage( "factorial( n )" );
    elseif( !isscalar( n ) ||  !isreal( n ) )
        error( "n doit être une valeur entière positive" );
    elseif( n < 0 )
        error( "il n'y a pas de définition pour les factorielles négatives" );
    endif

    if( n == 0 )
        answer = 1;
        return;
    else
        answer = prod( 1:n );
    endif

endfunction

Listing 3 : lg_factorial3.m

Cet exemple présente la fonction intégrée usage(). Elle est très similaire à error() en ce qu'elle affiche une trace des fonctions conduisant à l'appel pour aider au débogage mais, au lieu d'afficher "error: ...", elle affiche "usage: ...". Les fonctions isscalar() et isreal() vérifient que l'argument fourni est une valeur scalaire (par opposition à un vecteur, une chaîne, etc.) et un nombre réel (par opposition à un nombre complexe), respectivement. Le point d'exclamation ! placé avant inverse le test, de sorte qu'il lit comme si n n'était pas un scalaire ou n un nombre réel, puis renvoie une erreur.

Vous avez également remarqué que j'ai changé le code pour calculer la factorielle. J'utilise à présent la fonction intégrée prod() qui calcule le produit des éléments dans un vecteur donné. Dans ce cas, le vecteur donné est le interval 1:n.

Dans l'article précédent, j'ai mentionné qu'Octave comporte une documentation exhaustive qui est accessible via la commande info de GNU/Linux. Il y a aussi une fonction d'aide intégrée pour chaque commande. Par exemple :


octave:2> help prod
prod is a built-in function

 -- Built-in Function:  prod (X, DIM)
     Product of elements along dimension DIM.  If DIM is omitted, it
     defaults to 1 (column-wise products).

octave:3>

Une fonction « bien écrite » devrait permettre le recours à la fonction d'aide d'une manière similaire. Le texte d'aide est considéré comme le premier bloc de non copyright (voir les commentaires ci-dessous) provenant d'un fichier de fonction :


## usage: answer = lg_factorial4( n )
##
## Retourne la factorielle de n (n!). n doit être un entier 
## positif ou 0.

function answer = lg_factorial4( n )

    if( nargin != 1 )
        usage( "factorial( n )" );
    elseif( !isscalar( n ) ||  !isreal( n ) )
        error( "n doit être une valeur entière positive" );
    elseif( n < 0 )
        error( "il n'y a pas de définition pour les factorielles négatives" );
    endif

    if( n == 0 )
        answer = 1;
        return;
    else
        answer = prod( 1:n );
    endif

endfunction

Listing 4 : lg_factorial4.m

L'appel de la fonction d'aide donne à présent :


octave:3< help lg_factorial4
lg_factorial4 est la fonction définie par l'utilisateur à partir du fichier
/home/user/lg/lg_factorial4.m

usage: answer = lg_factorial4( n )

Retourne la factorielle de n (n!). n doit être un entier
positif ou 0.


octave:4>

Un autre algorithme courant pour calculer la factorielle d'un nombre consiste à employer une fonction récursive (une fonction récursive est une fonction qui s'appelle elle-même). Elle peut être implémentée dans Octave (en ignorant le contrôle d'erreur pour l'instant) ainsi :


function answer = lg_factorial5( n )

    if( n == 0 )
        answer = 1;
        return;
    else
        answer = n * lg_factorial5( n -1 );
    endif

endfunction

Listing 5 : lg_factorial5.m

3. Test de l'efficacité de la fonction

L'utilisation dans les universités d'Octave a tendance à prendre la forme de simulations à grande échelle et chronophages. À tel point qu'il est important de savoir comment écrire du code à la fois rapide et efficace aussi bien que tester l'efficacité du code que vous écrivez.

Commençons par comparer les trois algorithmes ci-dessus pour calculer la factorielle d'un nombre. Octave possède deux fonctions pour démarrer et arrêter un « minuteur d'horloge » (wall-clock timer) :


octave:4> tic(); sleep( 10 ); toc()
ans = 10.0
octave:5>

Pour justifier la comparaison, le seul contrôle que nous effectuons est si n == 0, sinon nous supposerons que c'est un entier positif. Nous utiliserons le programme du listing 5 ci-dessus, la version itérative présentée dans lg_factorial6.m et la version de prod() présentée dans lg_factorial7.m. Les commandes exécutées pour la comparaison et les temps se trouvent ici :

octave:5> tic(); for i=1:100 for n=1:100 lg_factorial5( n ); end; end; toc()
ans = 23.5154919996858
octave:6> tic(); for i=1:100 for n=1:100 lg_factorial6( n ); end; end; toc()
ans = 3.06905199959874
octave:7> tic(); for i=1:100 for n=1:100 lg_factorial7( n ); end; end; toc()
ans = 0.537685000337660
octave:8>

Premièrement, la fonction récursive a pris beaucoup plus de temps que prévu. Tout comme avec la plupart des autres langages de programmation, appeler des fonctions (même récursives) exige beaucoup de temps système (overhead). Le résultat qui risque de vous surprendre est que l'emploi de la fonction prod() est environ six fois plus rapide qu'une itération. C'est parce que Octave, comme Matlab, est assez lent pour itérer et il faudrait donc l'éviter autant que possible.

Comme je l'ai déjà établi, il est important que les fonctions soient bien écrites et bien documentées, en particulier si vous souhaitez partager votre code. D'aucuns peuvent penser que l'effet du contrôle d'une entrée valide peut considérablement ralentir l'exécution d'une fonction. Comparons l'algorithme d'itération dans lg_factorial6.m et le même algorithme mais un contrôle d'erreur complet dans lg_factorial8.m :

octave:8> tic(); for i=1:100000 lg_factorial6( 10 ); end; toc()
ans = 9.44780500046909
octave:9> tic(); for i=1:100000 lg_factorial8( 10 ); end; toc()
ans = 17.9307480007410
octave:10> 

Il y a clairement une différence, mais quand vous considérez que chaque fonction est appelée 100 000 fois, le temps ajouté pour le contrôle d'erreur n'est que de 0,000085 seconde par appel de fonction.

Une autre astuce importante quand on écrit des fonctions Octave est d'éviter de redimensionner les matrices inutilement. Le manuel lui-même stipule que si vous construisez une seule matrice résultante à partir d'une série de calculs, il faut définir la taille de la matrice résultante d'abord, puis y insérer des valeurs. Voici une illustration graphique de la lenteur que cela peut représenter :

octave:10> clear a; tic(); for i=1:100000 a(i) = i; end; toc()
ans = 52.4
octave:11> a = [1]; tic(); for i=2:100000 a = [a i]; end; toc()
ans = 362.247
octave:12> a=zeros(100000,1); tic(); for i=1:100000 a(i) = i; end; toc()
ans = 1.42
octave:13> 

4. Scripts Octave

Les fichiers de scripts Octave ne sont qu'une séquence de commandes Octave qui sont évalués comme si vous les avez saisis vous-même à l'invite d'Octave. Ils sont utiles pour configurer des simulations là où vous souhaitez varier certains paramètres pour chaque exécution sans avoir à resaisir les commandes à chaque fois, pour des séquences de commandes qui n'appartiennent logiquement pas à une fonction et pour automatiser certaines tâches.

Les scripts Octave se trouvent dans un fichier portant l'extension .m, comme les fonctions, si ce n'est qu'un fichier de script ne doit pas commencer par le mot-clé function. Notez que les variables définies dans un script partagent le même espace de noms (ou portée) que les variables définies à l'invite d'Octave.

Voici un exemple simple de script Octave qui calcule le temps nécessaire à l'utilisateur pour créer un tableau d'entiers d'une taille donnée :

# Un exemple de script Octave 

len = input( "Quelle taille de tableau souhaitez-vous utiliser pour l'évaluation : " );

clear a; 
tic(); 
for i=1:len
    a(i) = i; 
endfor 
time1 = toc();

a = [1]; 
tic(); 
for i=2:len 
    a = [a i]; 
endfor
time2 = toc();

a=zeros( len, 1 ); 
tic(); 
for i=1:len 
    a(i) = i; 
endfor
time3 = toc();

printf( "Le temps pris pour la méthode 1 était de %.4f seconde(s)\n", time1 );
printf( "Le temps pris pour la méthode 2 était de %.4f seconde(s)\n", time2 );
printf( "Le temps pris pour la méthode 3 était de %.4f seconde(s)\n", time3 );

Listing 6 : lg_calc_time.m

Une fois enregistré dans un fichier nommé de façon apppropriée, vous pouvez l'exécutez comme suit :



octave:13< lg_calc_time
Quelle taille de tableau souhaitez-vous utiliser pour l'évaluation : 20000
Le temps pris pour la méthode 1 était de 1.3509 seconde(s)
Le temps pris pour la méthode 2 était de 12.8984 seconde(s)
Le temps pris pour la méthode 3 était des 0.2850 seconde(s)
octave:14>


5. Scripts Octave exécutables

Il est également possible d'avoir des fichiers de script Octave exécutables comme nous avons des scripts Bash exécutables. C'est une fonctionnalité très intéressante d'Octave pour les gros problèmes où un mélange de programmes, d'outils ou d'applications peut s'avérer nécessaire pour les résoudre.

L'exemple du listing 6 ci-dessus peut facilement être converti en un programme Octave exécutable en ajoutant une seule ligne au début :

#! /usr/bin/octave -qf

# Un exemple de script Octave exécutable 

len = input( "Quelle taille de tableau souhaitez-vous utiliser pour l'évaluation : " );
... ... ...              ... ... ...              ... ... ...
printf( "Le temps pris pour la méthode 3 était de %.4f seconde(s)\n", time3 );

Listing 7 : lg_calc_time.sh

Assurez-vous simplement que le chemin d'accès au programme Octave est correct pour votre système (/usr/bin/octave), rendez le script exécutable et lancez-le comme suit :

[barry@hiscomputer lg]$ chmod u+x lg_calc_time.sh
[barry@hiscomputer lg]$ ./lg_calc_time.sh
What size array do you wish to use for the evaluation: 20000
The time taken for method 1 was 1.3959 seconds
The time taken for method 2 was 13.0201 seconds
The time taken for method 3 was 0.2800 seconds
[barry@hiscomputer lg]$

Si vous appelez le script avec des arguments en ligne de commande, ils seront disponibles au moyen de la variable intégrée argv et le nombre d'arguments sera contenu dans la variable nargin que nous avons déjà évoquée. Un exemple simple en est présenté dans lg_calc_time2.sh, qui est identique au dernier exemple, sauf qu'il lit la taille du tableau à partir de la ligne de commande.

6. En-têtes conventionnels et notes de copyright

Comme je l'ai indiqué dans l'article précédent, Octave n'est pas aussi complet que Matlab pour les fonctions spécialisées. Les développeurs d'Octave sont à l'écoute de nouveux ajouts bien écrits et « robustes ». Il y a des conventions pour écrire des fichiers de fonction qui comportent des informations sur l'auteur, des restrictions de copyright, une date de création, un numéro de version, etc. Les fonctions d'inclusion avec Octave devront être distribuées avec une licence open source appropriée (vous trouverez d'autres informations dans l'annexe A de la documentation officielle — en anglais). Avec des en-têtes et des notes de copyright correctes, voici à quoi devrait ressembler ma fonction factorielle :


## Copyright (C) 2005 Barry O'Donovan
##
## This program is free software; you can redistribute it and/or
## modify it under the terms of the GNU General Public
## License as published by the Free Software Foundation;
## either version 2, or (at your option) any later version.
##
## Octave is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied
## warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
## PURPOSE.  See the GNU General Public License for more
## details.
##
## You should have received a copy of the GNU General Public
## License along with Octave; see the file COPYING.  If not,
## write to the Free Software Foundation, 59 Temple Place -
## Suite 330, Boston, MA 02111-1307, USA.

## usage: answer = lg_factorial( n )
##
## Returns the factorial of n (n!). n should be a positive
## integer or 0.

## Author: Barry O'Donovan <barry@ihl.ucd.ie>
## Maintainer: Barry O'Donovan <barry@ihl.ucd.ie>
## Created: February 2005
## Version: 0.1
## Keywords: factorial

function answer = lg_factorial( n )

    if( nargin != 1 )
        usage( "factorial( n )" );
    elseif( !isscalar( n ) ||  !isreal( n ) )
        error( "n must be a positive integer value" );
    elseif( n &lt; 0 )
        error( "there is no definition for negative factorials" );
    endif
    
    if( n == 0 )
        answer = 1;
        return;
    else
        answer = prod( 1:n );
    endif

endfunction

Listing 8 : lg_factorial.m

7. Le mot de la fin

Il y a de nombreuses astuces et techniques pour écrire du code Octave efficace. Par exemple, on note l'utilisation non sans imagination de la fonction reshape() pour les opérations entre les éléments de la même matrice, de façon à éviter une itération. Je suis à l'affût de ce genre d'astuces et si j'en obtiens assez, je les regrouperai dans un article.

Barry O'Donovan est diplômé de la National University of Ireland (Galway), avec un B.Sc. (Hons) en informatique et en mathématiques. Il prépare actuellement une thèse d'informatique avec le Information Hiding Laboratory, au University College Dublin (Irlande) dans le secteur du marquage numérique audio.

Barry utilise Linux depuis 1997 et son choix actuel va à Fedora Core. Il est membre du Irish Linux Users Group. Lorsqu'il ne travaille pas à sa thèse, on peut le voir faire un peu de Open Hosting, au pub avec ses amis ou faire de la course dans le parc de sa ville.

Adaptation française de la Gazette Linux

L'adaptation française de ce document a été réalisée dans le cadre du Projet de traduction de la Gazette Linux.

Vous pourrez lire d'autres articles traduits et en apprendre plus sur ce projet en visitant notre site : http://wiki.traduc.org/Gazette_Linux.

Si vous souhaitez apporter votre contribution, n'hésitez pas à nous rejoindre, nous serons heureux de vous accueillir.