 |
7. Programmation : interaction, structures de contrôle, scripts, fonctions, entrées-sorties fichier, debugging |
 |
7.1 Généralités
Les "M-files" sont des fichiers au format texte (donc "lisibles") contenant des instructions MATLAB/Octave et portant l'extension *.m. On a vu la commande diary (au chapitre "Workspace") permettant d'enregistrer un "journal de session" qui, mis à part l'output des commandes, pourrait être considéré comme un M-file. Mais la manière la plus efficace de créer des M-files (c'est-à-dire "programmer" en langage MATLAB/Octave) consiste bien entendu à utiliser un éditeur de texte ou de programmation.
On distingue fondamentalement deux types de M-files : les scripts (ou programmes) et les fonctions. Les scripts travaillent dans le workspace, et toutes les variables créées/modifiées lors de l'exécution d'un script sont donc visibles dans le workspace et accessibles ensuite interactivement ou par d'autres scripts. Les fonctions, quant à elles, n'interagissent avec le workspace ou avec le script-appelant principalement via leurs "paramètres" d'entrée/sortie, les autres variables manipulées restant internes (locales) aux fonctions.
MATLAB/Octave est un langage interprété (comme les langages Perl, Python, Ruby, PHP, les shell Unix...), c'est-à-dire que les M-files (scripts ou fonctions) n'ont pas besoin d'être préalablement compilés avant d'être utilisés (comme c'est le cas des langage classiques C/C++, Java, Fortran...). A l'exécution, des fonctionnalités interactives de debugging et de profiling permettent d'identifier les bugs et optimiser le code.
MATLAB et Octave étant de véritables "progiciels", le langage MATLAB/Octave est de "haut niveau" et offre toutes les facilités classiques permettant de développer rapidement des applications interactives évoluées. Nous décrivons dans les chapitres qui suivent les principales possibilités de ce langage dans les domaines suivants :
- interaction avec l'utilisateur (affichage dans la console, saisie au clavier, affichage de warnings/erreurs...)
- structures de contrôle (boucles, tests...)
- élaboration de scripts et fonctions
- entrées-sorties (gestion de fichiers, formatage...)
- debugging et profiling
7.2 Éditeurs
Les M-files étant des fichiers-texte, il est possible de les créer et les éditer avec n'importe quel éditeur de texte/programmation de votre choix. Idéalement, celui-ci devrait notamment offrir des fonctionnalités d'indentation automatique, de coloration syntaxique...
7.2.1 Commandes relatives à l'édition
- Pour créer ou éditer un M-file depuis la fenêtre de commande MATLAB/Octave et basculer dans l'éditeur :
edit M-file ou edit('M-file') ou
open M-file ou open('M-file')
- Bascule dans l'éditeur et ouvre le M-file spécifié. Si celui-ci n'existe pas, il est proposé de créer un fichier de ce nom (sauf sous MATLAB où open retourne une erreur).
Il est obligatoire de passer un nom de fichier à la commande open. S'agissant de edit, si l'on ne spécifie pas de M-file cela ouvre une fenêtre d'édition de fichier vide.
Sous MATLAB et sous Octave GUI, c'est l'éditeur intégré à ces IDE's qui est bien entendu utilisé. Si vous utilisez MATLAB ou Octave-CLI en ligne de commande dans une fenêtre terminal, voyez dans les chapitres qui suivent comment spécifier quel éditeur doit être utilisé.
Depuis les interfaces graphiques MATLAB et Octave GUI, on peut bien entendu aussi faire :
- ouvrir un fichier existant : , bouton Open File, ou double-clic sur l'icône d'un M-file dans l'explorateur de fichiers intégré (
"Current folder" ou
"File Browser")
- créer un nouveau fichier :
,
ou bouton New Script
7.2.2 Éditeur/débugger intégré à MATLAB
MATLAB fournit un IDE complet comportant un éditeur (illustration ci-dessous) offrant également des possibilités de debugging.

Éditeur intégré de MATLAB R2014 (ici sous Windows)
Quelques fonctionnalités pratiques de l'éditeur intégré de MATLAB :
- Pour indenter à droite / désindenter à gauche un ensemble de ligne, sélectionnez-les et faites tab / maj-tab (ou )
- Pour commenter/décommenter un ensemble de lignes de code (c'est-à-dire ajouter/enlever devant celles-ci le caractère %), sélectionnez-les et faites ctrl-R / ctrl-T (ou )
- Concernant l'usage des boutons et raccourcis de debugging, voir le chapitre "Debugging"
Pour utiliser un autre éditeur, définissez-le avec la commande
setenv('EDITOR','path/editeur')
7.2.3 Éditeurs pour GNU Octave
Éditeur/débugger intégré à Octave GUI
Avec l'apparition de l'interface graphique Octave GUI (Octave Graphical User Interface) depuis Octave ≥ 3.8, on dispose également d'un IDE complet comportant un éditeur permettant de faire de la coloration syntaxique, autocomplétion, debugging (définition de breakpoints, exécution step-by-step...).

Éditeur intégré de GNU Octave 4.0 (ici sous Windows)
Quelques fonctionnalités pratiques de l'éditeur intégré de Octave GUI :
- Sous les 3 systèmes d'exploitation, l'éditeur utilise l'encodage UTF-8. Cela ne pose pas de problème sous GNU/Linux et macOS où l'on peut donc utiliser des caractères spéciaux (accentués...).
Sous Windows cependant, c'est la console Command Window qui ne supporte pas les caractères spéciaux. Donc limitez-vous actuellement sous Windows aux caractères ASCII 7bit (non accentués...).
- Pour indenter à droite / désindenter à gauche un ensemble de ligne, sélectionnez-les et faites tab / maj-tab (ou )
- Pour commenter/décommenter un ensemble de lignes de code (c'est-à-dire ajouter/enlever devant celles-ci le caractère %), sélectionnez-les et faites ctrl-R / ctrl-maj-R (ou )
- Il est possible de placer des bookmarks ▶ dans la marge gauche avec F7 (ou ), puis de se déplacer de bookmarks en bookmarks avec F2 (ou ) et maj-F2 (ou )
- Concernant l'autocomplétion : sous onglet "Editor", vous voyez que l'autocomplétion est par défaut activée pour les fonctions, builtins et keywords. Vous pouvez encore activer "Match words in document" pour l'autocomplétion des noms de variables ! Si l'autocomplétion vous dérange, vous pouvez désactiver l'option "Show completion list automatically" et en faire usage à la demande avec ctrl-espace (ou ). La sélection dans la liste s'effectue avec curseur-haut ou bas, puis la complétion avec enter ou tab
- Lorsque le curseur se trouve juste à gauche ou à droite d'un crochet/parenthèse/accolade ouvrant ou fermant, vous pouvez vous déplacer vers le caractère correspondant (fermant ou ouvrant) avec ctrl-M (ou ), ou sélectionner tout ce qui se trouve entre deux avec ctrl-maj-M (ou ).
- Concernant les blocs d'instructions correspondant aux structures de contrôle, vous pouvez cliquer sur les symboles - et + dans la marge gauche pour replier/déplier ces blocs.
- Concernant l'usage des boutons et raccourcis de debugging, voir le chapitre "Debugging"
- La personnalisation de l'éditeur s'effectue avec dans les onglets "Editor" et "Editor Styles"
Autres éditeurs pour Octave
Il est cependant possible (et nécessaire lorsqu'on utilise Octave en ligne de commande depuis une fenêtre terminal) d'utiliser d'autres éditeurs de programmation. La situation dépend du système d'exploitation (voir notre chapitre "Installation/configuration GNU Octave") :
- sous Windows : les distributions GNU Octave MinGW et MXE intègrent l'éditeur Notepad++ (auparavant c'était Scintilla SciTE), mais d'autres bons éditeurs de programmation font aussi l'affaire, tels que : Atom, ConTEXT, cPad...
- sous Linux : on peut utiliser les éditeurs de base Gedit sous GNOME et Kate sous KDE, ainsi que Geany, Atom...
- sous macOS : nous vous recommandons Atom (libre), sinon TextWrangler (freeware)...
On spécifie quel éditeur doit être invoqué lorsque l'on travaille avec Octave-CLI (i.e. lorsque l'on passe la commande edit) avec la fonction built-in
EDITOR('path/editeur') que l'on insère généralement dans son prologue .octaverc
Ex: Le morceau de script multi-plateforme ci-dessous teste sur quelle plateforme on se trouve et redéfinit ici "Gedit" comme éditeur par défaut dans le cas où l'on est sous Linux :
if ~isempty(findstr(computer,'linux'))
EDITOR('gedit') % définition de l'éditeur par défaut
edit('mode','async') % exécuter la commande "edit" de façon détachée
else
% on n'est pas sous Linux, ne rien faire de particulier
end

Éditeur de programmation libre Notepad++ sous Windows
Conseils relatifs à l'éditeur Gedit sous Linux
En premier lieu, enrichissez votre éditeur Gedit par un jeu de "plugins" supplémentaires déjà packagés :
- pour ce faire, sous Linux/Ubuntu, installez le paquet "gedit-plugins" (en passant la commande : sudo apt-get install gedit-plugins)
- vous activerez ci-dessous les plugins utiles, depuis Gedit, via , puis dans l'onglet "Plugins"
- certains de ces plugins peuvent ensuite être configurés via le bouton Configure Plugin
Activation de la coloration syntaxique :
- sous Gedit, via (ou via le menu déroulant de langage dans la barre de statut de Gedit)
- activation de la mise en évidence des parenthèses, crochets et acollades : via , puis dans l'onglet "View" avtiver "Highlight matching brackets"
Affichage des numéros de lignes : via , puis dans l'onglet "View" activer "Display line numbers"
Pour pouvoir mettre en commentaire un ensemble de lignes sélectionnées :
- d'abord activer le plugin "Code comment"
- on peut dès lors utiliser, dans le menu , les commandes (raccourci ctrl-M) et (ctrl-maj-M)
Fermeture automatique des parenthèses, crochets, acollades, apostrophes ... : en activant simplement le plugin "Bracket Completion"
Affichage des caractères spéciaux tab, espace ... : en activant (et configurant) le plugin "Draw Spaces"
Pour automatiser certaines insertions (p.ex. structures de contrôles...) :
- activer le plugin "Snippets"
- puis, pour par exemple faire en sorte que si vous frappez iftab cela insère automatiquement l'ensemble de lignes suivantes :
ifespace
tab
else
tab
end
définissez avec , dans la catégorie "Octave", avec le
bouton + (Create new snippet), un snippet nommé if avec les attributs :
• tab trigger : if
• dans le champ Edit : le code à insérer figurant ci-dessus
• shortcut key (facultatif) : associez-y un raccourci clavier
Et réalisez ainsi d'autres snippets, par exemples pour les structures : for...end, while...end, do...until, switch...case...
7.3 Interaction écran/clavier, warnings et erreurs
Pour être en mesure de développer des scripts MATLAB/Octave interactifs (affichage de messages, introduction de données au clavier...) et les "débugger", MATLAB et Octave offrent bon nombre de fonctionnalités utiles décrites dans ce chapitre.
7.3.1 Affichage de texte et de variables
disp(variable)
disp('chaîne')
- Affiche la variable ou la chaîne de caractère spécifiée. Avec cette commande, et par oposition au fait de frapper simplement variable, seul le contenu de la variable est affiché et pas son nom. Les nombres sont formatés conformément à ce qui a été défini avec la commande format (présentée au chapitre "Fenêtre de commande").
Ex: les commandes M=[1 2;3 5] ; disp('La matrice M vaut :') , disp(M) produisent l'affichage du texte "La matrice M vaut :" sur une ligne, puis celui des valeurs de la matrice M sur les lignes suivantes
- {count=} fprintf('format', variable(s)...)
{count=} printf('format', variable(s)...)
- Affiche, de façon formatée, la(les) variable(s) spécifiées (et retourne facultativement le nombre count de caractères affichés). Cette fonction ainsi que la syntaxe du format, repris du langage de programmation C, sont décrits en détails au chapitre "Entrées-sorties".
L'avantage de cette méthode d'affichage, par rapport à disp, est que l'on peut afficher plusieurs variables, agir sur leur formatage (nombre de chiffres après le point décimal, justification...) et entremêler texte et variables sur la même ligne de sortie.
Ex: si l'on a les variables v=444; t='chaîne de car.';, l'instruction fprintf('variable v= %6.1f et variable t= %s \n',v,t) affiche, sur une seule ligne : "variable v= 444.0 et variable t= chaîne de car."
7.3.2 Affichage et gestion des avertissements et erreurs, beep
Gestion d'erreurs et avertissements
3:30 min |
Gestion d'erreurs et avertissements
|
Les erreurs sont des évènements qui provoquent l'arrêt d'un script ou d'une fonction, avec l'affichage d'un message explicatif.
Les avertissements (warnings) consistent en l'affichage d'un message sans que le déroulement soit interrompu.
warning( {'id',} 'message')
warning( {'id',} 'format', variable(s)...)
- Affiche le message spécifié sous la forme "warning: message", puis continue (par défaut) l'exécution du script ou de la fonction.
Le message peut être spécifié sous la forme d'un format (voir spécification des "Formats d'écriture" au chapitre "Entrées-sorties"), ce qui permet alors d'incorporer une(des) variable(s) dans le message !
L'identificateur id du message prend la forme composant{:composant}:mnémonique , où :
- le premier composant spécifie p.ex. le nom du package
- le second composant spécifie p.ex. le nom de la fonction
- le mnémonique est une notation abrégée du message
L'identificateur id est utile pour spécifier les conditions de traitement de l'avertissement (voir ci-dessous).
Sous Octave, une description de tous les types de warnings prédéfinis est disponible avec
help warning_ids
- struct = warning
- Passée sans paramètre, cette fonction indique de quelle façon sont traités les différents types de messages d'avertissements (warnings). Les différents états possibles sont :
on= affichage du message d'avertissement, puis continuation de l'exécution
off= pas d'affichage de message d'avertissement et continuation de l'exécution
error= condition traitée comme une erreur, donc affichage du message d'avertissement puis interruption !
- warning('on|off|error', 'id' )
- Changement de la façon de traiter les avertissements du type id spécifié. Voir ci-dessus la signification des conditions on, off et error. On arrive ainsi à désactiver (off) certains types d'avertissements, les réactiver (on), ou même les faire traiter comme des erreurs (error) !
- warning('query', 'id' )
- Récupère le statut courant de traitement des warnings de type id
- {string=}lastwarn
- Affiche (ou récupère sur la variable string) le dernier message d'avertissement (warning)
- Ex:
-
• X=123; S='abc'; warning('Demo:test','X= %u et chaine S= %s', X, S)
affiche l'avertissement : 'warning: X= 123 et chaîne S= abc'
• puis si l'on fait warning('off','Demo:test')
et que l'on exécute à nouveau le warning ci-dessus, il n'affiche plus rien
• puis si l'on fait warning('error','Demo:test')
et que l'on exécute à nouveau le warning ci-dessus, cela affiche cette fois-ci une erreur : 'error: X vaut: 123 et la chaîne S: abc'
error('message')
error('format', variable(s)...)
- Affiche le message indiqué sous la forme "error: message", puis interrompt l'exécution du script ou de la fonction dans le(la)quel(le) cette instruction a été placée, ainsi que l'exécution du script ou de la fonction appelante. Comme pour warning, le message peut être spécifié sous la forme d'un format, ce qui permet alors d'incorporer une(des) variable(s) dans le message.
Sous Octave, si l'on veut éviter qu'à la suite du message d'erreur soit affiché un "traceback" de tous les appels de fonction ayant conduit à cette erreur, il suffit de terminer la chaîne message par le caractère "newline", c'est-à-dire définir error("message... \n"). Mais comme on le voit, la chaîne doit alors être définie entre guillemets et non pas entre apostrophes, ce qui pose problème à MATLAB. Une façon de contourner ce problème pour faire du code portable pour Octave et MATLAB est de définir error(sprintf('message... \n'))
Remarque générale : Lorsque l'on programme une fonction, si l'on doit prévoir des cas d'interruption pour cause d'erreur, il est important d'utiliser error(...) et non pas disp('message'); return, afin que les scripts utilisant cette fonction puissent tester les situations d'erreur (notamment avec la structure de contrôle try...catch...end).
- {string=}lasterr
- Affiche (ou récupère sur la variable string) le dernier message d'erreur
- beep
- Émet un beep sonore
7.3.3 Entrée d'information au clavier
a) variable=input('prompt') ;
b) chaîne=input('prompt', 's') ;
- MATLAB/Octave affiche le prompt ("invite") spécifié, puis attend que l'utilisateur entre quelque-chose au clavier terminé par la touche enter
a) En l'absence du paramètre 's', l'information entrée par l'utilisateur est "interprétée" (évaluée) par MATLAB/Octave, et c'est la valeur résultante qui est affectée à la variable spécifiée. L'utilisateur peut donc, dans ce cas, saisir une donnée de n'importe quel type et dimension (nombre, vecteur, matrice...) voire toute expression valide !
On peut, après cela, éventuellement détecter si l'utilisateur n'a rien introduit (au cas où il aurait uniquement frappé enter) avec : isempty(variable), ou length(variable)==0
b) Si l'on spécifie le second paramètre 's' (signifiant string), le texte entré par l'utilisateur est affecté tel quel (sans évaluation) à la variable chaîne indiquée. C'est donc cette forme-là que l'on utilise pour saisir interactivement du texte.
Dans les 2 cas, on place généralement, à la fin de cette commande, un ; pour que MATLAB/Octave "travaille silencieusement", c'est-à-dire ne quittance pas à l'écran la valeur qu'il a affectée à la variable.
- Ex:
- • la commande v1=input('Entrer v1 (scalaire, vecteur, matrice, expression, etc...) : ') ; affiche "Entrer v1 (scalaire, vecteur, matrice, expression, etc...) : " puis permet de saisir interactivement la variable numérique "v1" (qui peut être de n'importe quel type/dimension)
• la commande nom=input('Entrez votre nom : ', 's') ; permet de saisir interactivement un nom (contenant même des espace...)
- a) pause
b) pause(secondes)
- a) Lorsque le script rencontre cette instruction sans paramètre, il effectue une pause, c'est-à-dire attend que l'utilisateur frappe n'importe quelle touche au clavier pour continuer son exécution.
b) Si une durée secondes est spécifiée, le script reprend automatiquement son exécution après cette durée.
Sous MATLAB, on peut passer la commande
pause off pour désactiver les éventuelles pauses qui seraient effectuées par un script (puis
pause on pour rétablir le mécanisme des pauses).
7.4 Structures de contrôle
Structures de contrôle, traitement d'erreurs
19:17 min |
Les structures de contrôle sont un aspect fondamental de tous les langages de programmation. Il s'agit de constructions permettant d'exécuter des blocs d'instructions de façon itérative (boucle) ou sous condition (test). Nous présentons dans cette vidéo :
• les boucles for, while et do/until, et l'effet dans celles-ci des instructions break et continue
• le test if/elsif/else
• la construction switch/case
• le traitement d'erreurs avec try/catch, et les fonctions warning et error
Mais avant de programmer des boucles (coûteuses en temps d'exécution), on réfléchira cependant toujours à deux fois, sous MATLAB/Octave (langages permettant le traitement natif de tableaux de N-dimension sans implémenter de boucles) s'il n'est pas possible de s'en passer, en programmant plutôt des expressions tirant parti des possibilités vectorisées de MATLAB/Octave (y.c. l'indexation logique qu'on présentera dans une prochaine vidéo), ce qui permet des gains de temps considérables lorsqu'on manipule de gros tableaux (millions d'éléments).
|
7.4.1 Présentation des structures de contrôle
Les "structures de contrôle" sont, dans un langage de programmation, des constructions permettant d'exécuter des blocs d'instructions de façon itérative (boucle) ou sous condition (test). MATLAB/Octave offre les structures de contrôle de base typiques présentées dans le tableau ci-dessous et qui peuvent bien évidemment être "emboîtées" les unes dans les autres. Notez que la syntaxe est différente des structures analogues dans d'autres langages (C, Java, Python).
Comme MATLAB/Octave permet de travailler en "format libre" (les caractères espace et tab ne sont pas significatifs), on recommande aux programmeurs MATLAB/Octave de bien "indenter" leur code, lorsqu'ils utilisent des structures de contrôle, afin de faciliter la lisibilité et la maintenance du programme.
Octave propose aussi des variations aux syntaxes présentées plus bas, notamment :
endfor,
endwhile,
endif,
endswitch,
end_try_catch, ainsi que d'autres structures telles que:
unwind_protect body... unwind_protect_cleanup cleanup... end_unwind_protect
Nous vous recommandons de vous en passer pour que votre code reste portable !
Structure |
Description |
Boucle for...end
for var = tableau
instructions...
end
|
Considérons d'abord le cas général où tableau est une matrice 2D (de nombres, de caractères, ou cellulaire... peu importe). Dans ce cas, l'instruction for parcourt les différentes colonnes de la matrice (c'est-à-dire matrice(:,i)) qu'il affecte à la variable var qui sera ici un vecteur colonne. À chaque "itération" de la boucle, la colonne suivante de matrice est donc affectée à var, et le bloc d'instructions est exécuté.
Si tableau est un vecteur, ce n'est qu'un cas particulier :
• dans le cas où c'est un vecteur ligne (p.ex. une série), la variable var sera un scalaire recevant à chaque itération l'élément suivant de ce vecteur
• si c'est un vecteur colonne, la variable var sera aussi un vecteur colonne qui recevra en une fois toutes les valeurs de ce vecteur, et le bloc d'instructions ne sera ainsi exécuté qu'une seule fois puis on sortira directement de la boucle !
Si l'on a tableau à 3 dimensions, for examinera d'abord les colonnes de la 1ère "couche" du tableau, puis celles de la seconde "couche", etc...
Ex:
- for n=10:-2:0 , n^2, end affiche successivement les valeurs 100 64 36 16 4 et 0
- for n=[1 5 2;4 4 4] , n, end affiche successivement les colonnes [1;4] [5;4] et [2;4]
|
Boucle parfor...end
parfor (var=deb:fin {, max_threads})
instructions...
end
|
Variante simplifiée mais parallélisée de la boucle for...end :
• le bloc d'instructions est exécuté parallèlement en différents threads (au maximum max_threads) sur plusieurs cores de CPU, l'ordre d'itération n'étant cependant pas garanti
• deb et fin doivent être des entiers, et fin > deb
|
Boucle while...end ("tant que" la condition n'est pas fausse)
while expression_logique
instructions...
end
|
Si l'expression_logique est satisfaite, l'ensemble d'instructions spécifiées est exécuté, puis l'on reboucle sur le test. Si elle ne l'est pas, on saute aux instructions situées après le end.
On modifie en général dans la boucle des variables constituant l'expression_logique, sinon la boucle s'exécutera sans fin (et on ne peut dans ce cas en sortir qu'avec un ctrl-C dans la fenêtre console !)
S'agissant de l'expression_logique, cela ne doit pas nécessairement être un scalaire ; ce peut aussi être un tableau de dimension quelconque, et dans ce cas la condition est considérée comme satisfaite si tous les éléments ont soit la valeur logique True soit des valeurs différentes de 0. A contrario elle n'est pas satisfaite si un ou plusieurs éléments ont la valeur logique False ou la valeur 0.
Ex:
- n=1 ; while (n^2 < 100) , disp(n^2) , n=n+1 ; end affiche les valeurs n^2 depuis n=1 et tant que n^2 est inférieur à 100, donc affiche les valeurs 1 4 9 16 25 36 49 64 81 puis s'arrête
|
Boucle do...until ("jusqu'à ce que" une condition se vérifie)
do
instructions...
until expression_logique
|
Propre à Octave, cette structure de contrôle classique permet d'exécuter des instructions en boucle tant que l'expression_logique n'est pas satisfaite. Lorsque cette expression est vraie, on sort de la boucle. Voir plus haut au chapitre while...end ce qu'il faut entendre par expression_logique.
La différence par rapport à la boucle while est que l'on vérifie la condition après avoir exécuté au moins une fois le bloc d'instructions.
Pour atteindre le même but sous MATLAB, on pourrait faire une boucle sans fin de type while true instructions... end à l'intérieur de laquelle on sortirait par un test et l'instruction break (voir plus bas).
|
Test if...elseif...else...end ("si, sinon si, sinon")
if expression_logique_1
instructions_1...
{ elseif expression_logique_i
instructions_i... }
{ else
autres_instructions... }
end
|
Si l'expression_logique est satisfaite, le bloc instructions_1 est exécuté, puis on saute directement au end sans faire les éventuels autres tests elseif.
Sinon (si l'expression_logique_1 n'est pas satisfaite), MATLAB/Octave examine tour à tour les éventuelles clauses elseif. Si l'une d'entre elles est satisfaite, le bloc instructions_i correspondant est exécuté, puis on saute directement au end (sans tester les autres clauses elseif).
Si aucune expression_logique n'a été satisfaite et qu'on a défini une clause else, le bloc autres_instructions correspondant est exécuté.
Noter que les blocs elseif ainsi que le bloc else sont donc facultatifs !
Voir plus haut au chapitre while...end ce qu'il faut entendre par expression_logique.
Ex:
- Soit le bloc d'instructions if n==1, disp('un'), elseif n==2, disp('deux'), else, disp('autre'), end.
Si l'on affecte n=1, l'exécution de ces instructions affiche "un", si l'on affecte n=2 il affiche "deux", et si "n" est autre que 1 ou 2 il affiche "autre"
|
Construction switch...case...otherwise...end
switch expression
case val1
instructions_a...
case {val2, val3 ...}
instructions_b...
{ otherwise
autres_instructions... }
end
|
Cette structure de contrôle réalise ce qui suit : si l'expression spécifiée (qui peut se résumer à une variable) retourne la valeur val1, seul le bloc d'instructions_a qui suit est exécuté. Si elle retourne la valeur val2 ou val3, seul le bloc de instructions_b correspondant est exécuté... et ainsi de suite. Si elle ne retourne rien de tout ça et qu'on a défini une clause otherwise, c'est le bloc des autres_instructions correspondant qui est exécuté.
Important : notez bien que si, dans une clause case, vous définissez plusieurs valeurs val possibles, il faut que celles-ci soient définies sous la forme de tableau cellulaire, c'est-à-dire entre caractères { }.
Contrairement au "switch" du langage C, il n'est pas nécessaire de définir des break à la fin de chaque bloc.
On peut avoir autant de blocs case que l'on veut, et la partie otherwise est donc facultative.
En lieu et place de val1, val2... on peut bien entendu aussi mettre des expressions.
Ex: |
Construction try...catch...end
try
instructions_1...
catch
instructions_2...
end
autres_instructions...
|
Cette construction sert à implémenter des traitements d'erreur.
Les instructions comprises entre try et catch (bloc instructions_1) sont exécutées jusqu'à ce qu'une erreur se produise, auquel cas MATLAB/Octave passe automatiquement à l'exécution des instructions comprises entre catch et end (bloc instructions_2). Le message d'erreur ne s'affiche pas mais peut être récupéré avec la commande lasterr.
Si le premier bloc de instructions_1 s'exécute absolument sans erreur, le second bloc de instructions_2 n'est pas exécuté, et le déroulement se poursuit avec autres_instructions
|
Sortie anticipée d'une boucle
break
|
A l'intérieur d'une boucle for, while ou do, cette instruction permet, par exemple suite à un test, de sortir prématurément de la boucle et de poursuivre l'exécution des instructions situées après la boucle. Si 2 boucles sont imbriquées l'une dans l'autre, un break placé dans la boucle interne sort de celle-ci et continue l'exécution dans la boucle externe.
A ne pas confondre avec return (voir plus bas) qui sort d'une fonction, respectivement interrompt un script !
Ex: sortie d'une boucle for :
for k=1:100
k2=k^2;
fprintf('carré de %3d = %5d \n',k,k2)
if k2 > 200 break, end % sortir boucle lorsque k^2 > 200
end
fprintf('\nsorti de la boucle à k = %d\n',k)
Ex: sortie d'une boucle while :
k=1;
while true % à priori boucle sans fin !
fprintf('carré de %3d = %5d \n', k, k^2)
if k >= 10 break, else k=k+1; end
% ici on sort lorsque k > 10
end
|
Sauter le reste des instructions d'une boucle et continuer d'itérer
continue
|
A l'intérieur d'une boucle (for ou while), cette instruction permet donc, par exemple suite à un test, de sauter le reste des instructions de la boucle et passer à l'itération suivante de la même boucle.
Ex:
start=1; stop=100; fact=8;
fprintf('Nb entre %u et %u divisibles par %u : ',start,stop,fact)
for k=start:1:stop
if rem(k,fact) ~= 0
continue
end
fprintf('%u, ', k)
end
disp('fin')
Le code ci-dessus affiche: "Nb entre 1 et 100 divisibles par 8 :
8, 16, 24, 32, 40, 48, 56, 64, 72, 80, 88, 96, fin"
|
double(var) |
Cette instruction permet de convertir en double précision la variable var qui, dans certaines constructions for, while, if, peut n'être qu'en simple précision |
Les structures de contrôle sont donc des éléments de langage extrêmement utiles. Mais dans MATLAB/Octave, il faut "penser instructions matricielles" (on dit aussi parfois "vectoriser" son algorithme) avant d'utiliser à toutes les sauces ces structures de contrôle qui, du fait que MATLAB est un langage interprété, sont beaucoup moins rapides que les opérateurs et fonctions matriciels de base !
Ex: l'instruction y=sqrt(1:100000); est beaucoup plus efficace/rapide que la boucle for n=1:100000, y(n)=sqrt(n); end (bien que, dans les 2 cas, ce soit un vecteur de 100'000 éléments qui est créé contenant les valeurs de la racine de 1 jusqu'à la racine de 100'000). Testez vous-même !
7.5 Scripts (programmes), mode batch
Scripts
19:16 min |
Les "scripts" sont la dénomination des programmes sous MATLAB/Octave. Dans cette vidéo nous voyons :
• ce qui caractérise un script
• comment un script peut interagir avec l'extérieur, que ce soit à travers le workspace, ou interactivement avec l'utilisateur
• comment on peut documenter un script ainsi qu'élaborer une aide en-ligne facilitant son utilisation
• comment utiliser efficacement l'éditeur intégré des environnements de développement Octave et MATLAB
• ce qu'est le "prologue utilisateur" sous MATLAB/Octave
|
7.5.1 Principes de base relatifs aux scripts
Un "script" ou "programme" MATLAB/Octave n'est rien d'autre qu'une suite de commandes MATLAB/Octave valides (par exemple un "algorithme" exprimé en langage MATLAB/Octave) sauvegardées dans un M-file, c'est-à-dire un fichier avec l'extension .m.
Par opposition aux "fonctions" (voir chapitre suivant), les scripts sont invoqués par l'utilisateur sans passer d'arguments, car ils opèrent directement dans le workspace principal. Un script peut donc lire et modifier des variables préalablement définies (que ce soit interactivement ou via un autre script), ainsi que créer de nouvelles variables qui seront accessibles dans le workspace (et à d'autres scripts) une fois le script exécuté.
À partir de la version R2016B de MATLAB, il est possible de définir des fonctions à l'intérieur d'un script (ce qui n'était auparavant pas supporté sous MATLAB). Il faut cependant prendre garde au fait que :
- ces fonctions sont purement locales au script, c'est-à-dire qu'elles ne peuvent pas être invoquées en dehors de celui-ci (que ce soit depuis d'autres scripts, fonctions externes ou interactivement depuis la fenêtre console)
- le M-file débutera par le code du script, la(les) fonction(s) devant prendre place à la fin du M-file
Il est possible (et vivement conseillé) de documenter le fonctionnement du script vis-à-vis du système d'aide en ligne help de MATLAB/Octave. Il suffit, pour cela, de définir, au tout début du script, des lignes de commentaire (lignes débutant par le caractère %). La commande help M-file affichera alors automatiquement le 1er bloc de lignes de commentaire contiguës du M-file. On veillera à ce que la toute première ligne de commentaire (appelée "H1-line") indique le nom du script (en majuscules) et précise brièvement ce que fait le script, étant donné que c'est cette ligne qui est affichée lorsque l'on fait une recherche de type lookfor mot-clé.
Pour exécuter un script, on peut utiliser l'une des méthodes suivantes, selon que l'on soit dans l'éditeur intégré, dans la console MATALAB/Octave ou depuis un autre script :
- a)
Run,
Save File and Run ou F5
b) script enter
c) run('{chemin/}script{.m}') ou run {chemin/}script{.m}
d)
source('{chemin/}script.m') ou source {chemin/}script.m
-
a) Lancement de l'exécution depuis l'éditeur intégré MATLAB ou Octave GUI
b) Le script doit obligatoirement se trouver dans le répertoire courant ou dans le path de recherche MATLAB/Octave (voir chapitre "Environnement"). Il ne faut pas spécifier l'extension .m du script
c) Cette forme permet d'exécuter un script situé en dehors du répertoire courant en indiquant le chemin d'accès (absolu ou relatif). On peut omettre ou spécifier l'extension .m du script
d) Cette forme est propre à Octave. Dans ce cas l'extension .m doit obligatoirement être indiquée
En phase de debugging, on peut activer l'affichage des commandes exécutées par le script en passant la commande echo on avant de lancer le script, puis désactiver ce "traçage" avec echo off une fois le script terminé.
Exemple de script: Le petit programme ci-dessous réalise la somme et le produit de 2 nombres, vecteurs ou matrices (de même dimension) demandés interactivement. Notez bien la 1ère ligne de commentaire (H1-line) et les 2 lignes qui suivent fournissant le texte pour l'aide en-ligne. On exécute ce programme en frappant somprod (puis répondre aux questions interactives...), ou l'on obtient de l'aide sur ce script en frappant help somprod.
%SOMPROD Script réalisant la somme et le produit de 2 nombres, vecteurs ou matrices
%
% Ce script est interactif, c'est-à-dire qu'il demande interactivement les 2 nombres,
% vecteurs ou matrices dont il faut faire la somme et le produit (élément par élément)
V1=input('Entrer 1er nombre (ou expression, vecteur ou matrice) : ') ;
V2=input('Entrer 2e nombre (ou expression, vecteur ou matrice) : ') ;
if ~ isequal(size(V1),size(V2))
error('les 2 arguments n''ont pas la meme dimension')
end
%{
1ère façon d'afficher les résultats (la plus propre au niveau affichage,
mais ne convenant que si V1 et V2 sont des scalaires) :
fprintf('Somme = %6.1f Produit = %6.1f \n', V1+V2, V1.*V2)
2ème façon d'afficher les résultats :
Somme = V1+V2
Produit = V1.*V2
%}
% 3ème façon (basique) d'afficher les résultats
disp('Somme =') , disp(V1+V2)
disp('Produit =') , disp(V1.*V2)
return % Sortie du script (instruction ici pas vraiment nécessaire,
% vu qu'on a atteint la fin du script !)
7.5.2 Exécuter un script en mode batch
Pour autant qu'il ne soit pas interactif, on peut exécuter un script depuis un shell (dans fenêtre de commande du système d'exploitation) ou en mode batch (p.ex. environnement GRID), c'est-à-dire sans devoir démarrer l'interface-utilisateur MATLAB/Octave, de la façon décrite ici.
Avec MATLAB :
- En premier lieu, il est important que le script.m s'achève sur une instruction quit, sinon la fenêtre MATLAB (minimisée dans la barre de tâches sous Windows) ne se refermera pas
- Passer la commande (sous Windows depuis une fenêtre "invite de commande", sous Linux depuis une fenêtre shell) :
matlab -nodesktop -nosplash -nodisplay -r script { -logfile fichier_resultat }
où :
- sous Windows, il faudra faire précéder la commande matlab du path (chemin d'accès à l'exécutable MATLAB, p.ex. C:\Program Files (x86)\MATLAB85\bin\ sous Windows 7)
- à la place de -logfile fichier_resultat on peut aussi utiliser > fichier_resultat
- le fichier de sortie fichier_resultat sera créé (en mode écrasement s'il préexiste)
- Sachez finalement qu'il est possible d'utiliser interactivement MATLAB en mode commande dans une fenêtre terminal (shell) et sans interface graphique (intéressant si vous utilisez MATLAB à distance sur un serveur Linux) ; il faut pour cela démarrer MATLAB avec la commande : matlab -nodesktop -nosplash
Avec Octave :
- Contrairement à MATLAB, il n'est ici pas nécessaire que le script.m s'achève par une instruction quit
- Vous avez ensuite les possibilités suivantes :
- Depuis une fenêtre de commande (fenêtre "invite de commande" sous Windows, shell sous Linux), frapper :
octave --silent --no-window-system script.m { > fichier_resultat }
- En outre, sous Linux ou macOS, vous pouvez aussi procéder ainsi :
- faire débuter le script par la ligne: #!/usr/bin/octave --silent --no-window-system
- puis mettre le script en mode execute avec la commande: chmod u+x script.m
- puis lancer le script avec: ./script.m { > fichier_resultat }
Notez que, dans ces commandes :
- avec > fichier_resultat, les résultats du script sont redirigés dans le fichier_resultat spécifié et non pas affichés dans la fenêtre de commande
- --silent (ou -q) : n'affiche pas les messages de démarrage de Octave
- --no-window-system (ou -W) : désactive le système de fenêtrage (i.e. X11 sous Linux) ; les graphiques apparaissent alors dans la fenêtre de commande (simulés par des caractères), mais il reste possible d'utiliser la commande saveas pour les sauvegarder sous forme de fichiers-image
- en ajoutant encore l'option --norc (ou -f) ou --no-init-file), on désactiverait l'exécution des prologues (utilisateurs .octaverc, et système)
- vous pourriez donc aussi faire débuter votre script par la ligne #!/usr/bin/octave -qWf
Ex: le script ci-dessous produit un fichier de données "job_results.log" et un graphique "job_graph.png" :
% Exemple de script MATLAB/Octave produisant des données et un graphique
% que l'on peut lancer en batch avec :
% matlab -nosplash -nodisplay -nodesktop -r job_matlab > job_results.log
% octave --silent --no-window-system --norc job_matlab.m > job_results.log
data = randn(100,3)
fig = figure;
plotmatrix(data, 'g+');
saveas(fig, 'job_graph.png', 'png')
quit
- A l'intérieur de votre script, vous pouvez récupérer sur un vecteur cellulaire avec la fonction argv() les différents arguments passés à la commande octave
En outre avec Octave, si vous ne désirez exécuter en "batch" que quelques commandes sans faire de script, vous pouvez procéder ainsi :
- Depuis une fenêtre de commande ou un shell, frapper:
octave -qf --eval "commandes..." { > fichier_resultat.txt }
Ex: la commande octave -qf --eval "disp('Hello !'), arguments=argv(), a=12; douze_au_carre=12^2, disp('Bye...')" affiche :
Hello !
arguments =
{
[1,1] = -qf
[2,1] = --eval
[3,1] = disp('Hello'), arguments=argv(), a=12; douze_au_carre=12^2, disp('Bye...')
}
douze_au_carre = 144
Bye...
7.5.3 Tester si un script s'exécute sous MATLAB ou sous Octave
Étant donné les différences qui peuvent exister entre MATLAB et Octave (p.ex. fonctions implémentées différemment ou non disponibles...), si l'on souhaite réaliser des scripts portables (i.e. qui tournent à la fois sous MATLAB et Octave, ce qui est conseillé !) on peut implémenter du code conditionnel relatif à chacun de ces environnements en réalisant, par exemple, un test via une fonction built-in appropriée.
Ex: on test ici l'existence de la fonction built-in OCTAVE_VERSION (n'existant que sous Octave) :
if exist('OCTAVE_VERSION') % Octave
% ici instruction(s) pour Octave
else % MATLAB
% ici instruction(s) équivalente(s) pour MATLAB
end
7.6 Fonctions
Fonctions
17:15 min |
Les fonctions utilisateur sont fondamentales en programmation, permettant notamment de rendre un code plus modulaire, plus lisible et moins redondant. Nous montrons dans cette vidéo :
• ce qui distingue fondamentalement les fionctions des scripts (présentés dans une vidéo précédente), au niveau de leur implémentation et de leur utilisation, notamment la question du passage d'arguments en entrée et la récupération de données en sortie
• la portée et la durée de vie des variables au sein des fonctions, et comment modifier cela (avec les instructions global et persistent)
• ce qu'il est possible de faire, s'agissant de la combinaison, dans un même M-file, de plusieurs fonctions
|
7.6.1 Principes de base relatifs aux fonctions
Également programmées sous la forme de M-files, les "fonctions" MATLAB/Octave se distinguent des "scripts" par la manière de les utiliser. On les appelle ainsi :
[variable(s) de sortie ...] = nom_fonction(paramètre(s) d'entree ...)
- On invoque donc une fonction en l'appelant par son nom_fonction et en lui passant, entre parenthèses, les paramètres d'entrée requis (qui peuvent être des valeurs, des variables, des expressions...) ;
s'il n'y a pas de paramètre à passer, on peut omettre les parenthèses, par exemple : beep() ou beep.
- Si la fonction retourne des valeurs de sortie, on l'invoque en l'affectant à une ou des variable(s) entre crochets ;
s'il n'y a qu'une valeur de sortie, les crochets peuvent être omis.
Le mécanisme de passage des paramètres à la fonction s'effectue "par valeur" (c'est-à-dire copie) et non pas "par référence". La fonction ne peut donc pas modifier les variables d'entrée au niveau du script appelant (workspace principal) ou de la fonction appelante (workspace de cette dernière).
Les variables créées à l'intérieur de la fonction sont dites "locales", c'est-à-dire qu'elles sont par défaut inaccessibles en dehors de la fonction (que ce soit dans le workspace principal ou dans d'autres fonctions ou scripts). Chaque fonction dispose donc de son propre workspace local de fonction.
- Si l'on tient cependant à ce que certaines variables de la fonction soient visibles et accessibles à l'extérieur de celle-ci, on peut les rendre "globales" en les définissant comme telles dans la fonction, avant qu'elles ne soient utilisées, par une déclaration global variable(s) (voir plus bas). Il faudra aussi faire une telle déclaration dans le workspace principal (et avant d'utiliser la fonction !) si l'on veut pouvoir accéder à ces variables dans le workspace principal ou ultérieurement dans d'autres scripts !
- Il est en outre possible de déclarer certaines variables comme "statiques" avec la déclaration persistent (voir aussi plus bas) au cas où l'on désire, à chaque appel à la fonction, retrouver certaines variables internes dans l'état où elles ont été laissées lors de l'appel précédent. Attention, cela ne devrait pas être utilisé pour les fonctions qui doivent être récursive !
Il est possible de définir, dans le même M-file, plusieurs fonctions à la suite les unes des autres. Cependant seule la première fonction du M-file, appelée fonction principale (main function), sera accessible de l'extérieur. Les autres, appelées fonctions locales (ou subfunctions), ne pourront être appelées que par la fonction principale ou les autres fonctions locales du M-file.
Il est finalement possible de définir des fonctions à l'intérieur du corps d'une fonction. Ces fonctions imbriquées (nested function) ne pourront cependant être invoquées que depuis la fonction dans laquelle elles sont définies.
Le code de toute fonction débute par une ligne de déclaration de fonction qui définit son nom_fonction et ses arguments args_entree et args_sortie (séparés par des virgules), selon la syntaxe :
function [argument(s)_de_sortie, ...] = nom_fonction(argument(s)_d_entree, ...)
- les crochets ne sont pas obligatoires s'il n'y a qu'un arg_sortie
- s'il n'y a pas de paramètre de sortie, on peut omettre []=
- s'il n'y a pas de paramètre d'entrée, on peut omettre ()
Octave affiche un warning si le nom de la fonction principale (tel que défini dans la 1ère déclaration de fonction) est différent du nom du M-file. Sous MATLAB, le fait que les 2 noms soient identiques n'est pas obligatoire mais fait partie des règles de bonne pratique.
Se succèdent donc, dans le code d'une fonction (et dans cet ordre) :
- déclaration de la fonction principale (ligne function... décrite ci-dessus)
- lignes de commentaires (commençant par le caractère %) décrivant la fonction pour le système d'aide en-ligne, à savoir :
• la "H1-line" précisant le nom de la fonction et indiquant des mots-clé (ligne qui sera retournée par la commande lookfor mot-clé)
• les lignes du texte d'aide (qui seront affichées par la commande help nom_fonction)
- déclarations d'éventuelles variables globale ou statique
- code proprement dit de la fonction (qui va affecter les variables argument(s)_de_sortie)
- éventuelle(s) instruction(s) return définissant de(s) point(s) de sortie de la fonction
- instruction end signalant la fin de la fonction
Les templates de fonctions Octave se terminent par l'instruction
endfunction, mais nous vous suggérons de la remplacer par end afin que vos fonctions soient compatibles à la fois pour MATLAB et Octave. Mais en fait sous MATLAB et Octave, l'instruction end finale d'une fonction peut aussi être omise, sauf lorsque l'on définit plusieurs fonctions dans un même M-file.
Exemple de fonction: On présente, ci-dessous, deux façons de réaliser une petite fonction retournant le produit et la somme de 2 nombres, vecteurs ou matrices. Dans les deux cas, le M-file doit être nommé fsomprod.m (c'est-à-dire identique au nom de la fonction). On peut accéder à l'aide de la fonction avec help fsomprod, et on affiche la première ligne d'aide en effectuant par exemple une recherche lookfor produit. Dans ces 2 exemples, mis à part les arrêts en cas d'erreurs (instructions error), la sortie s'effectue à la fin du code mais aurait pu intervenir ailleurs (instructions return) !
Fonction |
Appel de la fonction |
function resultat = fsomprod(a,b)
%FSOMPROD somme et produit de 2 nombres ou vecteurs-ligne
% Usage: R=FSOMPROD(V1,V2)
% Retourne matrice R contenant: en 1ère ligne la
% somme de V1 et V2, en seconde ligne le produit de
% V1 et V2 élément par élément
if nargin~=2
error('cette fonction attend 2 arguments')
end
sa=size(a); sb=size(b);
if ~ isequal(sa,sb)
error('les 2 arguments n'ont pas la même dimension')
end
if sa(1)~=1 || sb(1)~=1
error('les arg. doivent être scalaires ou vecteurs-ligne')
end
resultat(1,:)=a+b; % 1ère ligne de la matrice-résultat
resultat(2,:)=a.*b; % 2ème ligne de la matrice-résultat,
% produit élément par élément !
return % sortie de la fonction (instruction ici pas
% nécessaire vu qu'on a atteint fin fonction)
end % pas nécessaire si le fichier ne contient que cette fct
|
Remarque : cette façon de retourner le résultat (sur une seule variable) ne permet pas de passer à cette fonction des matrices.
r=fsomprod(4,5)
retourne la vecteur-colonne r=[9 ; 20]
r=fsomprod([2 3 1],[1 2 2])
retourne la matrice r=[3 5 3 ; 2 6 2]
|
function [somme,produit] = fsomprod(a,b)
%FSOMPROD somme et produit de 2 nombres, vecteurs ou matrices
% Usage: [S,P]=FSOMPROD(V1,V2)
% Retourne matrice S contenant la somme de V1 et V2,
% et matrice P contenant le produit de V1 et V2
% élément par élément
if nargin~=2
error('cette fonction attend 2 arguments')
end
if ~ isequal(size(a),size(b))
error('les 2 arg. n'ont pas la même dimension')
end
somme=a+b;
produit=a.*b; % produit élément par élément !
return % sortie de la fonction (instruction ici pas
% nécessaire vu qu'on a atteint fin fonction)
end % pas nécessaire si le fichier ne contient que cette fct
|
Remarque : cette façon de retourner le résultat (sur deux variable) rend possible le passage de matrices à cette fonction !
[s,p]=fsomprod(4,5)
retourne les scalaires s=9 et p=20
[s,p]=fsomprod([2 3;1 2],[1 2; 3 3])
retourne les matrices s=[3 5 ; 4 5] et p=[2 6 ; 3 6]
|
7.6.2 Pointeurs de fonctions et fonctions anonymes
Un pointeur de fonction (function handle) est une technique alternative d'invocation d'une fonction. L'intérêt réside dans le passage de fonctions à d'autres fonctions et la définition de fonctions anonymes (voir ci-dessous). Les pointeurs de fonctions sont notamment beaucoup utilisés pour définir les callbacks dans les interfaces utilisateurs graphiques (voir chapitre "Programmation GUI").
- un pointeur de fonction est défini par l'instruction : function_handle = @function_name ; où function_name peut désigner une fonction MATLAB/Octave ou une fonction utilisateur
- on peut dès lors appeler la fonction avec l'instruction : function_handle(éventuels paramètres de la fonction...)
- on peut vérifier le type de variable associée à un pointeur de fonction avec : whos function_handle
- avec functions(function_handle) on récupère une structure qui renseigne sur : nom de la fonction, son type (simple, anonymous, subfunction...), éventuel m-file associé
Ex:
si l'on défini f = @sin ;, on peut tracer le graphique de cette fonction avec fplot(f, [0 2*pi]) (qui est équivalent à fplot(@sin, [0 2*pi]))
on peut bien entendu ensuite calculer le sinus de π avec f(pi) qui est équivalent à feval(f, pi)
Une fonction anonyme (anonymous function) est une façon très concise de définir une fonction-utilisateur basée sur une expression.
- la syntaxe de définition d'une fonction anonyme est :
function_handle= @(arg1, arg2...) expression ;
la fonction anonyme ne doit pas nécessairement être affectée à un pointeur de fonction, d'où le nom de "fonction anonyme" ou "fonction sans nom" (unnamed function)
- on peut dès lors appeler cette fonction anonyme avec : function_handle(arg1, arg2...)
Ex:
on pourrait définir la fonction "carré" avec carre = @(nb) nb.^2 ; puis l'invoquer avec carre([2 3 4]) qui retournera [4 9 16]
ou la grapher avec fplot(carre, [0 3]), ou tracer la fonction carré de façon complètement anonyme avec fplot(@(nb) nb.^2, [0 3])
la fonction somme = @(nb1, nb2) nb1+nb2 ; implémente la somme de 2 variables, et somme([2 3], [4 1]) retournera donc [6 4]
Une fonction inline est une ancienne manière de définir une fonction anonyme. Notez cependant que les fonctions inline vont disparaître dans une prochaine version de MATLAB et que Octave suivra également le mouvement... donc ne les utilisez en principe plus.
- la syntaxe de définition d'une fonction inline est :
function_handle= inline('expression' {, 'arg1', 'arg2'...}) ;
- on peut alors appeler cette fonction inline avec : function_handle(arg1, arg2...)
Ex:
on pourrait définir la fonction "carré" avec carre = inline('nb.^2') ; puis l'invoquer avec carre([2 3 4]) qui retournera [4 9 16]
la fonction somme = inline('nb1+nb2', 'nb1', 'nb2') implémente la somme de 2 variables, et somme([2 3], [4 1]) retournera donc [6 4]
7.6.3 P-Code
Lorsque du code MATLAB est exécuté, il est automatiquement interprété et traduit ("parsing") dans un langage de plus bas niveau qui s'appelle le P-Code (pseudo-code). Sous MATLAB seulement, s'agissant d'une fonction souvent utilisée, on peut éviter que cette "passe de traduction" soit effectuée lors de chaque appel en sauvegardant le P-Code sur un fichier avec la commande
pcode nom_fonction. Un fichier de nom nom_fonction.p est alors déposé dans le répertoire courant (ou dans le dossier où se trouve le M-file si l'on ajoute à la commande pcode le paramètre -inplace), et à chaque appel la fonction pourra être directement exécutée sur la base du P-Code de ce fichier sans traduction préalable, ce qui peut apporter des gains de performance.
Le mécanisme de conversion d'une fonction ou d'un script en P-Code offre également la possibilité de distribuer ceux-ci à d'autres personnes sous forme binaire en conservant la maîtrise du code source.
7.7 Autres commandes et fonctions utiles en programmation
Nous énumérons encore ici quelques commandes ou fonctions supplémentaires qui peuvent être utiles dans la programmation de scripts ou de fonctions.
return
- Termine l'exécution de la fonction ou du script. Un script ou une fonction peut renfermer plusieurs return (sorties contrôlées par des structures de contrôle...). Une autre façon de sortir proprement en cas d'erreur est d'utiliser la fonction error (voir plus haut).
On ne sortira jamais avec exit ou quit qui non seulement terminerait le script ou la fonction mais fermerait aussi la session MATLAB/Octave !
- var= nargin
- A l'intérieur d'une fonction, retourne le nombre d'arguments d'entrée passés lors de l'appel à cette fonction. Permet par exemple de donner des valeurs par défaut aux paramètres d'entrée manquant.
Utile sous Octave pour tester si le nombre de paramètres passés par l'utilisateur à la fonction est bien celui attendu par la fonction (ce test n'étant pas nécessaire sous MATLAB ou le non respect de cette condition est automatiquement détecté).
Voir aussi la fonction nargchk qui permet aussi l'implémentation simple d'un message d'erreur.
Ex: voir ci-après
- varargin
- A l'intérieur d'une fonction, tableau cellulaire permettant de récupérer un nombre d'arguments quelconque passé à la fonction
Ex: soit la fonction test_vararg.m suivante :
function test_vararg(varargin)
fprintf('Nombre d''arguments passes a la fonction : %d \n',nargin)
for no_argin=1:nargin
fprintf('- argument %d:\n', no_argin)
disp(varargin{no_argin} )
end
end
si on l'invoque avec test_vararg(111,[22 33;44 55],'hello !',{'ca va ?'}) elle retourne :
Nombre d'arguments passes a la fonction : 4
- argument 1:
111
- argument 2:
22 33
44 55
- argument 3:
hello !
- argument 4:
{ [1,1] = ca va ? }
- string= inputname(k)
- A l'intérieur d'une fonction, retourne le nom de variable du k-ème argument passé à la fonction
- var= nargout
- A l'intérieur d'une fonction, retourne le nombre de variables de sortie auxquelles la fonction est affectée lors de l'appel. Permet par exemple d'éviter de calculer les paramètres de sortie manquants....
Voir aussi la fonction nargoutchk qui permet aussi l'implémentation simple d'un message d'erreur.
Ex: A l'intérieur d'une fonction-utilisateur mafonction :
• lorsqu'on l'appelle avec mafonction(...) : nargout vaudra 0
• lorsqu'on l'appelle avec out1 = mafonction(...) : nargout vaudra 1
• lorsqu'on l'appelle avec [out1 out2] = mafonction(...) : nargout vaudra 2, etc...
- string= mfilename
- A l'intérieur d'une fonction ou d'un script, retourne le nom du M-file de cette fonction ou script, sans son extension .m
Ex d'instruction dans une fonction : warning(['La fonction ' mfilename ' attend au moins un argument'])
- global variable(s)
- Définit la(les) variable(s) spécifiée(s) comme globale(s). Cela peut être utile lorsque l'on veut partager des données entre fonctions sans devoir passer ces données en paramètre lors de l'appel à ces fonctions. Il est alors nécessaire de déclarer ces variables globales, avant de les utiliser, que ce soit interactivement sans la console ou dans des scripts ou fonctions.
Une bonne habitude serait d'identifier clairement les variables globales de fonctions, par exemple en leur donnant un nom en caractères majuscules.
Ex : la fonction fct1.m ci-dessous mémorise (et affiche) le nombre de fois qu'elle a été appelée :
function fct1
global COMPTEUR
COMPTEUR=COMPTEUR+1;
fprintf('fonction appelee %04u fois \n',COMPTEUR)
end
Pour tester cela, il faut passer les instructions suivantes dans la fenêtre de commande MATLAB/Octave :
global COMPTEUR % cela déclare le compteur également global dans le workspace
COMPTEUR = 0 ; % initialisation du compteur
fct1 % => cela affiche "fonction appelee 1 fois"
fct1 % => cela affiche "fonction appelee 2 fois"
- persistent variable(s)
- Utilisable dans les fonctions seulement, cette déclaration définit la(les) variable(s) spécifiée(s) comme statique(s), c'est-à-dire conservant de façon interne leurs dernières valeurs entre chaque appel à la fonction. Ces variables ne sont cependant pas visibles en-dehors de la fonction (par opposition aux variables globales).
Ex : la fonction fct2.m ci-dessous mémorise (et affiche) le nombre de fois qu'elle a été appelée. Contrairement à l'exemple de la fonction fct1.m ci-dessus, la variable compteur n'a pas à être déclarée dans la session principale (ou dans le script depuis lequel on appelle cette fonction), et le compteur doit ici être initialisé dans la fonction.
function fct2
persistent compteur
% au premier appel, après cette déclaration persistent compteur existe et vaut []
if isempty(compteur)
compteur=0 ;
end
compteur=compteur+1 ;
fprintf('fonction appelee %04u fois \n',compteur)
end
Pour tester cela, il suffit de passer les instructions suivantes dans la fenêtre de commande MATLAB/Octave :
fct2 % => cela affiche "fonction appelee 1 fois"
fct2 % => cela affiche "fonction appelee 2 fois"
- a) eval('expression1', {'expression2'})
b) var = evalc(...)
-
a) Évalue et exécute l'expression1 MATLAB/Octave spécifiée. En cas d'échec, évalue l'expression2
b) Comme a) sauf que l'output et les éventuelles erreurs sont envoyés sur var
Ex: le petit script suivant permet de grapher n'importe quelle fonction y=f(x) définie interactivement par l'utilisateur :
fonction = input('Quelle fonction y=fct(x) voulez-vous grapher : ','s');
min_max = input('Indiquez [xmin xmax] : ');
x = linspace(min_max(1),min_max(2),100);
eval(fonction,'error(''fonction incorrecte'')');
plot(x,y)
end
- class(objet)
- Retourne la "classe" de objet (double, struct, cell, char).
typeinfo(objet)
- Sous Octave seulement, retourne le "type" de objet (scalar, range, matrix, struct, cell, list, bool, sq_string, char matrix, file...).
7.8 Entrées-sorties formatées, manipulation de fichiers
Lorsqu'il s'agit de charger, dans MATLAB/Octave, une matrice à partir de données externes stockées dans un fichier-texte, les commandes load -ascii et dlmread/dlmwrite, présentées au chapitre "Workspace MATLAB/Octave", sont suffisantes. Mais lorsque les données à importer sont dans un format plus complexe ou qu'il s'agit d'importer du texte ou d'exporter des données vers d'autres logiciels, les fonctions présentées ci-dessous s'avèrent nécessaires.
7.8.1 Vue d'ensemble des fonctions d'entrée/sortie de base
Le tableau ci-dessous donne une vision synthétique des principales fonctions d'entrée/sortie (présentées en détail dans les chapitres qui suivent). Notez que :
- le caractère s débutant le nom de certaines fonctions signifie "string", c'est-à-dire que l'on manipule des chaînes
- le caractère f débutant le nom de certaines fonctions signifie "file", c'est-à-dire que l'on manipule des fichiers
- le caractère f terminant le nom de certaines fonctions signifie "formaté"
|
Écriture |
Lecture |
Interactivement
|
Écriture à l'écran (sortie standard)
• non formaté: disp(variable|chaîne)
(avec un seul paramètre !)
• formaté: fprintf(format, variable(s))
(ou printf...)
|
Lecture au clavier (entrée standard)
• non formaté: var = input(prompt {, 's'} )
• formaté: var = scanf(format)
|
Sur chaîne de caractères
|
• string = sprintf(format, variable(s))
• autres fonctions : mat2str ...
|
• var|mat = sscanf(string, format {,size})
• [var1, var2... ] = strread(string,format {,n})
• autres fonctions : textscan ...
|
Sur fichier texte
|
• fprintf(file_id, format, variable(s)... )
• autres fonctions : save -ascii, dlmwrite ...
|
• var = fscanf(file_id, format {,size})
• line = fgetl(file_id)
• string = fgets(file_id {,nb_car})
• autres fonctions : load(fichier), textread, textscan, fileread, dlmread ...
|
Via internet (protocoles HTTTP, FTP ou FILE)
|
• string = urlread(url, method, param)
• urlwrite(url, fichier, method, param)
(pour url de type HTTP, method : 'get' ou 'post')
• autres fonctions : webread et webwrite (RESTful web services )
|
• string = urlread(url)
• urlwrite(url, fichier)
• autres fonctions : webread et webwrite (RESTful web services )
|
Sur fichier binaire
|
• autres fonctions : fwrite, xlswrite ...
|
• autres fonctions : fread, xlsread ...
|
7.8.2 Formats de lecture/écriture
Formats, écriture et décodage de chaînes
08:10 min |
Les spécifications de format et leur usage dans l'écriture et décodage de chaînes
|
Les différentes fonctions de lecture/écriture sous forme texte présentées ci-dessous font appel à des "formats" (parfois appelés "templates" dans la documentation). Le but de ceux-ci est de :
- décrire la manière selon laquelle il faut interpréter ce que l'on lit (s'agit-il d'un nombre, d'une chaîne de caractère...)
- dlfinir sous quelle forme il faut écrire les données (pour un nombre: combien de chiffres avant/après la virgule ; pour une chaîne: type de justification...)
Les formats MATLAB/Octave utilisent un sous-ensemble des conventions et spécifications de formats du langage C.
Les formats sont des chaînes de caractères se composant de "spécifications de conversion" dont la syntaxe est décrite dans le tableau ci-dessous.
ATTENTION : dans un format de lecture (sscanf, fscanf), on ne préfixera en principe pas les "spécifications de conversion" de nombres (u d i o x X f e E g G) par des valeurs n (taille du champ) et m (nombre de décimales), car le comportement de MATLAB et de Octave peut alors conduire à des résultats différents (découpage avec MATLAB, et aucun effet sous Octave).
Ex: sscanf('560001','%4f') retourne :
sous MATLAB le vecteur [5600 ; 1] , et
sous Octave la valeur 560001
Spécifications |
Description |
espace |
• En lecture (sscanf, fscanf) : les caractères espace dans un format sont ignorés (i.e. n'ont aucune signification) !
• En écriture (sprintf, fprintf) : les caractères espace dans un format sont écrits dans la chaîne résultante !
|
%u
%nu
|
Correspond à un nombre entier positif (non signé)
• En lecture:
Ex: sscanf('100 -5','%u') => 100 et 4.295e+09 (!)
• En écriture: si n est spécifié, le nombre sera justifié à droite dans un champ de n car. au min.
Ex: sprintf('%5u ', 100, -5) => ' 100 -5.000000e+000' et ' 100 -5'
|
%d %i
%nd %ni
|
Correspond à un nombre entier positif ou négatif
• En lecture:
Ex: sscanf('100 -5','%d') => 100 et -5
• En écriture: si n est spécifié, le nombre sera justifié à droite dans un champ de n car. au min.
Ex: sprintf('%d %03d ', 100, -5) => '100 -05'
|
%o
%no
|
Correspond à un nombre entier positif en base octale
• En lecture:
Ex: sscanf('100 -5','%o') => 64 et 4.295e+09 (!)
• En écriture: si n est spécifié, le nombre sera justifié à droite dans un champ de n car. au min.
Ex: sprintf('%04o ', 64, -5) => '0100 -5.000000e+000' et '0100 -005'
|
%x %X
%nx %nX
|
Correspond à un nombre entier positif en base hexadécimale
• En lecture:
Ex: sscanf('100 -5','%x') => 256 et 4.295e+09 (!)
• En écriture: si n est spécifié, le nombre sera justifié à droite dans un champ de n car. au min.
Ex: sprintf('%x %04X ', 255, 255, -5) => 'ff 00FF -5.000000e+000' et 'ff 00FF -5'
|
%f
%nf %n.mf
|
Correspond à un nombre réel sans exposant (de la forme {-}mmm.nnn)
• En lecture:
Ex: sscanf('5.6e3 xy -5','%f xy %f') => [5600 ; -5]
• En écriture: si n est spécifié, le nombre sera justifié à droite dans un champ de n car. au min., et affiché avec m chiffres après la virgule.
Ex1: sprintf('%4.2f', 3.467) => '3.47' donc notez bien qu'il y a un arrondi automatique et non pas une troncature (i.e. on ne reçoit pas '3.46')
Ex2: sprintf('%f %0.2f ', 56e002, -78e-13, -5) => '5600.000000 -0.00 -5.000000'
|
%e %E
%ne %nE
%n.me %n.mE
|
Correspond à un nombre réel en notation scientifique (de la forme {-}m.nnnnnE{+|-}xxx)
• En lecture:
Ex: sscanf('5.6e3 xy -5','%e %*s %e') => [5600 ; -5]
• En écriture: si n est spécifié, le nombre sera justifié à droite dans un champ de n car. au min., et affiché avec m chiffres après la virgule. Avec e, le caractère 'e' de l'exposant sera en minuscule ; avec E il sera en majuscule.
Ex: sprintf('%e %0.2E ', 56e002, -78e-13, -5) => '5.600000e+003 -7.80E-12 -5.000000e+00'
|
%g %G
%ng %nG
|
Correspond à un nombre réel en notation scientifique compacte (de la forme {-}m.nnnnnE{+|-}x)
• En lecture:
Ex: sscanf('5.6e3 xy -5','%g %*2c %g') => [5600 ; -5]
• En écriture: donne lieu à une forme plus compacte que %f et %e. Si n est spécifié, le nombre sera justifié à droite dans un champ de n car. au min. Avec g, le caractère 'e' de l'exposant sera en minuscule ; avec G il sera en majuscule.
Ex: sprintf('%g %G ', 56e002, -78e-13, -5) => '5600 -7.8E-12 -5'
|
%c
%nc
|
Correspond à 1 ou n caractère(s), y compris d'éventuels caractères espace
• En lecture:
Ex: sscanf('ab1 2cd3 4ef', '%2c %*3c') => 'abcdef'
• En écriture:
Ex: sprintf(' %c: (ASCII: %3d)\n', 'aabbcc') => cela affiche :
a: (ASCII: 97)
b: (ASCII: 98)
c: (ASCII: 99)
|
%s
%ns
|
Correspond à une chaîne de caractères
• En lecture: les chaînes sont délimitées par un ou plusieurs caractères espace
Ex: sscanf('123 abcd', '%2s %3s') => '123abcd'
• En écriture: si n est spécifié, la chaîne sera justifiée à droite dans un champ de n car. au min.
Ex: sprintf('%s|%5s|%-5s|', 'blahblah...', 'abc', 'XYZ')
=> 'blahblah...| abc|XYZ |'
|
Caractères
spéciaux
|
Pour faire usage de certains caractères spéciaux, il est nécessaire de les encoder de la façon suivante :
• \n pour un saut à la ligne suivante (new line)
• \t pour un tab horizontal
• %% pour le caractère "%"
• \\ pour le caractère "\"
|
Tout autre
caractère
|
Tout autre caractère (ne faisant pas partie d'une spécification %...) sera utilisé de la façon suivante :
• En lecture: le caractère "matché" sera sauté. Exception: les caractères espace dans un format de lecture sont ignorés
Ex: sscanf('12 xy 34.5 ab 67', '%f xy %f ab %f') => [12.0 ; 34.5 ; 67.0]
• En écriture: le caractère sera écrit tel quel dans la chaîne de sortie
Ex: article='stylos' ; fprintf('Total: %d %s \n', 4, article) => 'Total: 4 stylos'
|
De plus, les "spécifications de conversion" peuvent être modifiées (préfixées) de la façon suivante :
Spécifications |
Description |
%-n... |
• En écriture: l'élément sera justifié à gauche (et non à droite) dans un champ de n car. au min.
Ex: sprintf('|%-5s|%-5.1f|', 'abc', 12) => '|abc |12.0 |'
|
%0n... |
• En écriture: l'élément sera complété à gauche par des '0' (chiffres zéros, et non caractères espace) dans un champ de n car. au min.
Ex: sprintf('|%05s|%05.1f|', 'abc', 12) => '|00abc|012.0|'
|
%*... |
• En lecture: saute l'élément qui correspond à la spécification qui suit
Ex: sscanf('12 blabla 34.5 67.8', '%d %*s %*f %f') => [12 ; 67.8]
|
7.8.3 Lecture/écriture formatée de chaînes
Lecture/décodage de chaîne
La fonction sscanf ("string scan formated") permet, à l'aide d'un format de lecture, de décoder le contenu d'une chaîne de caractère et d'en récupérer les données sur un vecteur ou une matrice. La lecture s'effectue en "format libre" en ce sens que sont considérés, comme séparateurs d'éléments dans la chaîne, un ou plusieurs espace ou tab. Si la chaîne renferme davantage d'éléments qu'il n'y a de "spécifications de conversion" dans le format, le format sera "réutilisé" autant de fois que nécessaire pour lire toute la chaîne. Si, dans le format, on mélange des spécifications de conversion numériques et de caractères, il en résulte une variable de sortie (vecteur ou matrice) entièrement numérique dans laquelle les caractères des chaînes d'entrée sont stockés, à raison d'un caractère par élément de vecteur/matrice, sous forme de leur code ASCII.
vec = sscanf(string, format)
[vec, count] = sscanf(string, format)
- Décode la chaîne string à l'aide du format spécifié, et retourne le vecteur-colonne vec dont tous les éléments seront de même type. La seconde forme retourne en outre, sur count, le nombre d'éléments générés.
Ex:
• vec=sscanf('abc 1 2 3 4 5 6', '%*s %f %f') => vec=[1;2;4;5]
Notez que, en raison de la "réutilisation" du format, les nombres 3 et 6 sont ici sautés par le %*s !
• vec=sscanf('1001 1002 abc', '%f %f %s') => vec=[1001;1002;87;98;99]
Mélange de spécifications de conversion numériques et de caractères => la variable 'vec' est de type nombre, et la chaîne 'abc' y est stockée par le code ASCII de chacun de ses caractères
- mat = sscanf(string, format, size)
[mat, count] = sscanf(string, format, size)
- Permet de remplir une matrice mat, colonne après colonne. La syntaxe du paramètre size est :
• nb => provoque la lecture des nb premiers éléments, et retourne un vecteur colonne
• [nb_row, nb_col] => lecture de nb_row x nb_col éléments, et retourne une matrice de dimension nb_row x nb_col
Ex:
• vec=sscanf('1 2 3 4 5 6', '%f', 4) => vec=[1;2;3;4]
• [mat,ct]=sscanf('1 2 3 4 5 6', '%f', [3,2]) => mat=[1 4 ; 2 5 ; 3 6], ct=6
• [mat,ct]=sscanf('1 2 3 4 5 6', '%f', [2,3]) => mat=[1 3 5 ; 2 4 6], ct=6
[var1, var2, var3 ...] = sscanf(string, format, 'C')
- (Proche du langage C, cette forme très flexible n'est disponible que sous Octave)
Chaque "spécification de conversion" définie dans le format est associée à une variable de sortie var-i qui sera du type correspondant !
Ex:
•
[str,nb1,nb2]=sscanf('abcde 12.34 45.3e14 fgh', '%3c %*s %f %f', 'C') => str='abc', nb1=12.34, nb2=4.53e+15
[var1, var2, var3... ] = strread(string, format {,n} {,'delimiter',delimiteur})
- (Analogue à textread présenté plus bas, mais agissant ici sur une string).
Fonction de découpage de la chaîne string : chaque "spécification de conversion" définie dans le format est associée à une variable de sortie var-i qui sera du type correspondant ! Tant qu'il y a des données à lire, le format est réutilisé circulairement n fois, puis le découpage s'arrête. Si n n'est pas spécifié, le format est réutilisé autant de fois que nécessaire.
Le découpage s'effectue là où il y a 1 ou plusieurs caractères espace. Mais on peut spécifier d'autres caractères de délimitation par la chaîne delimiteur. On utilisera la notation \... pour désigner certains caractères spéciaux, p.ex. \t pour le caractère tab.
Les "spécification de conversion" suivantes peuvent être utilisées : %s pour identifier une chaîne ; %f ou %n pour un nombre double précision ; %d ou %u pour un entier 32bits ; %* pour sauter un mot ; string pour sauter la chaîne string.
Ex:
• [str,nb1,nb2]=strread('abcde 1.23 4.56 fgh 7.98 10.1', '%s%f%f', 1) => str={'abcde'}, nb1=1.23, nb2=4.56 ; format utilisé 1x
• [str,nb1,nb2]=strread('abcde 1.23 4.56 fgh 7.98 10.1', '%s%f%f') => str={'abcde' ; 'fgh'}, nb1=[1.23 ; 7.98], nb2=[4.56 ; 10.1] ; format ici utilisé 2x
• [nb1,int2,nb3]=strread('1.23 ** 4.56 ## 7.89', '%f ** %u %* %f') => nb1=1.23, int2=5, nb3=7.89 ; '**' étant explicitement sauté, de même que '##' qui est matché par %*
• [nb1,nb2]=strread('1.2 # 3.4 * 5.6 # 7.8', '%f%f','delimiter','*#') => nb1=[1.2 ; 5.6], nb2=[3.4 ; 7.8] ; usage des délimiteurs espace, * et #
Rappelons que si une chaîne ne contient que des nombres, on peut aussi aisément récupérer ceux-ci à l'aide de la fonction str2num présentée au chapitre sur les "Chaînes de caractères".
Ex: str2num('12 34 ; 56 78') retourne la matrice [12 34 ; 56 78]
Voir finalement aussi la fonction textscan qui est capable de lire des chaînes mais aussi des fichiers !
Écriture formatée de variables sur une chaîne
La fonction sprintf ("string print formated") lit les variables qu'on lui passe et les retourne, de façon formatée, sur une chaîne de caractère. S'il y a davantage d'éléments parmi les variables que de "spécifications de conversion" dans le format, le format sera "réutilisé" autant de fois que nécessaire.
string = sprintf(format, variable(s)... )
- La variable string (de type chaîne) reçoit donc la(les) variable(s) formatée(s) à l'aide du format spécifié. Si, parmi les variables, il y a une ou plusieurs matrice(s), les éléments sont envoyés colonne après colonne.
Ex:
• nb=4 ; prix=10 ; disp(sprintf('Nombre d''articles: %04u Montant: %0.2f Frs', nb, nb*prix))
ou, plus simplement: fprintf('Nombre d''articles: %04u Montant: %0.2f Frs \n', nb, nb*prix)
=> affiche: Nombre d'articles: 0004 Montant: 40.00 Frs
La fonction mat2str ("matrix to string") décrite ci-dessous (et voir chapitre "chaînes de caractères") est intéressante pour sauvegarder de façon compacte sur fichier des matrices sous forme texte (en combinaison avec fprintf) que l'on pourra relire sous MATLAB/Octave (lecture-fichier avec fscanf, puis affectation a une variable avec eval).
- string = mat2str(mat {,n})
- Convertit la matrice mat en une chaîne de caractère string incluant les crochets [ ] et qui serait dont "évaluable" avec la fonction eval. L'argument n permet de définir la précision (nombre de chiffres).
Ex:
• str_mat = mat2str(eye(3,3)) produit la chaîne "[1 0 0;0 1 0;0 0 1]"
• et pour affecter ensuite les valeurs d'une telle chaîne à une matrice x, on ferait eval(['x=' str_mat])
Voir aussi les fonctions plus primitives int2str (conversion nombre entier->chaîne) et num2str (conversion nombre réel->chaîne).
7.8.4 Lecture/écriture formatée de fichiers
Lecture et écriture de fichiers
14:56 min |
Quel que soit le langage utilisé (MATLAB/Octave, Python, C, Java...), la grande majorité des programmes que l'on développe sont appelés à manipuler des données qui doivent être persistantes, c'est-à-dire stockées sous forme de fichiers sur disque. Bien souvent aussi ces programmes ne travaillent pas seuls, c'est-à-dire qu'ils utilisent des données produites par d'autres logiciels, ou fournissent des données à d'autres programmes.
Il existe fondamentalement 2 types de fichiers : les fichiers binaires et les fichiers texte :
• les fichiers binaires ne sont pas lisibles par un oeil humain, et leur contenu n'est souvent pas documenté, donc seul le programme qui les a créé peut les utiliser (exemple: les fichiers au format natifs manipulés par un tableur tel que LibreOffice Calc ou Microsoft Excel...)
• les fichiers-texte, quant à eux, sont directement lisibles par l'être humain et modifiables dans un éditeur ; et c'est ce type de fichier qui est le plus utilisé lorsqu'on échange des données entre différents programmes (exemple: le format CSV...)
Nous présentons dans cette vidéo la lecture et l'écriture de fichiers texte sous MATLAB/Octave, et utilisons pour cela les "formats", concept présenté dans la vidéo précédente.
|
Lecture de données numériques structurées
S'il s'agit de lire/écrire des fichiers au format texte contenant des données purement numériques et avec le même nombre de données dans chaque ligne du fichier (i.e. des tableaux de nombres), la technique la plus efficace consiste à utiliser les fonctions suivantes décrites plus en détail au chapitre "Sauvegarde et chargement de données numériques via des fichiers-texte" :
- données délimitées par un ou plusieurs espace et/ou tab :
lecture avec : var=load('file_name') ;
écriture avec : save -ascii {-double} file_name var
- données séparées par un délimiteur spécifié :
lecture avec : var=dlmread('file_name', délimiteur) ;
écriture avec : dlmwrite('file_name', var, délimiteur)
voir aussi les fonctions csvread et csvwrite
Lecture intégrale d'un fichier sur une chaîne
- string = fileread('file_name')
- Cette fonction lit d'une traite l'intégralité du fichier-texte nommé file_name et retourne son contenu sur un vecteur colonne string de type chaîne
Ex: Dans l'exemple ci-dessous, la première instruction "avale" le fichier essai.txt sur le vecteur ligne de type chaîne fichier_entier (vecteur ligne, car on transpose le résultat de fileread). La seconde découpe ensuite cette chaîne selon les sauts de ligne (\n) de façon à charger le tableau cellulaire tabcel_lignes à raison d'une ligne du fichier par cellule.
fichier_entier = fileread('essai.txt')' ;
tabcel_lignes = strread(fichier_entier, '%s', 'delimiter', '\n') ;
- [status, string] = dos('type file_name')
[status, string] = unix('cat file_name')
- Ces instructions lisent également l'intégralité du fichier-texte nommé file_name, mais le retournent sur un vecteur ligne string de type chaîne. On utilisera la première forme sous Windows, et la seconde sous Linux ou macOS.
Fonction textread
- [vec1, vec2, vec3 ...] = textread(file_name, format {,n})
- (Analogue à strtread présenté plus haut, mais agissant dans ce cas sur un fichier entier).
Fonction simple et efficace de lecture d'un fichier-texte nommé file_name dont l'ensemble des données répond à un format homogène. Les données peuvent être délimitées par un ou plusieurs espace, tab, voire même saut(s) de ligne newline. La lecture s'effectue ainsi en "format libre" (comme avec sscanf et fscanf).
• Le vecteur-colonne vec1 recevra la 1ère "colonne" du fichier, le vecteur vec2 recevra la 2e colonne, vec3 la 3e, et ainsi de suite... La lecture s'effectue jusqu'à la fin du fichier, à moins que l'on spécifie le nombre n de fois que le format doit être réutilisé.
• Le nombre de variables vec1 vec2 vec3... et leurs types respectifs découlent directement du format
• Si vec-i réceptionne des chaînes de caractères, il sera de type "tableau cellulaire", en fait vecteur-colonne cellulaire
Les "spécifications de conversion" de format %f, %s, %u et %d peuvent être utilisées avec textread sous MATLAB et Octave.
Sous Octave seulement on peut en outre utiliser les spécifications %o et %x.
Sous MATLAB, les spécifications %u et %d génèrent un vecteur réel double précision. Mais ATTENTION, sous Octave elles génèrent un vecteur entier 32 bits !
Sous MATLAB seulement on peut encore utiliser :
%[...] : lit la plus longue chaîne contenant les caractères énumérés entre [ ]
%[^...] : lit la plus longue chaîne non vide contenant les caractèrens non énumérés entre [ ]
Ex:
Soit le fichier-texte de données ventes.txt suivant :
10001 Dupond Livres 12 23.50
10002 Durand Classeurs 15 3.95
10003 Muller DVDs 5 32.00
10004 Smith Stylos 65 2.55
10005 Rochat CDs 25 15.50
10006 Leblanc Crayons 100 0.60
10007 Lenoir Gommes 70 2.00
et le script MATLAB/Octave suivant :
[No_client, Nom, Article, Nb_articles, Prix_unit] = textread('ventes.txt', '%f %s %s %f %f') ;
Montant = Nb_articles .* Prix_unit ;
disp(' Client [No ] Nb Articles Prix unit. Montant ')
disp(' --------- ------- ----- --------- ----------- ------------')
format = ' %10s [%d] %5d %-10s %8.2f Frs %8.2f Frs\n' ;
for no=1:1:length(No_client)
fprintf(format, Nom{no}, No_client(no), Nb_articles(no), Article{no}, Prix_unit(no), Montant(no) ) ;
end
fprintf('\n\n TOTAL %8.2f Frs \n', sum(Montant) )
Attention : bien noter, ci-dessus, les accolades pour désigner éléments de Nom{} et de Article{}. Ce sont des "tableaux cellulaires" dont on pourrait aussi récupérer les éléments, sous forme de chaîne, avec : char(Nom(no)), char(Article(no)).
L'exécution de ce script: lit le fichier, calcule les montants, et affiche ce qui suit :
Client [No ] Nb Articles Prix unit. Montant
--------- ------- ----- --------- ----------- ------------
Dupond [10001] 12 Livres 23.50 Frs 282.00 Frs
Durand [10002] 15 Classeurs 3.95 Frs 59.25 Frs
Muller [10003] 5 DVDs 32.00 Frs 160.00 Frs
Smith [10004] 65 Stylos 2.55 Frs 165.75 Frs
Rochat [10005] 25 CDs 15.50 Frs 387.50 Frs
Leblanc [10006] 100 Crayons 0.60 Frs 60.00 Frs
Lenoir [10007] 70 Gommes 2.00 Frs 140.00 Frs
TOTAL 1254.50 Frs
Fonctions classiques de manipulation de fichiers (de type ANSI C)
file_id = fopen(file_name, mode)
[file_id, message_err ] = fopen(file_name, mode)
- Ouvre le fichier de nom défini par la variable/chaîne file_name, et retourne l'identifiant file_id qui permettra de le manipuler par les fonctions décrites plus bas.
Si file_name ne définit qu'un nom de fichier, celui-ci est recherché dans le répertoire courant. Si l'on veut ouvrir un fichier se trouvant dans un autre répertoire, il faut bien entendu faire précéder le nom du fichier de son chemin d'accès (path) relatif ou absolu. S'agissant du séparateur de répertoire, bien que celui-ci soit communément \ sous Windows, nous vous conseillons de toujours utiliser / (accepté par MATBAL/Octave sous Windows) pour que vos scripts/fonctions soient portables, c'est-à-dire utilisables dans les 3 mondes Windows, macOS et GNU/Linux.
Le mode d'accès au fichier sera défini par l'une des chaînes suivantes :
• 'rt' ou 'rb' ou 'r' : lecture seule (read)
• 'wt' ou 'wb' ou 'w' : écriture (write), avec création du fichier si nécessaire, ou écrasement s'il existe
• 'at' ou 'ab' ou 'a' : ajout à la fin du fichier (append), avec création du fichier si nécessaire
• 'rt+' ou 'rb+' ou 'r+' : lecture et écriture, sans création
• 'wt+' ou 'wb+' ou 'w+' : lecture et écriture avec écrasement du contenu
• 'at+' ou 'ab+' ou 'a+' : lecture et ajout à la fin du fichier, avec création du fichier si nécessaire
Le fait de spécifier t ou b ou aucun de ces deux caractères dans le mode a la signification suivante :
• t : ouverture en mode "texte"
• b ou rien : ouverture en mode "binaire" (mode par défaut)
Sous Windows ou macOS, il est important d'utiliser le mode d'ouverture "texte" si l'on veut que les fins de ligne soient correctement interprétées !
En cas d'échec (fichier inexistant en lecture, protégé en écriture, etc...), file_id reçoit la valeur "-1". On peut aussi récupérer un message d'erreur sous forme de texte explicite sur message_err
Identifiants prédéfinis (toujours disponibles, correspondant à des canaux n'ayant pas besoin d'être "ouverts") :
• 1 ou
stdout : correspond à la sortie standard (standard output, c'est-à-dire fenêtre console MATLAB/Octave), pouvant donc être utilisé pour l'affichage à l'écran
• 2 ou
stderr : correspond au canal erreur standard (standard error, par défaut aussi la fenêtre console), pouvant aussi être utilisé par le programmeur pour l'affichage d'erreurs
• 0 ou
stdin : correspond à l'entrée standard (standard input, c'est-à-dire saisie au clavier depuis console MATLAB/Octave).
Pour offrir à l'utilisateur la possibilité de désigner le nom et emplacement du fichier à ouvrir/créer à l'aide d'une fenêtre de dialogue classique (interface utilisateur graphique), on se référera aux fonctions uigetfile (lecture de fichier) et uiputfile (écriture de fichier) présentées au chapitre "Fenêtres de sélection de fichiers". Pour sélectionner un répertoire, on utilisera la fonction uigetdir.
- [file_name, mode] = fopen(file_id)
- Pour un fichier déjà ouvert avec l'identifiant file_id spécifié, retourne son nom file_name et le mode d'accès.
freport()
- Affiche la liste de tous les fichiers ouverts, avec file_id, mode et file_name.
On voit que les canaux stdin, stdout et stderr sont toujours pré-ouverts
{status=} fclose(file_id)
fclose('all')
- Referme le fichier ayant l'identifiant file_id (respectivement tous les fichiers ouverts). Le status retourné est "0" en cas de succès, et "-1" en cas d'échec.
A la fin de l'exécution d'un script ayant ouvert des fichiers, tous ceux-ci sont automatiquement refermés, même en l'absence de fclose.
variable = fscanf(file_id, format {,size})
[variable, count] = fscanf(file_id, format {,size})
- Fonction de lecture formatée ("file scan formated") du fichier-texte spécifié par son identifiant file_id. Fonctionne de façon analogue à la fonction sscanf vue plus haut (à laquelle on renvoie le lecteur pour davantage de précision), sauf qu'on lit ici sur un fichier et non pas sur une chaîne de caractères.
Remarque importante : en l'absence du paramètre size (décrit plus haut sous sscanf), fscanf tente de lire (avaler, "slurp") l'intégralité du fichier (et non pas seulement de la ligne courante comme fgetl ou fgets).
Ex:
Soit le fichier-texte suivant :
10001 Dupond
Livres 12 23.50
10002 Durand
Classeurs 15 3.95
La lecture des données de ce fichier avec fscanf s'effectuerait de la façon suivante :
file_id = fopen('fichier.txt', 'rt') ;
no = 1 ;
while ~ feof(file_id)
No_client(no) = fscanf(file_id,'%u',1) ;
Nom{no,1} = fscanf(file_id,'%s',1) ;
Article{no,1} = fscanf(file_id,'%s',1) ;
Nb_articles(no) = fscanf(file_id,'%u',1) ;
Prix_unit(no) = fscanf(file_id,'%f',1) ;
no = no + 1 ;
end
status = fclose(file_id) ;
[variable, count] = scanf(format {,size})
- Fonction spécifiquement Octave de lecture formatée sur l'entrée standard (donc au clavier, identifiant 0). Pour le reste, cette fonction est identique à fscanf.
- line = fgetl(file_id)
- Lecture, ligne par ligne ("file get line"), du fichier-texte spécifié par l'identifiant file_id. A chaque appel de cette fonction on récupère, sur la variable line de type chaîne, la ligne suivante du fichier (sans le caractère de fin de ligne).
- string = fgets(file_id {,nb_car})
- Lecture, par groupe de nb_car ("file get string"), du fichier-texte spécifié par l'identifiant file_id. En l'absence du paramètre nb_car, on récupère, sur la variable string, la ligne courante inclu le(s) caractère(s) de fin de ligne (<cr> <lf> dans le cas de Windows).
{count=} fskipl(file_id ,nb_lignes)
- Avance dans le fichier file_id en sautant nb_lignes ("file skip lines"). Retourne le nombre count de lignes sautées (qui peut être différent de nb_lignes si l'on était près de la fin du fichier).
{status=} feof(file_id)
- Test si l'on a atteint la fin du fichier spécifié par l'identifiant file_id : retourne "1" si c'est le cas, "0" si non. Utile pour implémenter une boucle de lecture d'un fichier.
Ex: voir l'usage de cette fonction dans l'exemple fscanf ci-dessus
- frewind(file_id)
- Se (re)positionne au début du fichier spécifié par l'identifiant file_id.
Pour un positionnement précis à l'intérieur d'un fichier, voyez les fonctions :
• fseek(file_id, offset, origin) : positionnement offset octets après origin
• position = ftell(file_id) : retourne la position courante dans le fichier
{count=} fprintf(file_id, format, variable(s)... )
- Fonction d'écriture formatée ("file print formated") sur un fichier-texte spécifié par l'identifiant file_id, et retourne le nombre count de caractères écrits. Fonctionne de façon analogue à la fonction sprintf vue plus haut (à laquelle on renvoie le lecteur pour davantage de précision), sauf qu'on écrit ici sur un fichier et non pas sur une chaîne de caractères.
{count=} fprintf(format, variable(s)... )
{count=} printf(format, variable(s)... )
- Utiliser fprintf en omettant le file_id (qui est est identique à utiliser le file_id "1" représentant la sortie standard) ou
printf (spécifique à Octave), provoque une écriture/affichage à l'écran (i.e. dans la fenêtre de commande MATLAB/Octave).
Ex: affichage de la fonction y=exp(x) sous forme de tableau avec :
x=0:0.05:1 ; exponentiel=[x;exp(x)] ; fprintf(' %4.2f %12.8f \n',exponentiel)
{status=} fflush(file_id)
- Pour garantir de bonnes performances, Octave utilise un mécanisme de mémoire tampon (buffer) pour les opérations d'écriture. Les écritures ainsi en attente sont périodiquement déversées sur le canal de sortie, au plus tard lorsque l'on fait un fclose. Avec la fonction fflush, on force le vidage (flush) du buffer sur le canal de sortie.
Dans des scripts interactifs ou affichant des résultats en temps réel dans la fenêtre console, il peut être utile dans certaines situations de flusher la sortie standard avec
fflush(stdout). Voyez aussi la fonction
page_output_immediately pour carrément désactiver le buffering.
Fonction textscan
Cette fonction est capable à la fois de lire un fichier ou de décoder une chaîne.
- vec_cel = textscan(file_id, format {,n})
- Lecture formatée d'un fichier-texte spécifié par l'identifiant file_id (voir ci-dessus) et dont l'ensemble des données répond à un format homogène.
La lecture s'effectue jusqu'à la fin du fichier, à moins que l'on spécifie le nombre n de fois que le format doit être réutilisé.
Dans le format, on peut notamment utiliser \r, \n ou \r\n pour matcher respectivement les caractères de fin de lignes "carriage-return" (macOS), "line-feed" (Unix/Linux) ou "carriage-return + line-feed (Windows)
La fonction retourne un vecteur-ligne cellulaire vec_cel dont la longueur correspond au nombre de spécifications du format. Chaque cellule contient un vecteur-colonne de type correspondant à la spécification de format correspondante.
Ex: on peut lire le fichier ventes.txt ci-dessus avec :
file_id = fopen('ventes.txt', 'rt');
vec_cel = textscan(file_id, '%u %s %s %u %f');
fclose(file_id);
et l'on récupère alors dans vec_cel{1} le vecteur de nombre des No, dans vec_cel{2} le vecteur cellulaire des Clients, dans vec_cel{3} le vecteur cellulaire des Articles, etc...
- vec_cel = textscan(string, format {,n})
- Opère ici sur la chaîne string
7.8.5 Fonctions de lecture/écriture de fichiers spécifiques/binaires
- fread(...) et
fwrite(...)
- Fonctions de lecture/écriture binaire (non formatée) de fichiers, pour un stockage plus compact qu'avec des fichiers en format texte. Voyez l'aide pour davantage d'information.
- xlsread(...) et
xlswrite(...)
- Fonctions de lecture/écriture de classeurs Excel (binaire). Voyez l'aide pour davantage d'information.
odsread(...) et
odswrite(...)
- Fonctions de lecture/écriture de classeurs OpenOffice/LibreOffice (binaire). Voyez l'aide pour davantage d'information.
7.9 "Publier" un code MATLAB/Octave
7.9.1 La fonctionnalité "publish"
Se rapprochant du concept de notebook, la fonction publish permet d'exécuter un script MATLAB/Octave en produisant un rapport comportant 3 parties :
- le code source du script
- les résultats d'exécution du script
- les éventuels graphiques produits par le script
- publish('script{.m}' {, 'format','fmt_rapport', 'imageFormat','fmt_images', 'showCode',true|false, 'evalCode',true|false } )
- Le rapport est créé dans le sous-répertoire nommé "html", mais on peut désigner un autre répertoire avec 'outputDir', 'path'
Les paramètres optionnels sont :
- format : valeurs possibles : html, latex,
doc, pdf (ce dernier nécessitant pdflatex c-à-d. texlive sous Linux) ;
les valeurs par défaut sont : html sous MATLAB, latex sous Octave
- imageFormat : valeurs possibles : png, jpg, pdf ; les valeurs par défaut sont :
- si format html : png
- si format pdf : jpg
- showCode : affichage du code dans le rapport, par défaut true
- evalCode : exécution du code, par défaut true ; si mis à false, le code n'est pas exécuté et aucun graphique n'est bien évidement produit
- grabcode('URL')
- Récupère le code qui a été publié en HTML à l'URL spécifiée avec publish
Ex: Soit le script ci-dessous nommé code.m :
% Exécution/publish de ce code sous Octave
% Données
x= 4*rand(1,50) -2 % val. aléatoires entre -2 et +2
y= 4*rand(1,50) -2 % val. aléatoires entre -2 et +2
z= x.*exp(-x.^2 - y.^2) % Z en ces points
% Premier graphique (double, de type multiple plots)
figure(1)
subplot(1,2,1)
plot(x,y,'o')
title('semis de points aleatoire')
grid('on')
axis([-2 2 -2 2])
axis('equal')
subplot(1,2,2)
tri_indices= delaunay(x, y); % form. triangles => matr. indices
trisurf(tri_indices, x, y, z) % affichage triangles
title('z = x * exp(-x^2 - y^2) % sur ces points')
zlim([-0.5 0.5])
set(gca,'ztick',-0.5:0.1:0.5)
view(-30,10)
% Second graphique (simple)
figure(2)
xi = -2:0.2:2 ;
yi = xi';
[XI,YI,ZI] = griddata(x,y,z,xi,yi,'nearest'); % interp. grille
surfc(XI,YI,ZI)
title('interpolation sur grille')
|
Son exécution avec la commande :
publish('code.m', 'format','html', 'imageFormat','png')
produit le rapport accessible sous ce lien
|
7.9.2 Les noteboks Jupyter
Il s'agit d'une technique moderne et extrêmement élégante, que nous présentons dans un support de cours indépendant, permettant de mixer, dans un document vivant, du texte, du code et des graphiques.
7.10 Debugging, profiling et optimisation de codes MATLAB/Octave
7.10.1 Debugging
Lorsqu'il s'agit de débuguer un script ou une fonction qui pose problème, la première idée qui vient à l'esprit est de parsemer le code d'instructions d'affichages intermédiaires. Plutôt que de faire des disp, on peut alors avantageusement utiliser la fonction warning présentée plus haut, celle-ci permettant en une seule instruction d'afficher du texte et des variables ainsi que de désactiver/réactiver aisément l'affichage de ces warnings. Mais il existe des fonctionnalités de debugging spécifiques présentées ci-après.
Commandes de debugging simples
- echo on | off
echo on all | off all
- • Active (on) ou désactive (off, c'est le cas par défaut) l'affichage/écho de toutes les commandes exécutées par les scripts
• Active (on all) ou désactive (off all, c'est le cas par défaut) l'affichage/écho de toutes les commandes exécutées par les fonctions
- keyboard
keyboard('prompt ')
- Placée à l'intérieur d'un M-file, cette commande invoque le mode de debugging "keyboard" de MATLAB/Octave : l'exécution du script est suspendue, et un prompt spécifique s'affiche (
K>>, respectivement
debug> ou le prompt spécifié). L'utilisateur peut alors travailler normalement en mode interactif dans MATLAB/Octave (visualiser ou changer des variables, passer des commandes...). Puis il a le choix de :
• continuer l'exécution du script en frappant en toutes lettres la commande return
• ou avorter la suite du script en frappant la commande dbquit sous MATLAB ou Octave
Ce mode "keyboard" permet ainsi d'analyser manuellement certaines variables en cours de déroulement d'un script.
Debugging en mode graphique
Les éditeurs/débuggers intégrés de MATLAB et de Octave GUI (depuis Octave 3.8) permettent de placer visuellement des breakpoints (points d'arrêt) dans vos scripts/fonctions, puis d'exécuter ceux-ci en mode step-by-step (instructions pas à pas). L'intérêt réside dans le fait que lorsque l'exécution est suspendue sur un breakpoint ou après un step, vous pouvez, depuis la fenêtre de console à la suite du prompt
K>> ou
debug>, passer interactivement toute instruction MATLAB/Octave (p.ex. vérifier la valeur d'une variable, la modifier...), puis poursuivre l'exécution. Par rapport à la méthode keyboard ci-dessus, l'avantage ici est qu'on ne "pollue" pas notre code d'instructions provisoires.
Les boutons décrits ci-dessous se cachent dans l'onglet EDITOR du bandeau MATLAB, et dans palette d'outils de l'éditeur intégré de Octave GUI.
A) Mise en place de breakpoints :
- clic dans la marge gauche de l'éditeur,
F12,
,
Toggle Breakpoint : place/supprime un breakpoint sur la ligne courante, symbolisé par un disque rouge ● (identique à dbstop/dbclear)
Next Breakpoint et
Previous Breakpoint : déplace le curseur d'édition au beakpoint suivant/précédent du fichier
,
Remove All Breakpoints : supprime tous les breakpoints qui ont été définis
B) Puis exécution step-by-step :
- Save (File) and Run ou F5 : débute l'exécution du script et suspend l'exécution au premier breakpoint rencontré, la console affichant alors le prompt
K>> ou
debug>
- dans la marge gauche une flèche (verte ➡ sous MATLAB, jaune ➡ sous Octave) pointe sur la prochaine instruction qui sera exécutée
- Step ou F10 : exécute la ligne courante et se suspend au début de la ligne suivante (identique à dbstep) ; si la ligne courante est un appel de fonction, exécute le reste du code de la fonction d'une traite
- Step In ou F11 : si la ligne courante est un appel de fonction, exécute aussi les instructions de celle-ci en mode step-by-step (identique à dbstep in)
- Step Out ou maj-F11 : lorsque l'on est en mode step-by-step dans une fonction, poursuit l'exécution de celle-ci jusqu'à la sortie de la fonction (identique à dbstep out)
- Continue ou F5 : poursuit l'exécution jusqu'au breakpoint suivant (identique à return)
Quit Debugging,
Exit Debug Mode ou maj-F5 : interrompt définitivement l'exécution (identique à dbquit)
Fonctions de debugging
Les fonctions ci-dessous sont implicitement appelées lorsque l'on fait du debugging en mode graphique (chapitre précédent), mais vous pouvez aussi les utiliser depuis la console MATLAB/Octave.
A) Mise en place de breakpoints :
dbstop in script|fonction at no
dbstop('script|fonction', no {, no, no...} ) ou
dbstop('script|fonction', vecteur_de_nos)
- Défini (ajoute), lors de l'exécution ultérieure du script ou de la fonction indiquée, des breakpoints au début des lignes de no spécifié
L'avantage, par rapport à l'instruction keyboard décrite précédemment, est qu'ici on ne "pollue" pas notre code, les breakpoints étant définis interactivement avant l'exécution
dbclear in script|fonction at no respectivement
dbclear in script|fonction
dbclear('script|fonction', no {, no, no...} ) respectivement
dbclear('script|fonction')
- Supprime, dans le script ou la fonction indiquée, les breakpoints précédemment définis aux lignes de no spécifié.
Dans sa seconde forme, cette instruction supprime tous les breakpoints relatif au script/fonction spécifié.
- struct =
dbstatus {script|fonction}
struct =
dbstatus {('script|fonction')}
- Affiche (ou retourne sur une structure) les vecteurs contenant les nos de ligne sur lesquels sont couramment définis des breakpoints. Si on ne précise pas de script/fonction, retourne les breakpoints de tous les scripts/fonctions
B) Puis exécution en mode step-by-step à l'aide des fonctions MATLAB/Octave suivantes :
- l'exécution s'arrête automatiquement au premier beakpoint spécifié, et la console affiche le prompt
K>> ou
debug>
- dbstep {n} : exécution de la (des n) ligne(s) suivante(s) du script/fonction
dbstep in : si la ligne courante est un appel de fonction, passe à l'exécution de celle-ci en mode step-by-step
dbstep out : si l'on est en mode step-by-step dans une fonction, poursuit l'exécution de celle-ci jusqu'à la sortie de la fonction
- return (commande passée en toutes lettres) : continuation de l'exécution jusqu'au breakpoint suivant
- dbcont : achève l'exécution en ignorant les breakpoints qui suivent
enter : la commande de debugging précédemment passée est répétée ; sous MATLAB, faire curseur-hautenter
dbwhere : affichage du numéro (et contenu) de la ligne courante du script/fonction
- dbquit : avorte l'exécution du script/fonction
S'agissant d'exécution de scripts/fonctions imbriqués, on peut encore utiliser les commandes de debugging dbup, dbdown, dbstack...
7.10.2 Profiling
Sous le terme de "profiling" on entend l'analyse des performances d'un programme (script, fonctions) afin d'identifier les parties de code qui pourraient être optimisées dans le but d'améliorer les performances globales du programme. Les outils de profiling permettent ainsi de comptabiliser de façon fine (au niveau script, fonctions et même instructions) le temps consommé lors de l'exécution du programme, puis de présenter les résultats de cette analyse sous forme de tableaux et d'explorer ces données.
Pour déterminer le temps CPU utilisé dans certaines parties de vos scripts ou fonctions, une alternative aux outils de profiling ci-dessous serait d'ajouter manuellement dans votre code des fonctions de "timing" (chronométrage du temps consommé) décrites au chapitre "Dates et temps", sous-chapitre "Fonctions de timing et de pause".
Commandes liées au profiling
Enclencher/déclencher le processus de profiling :
- profile on ou profile('on')
profile resume ou profile('resume')
- Démarre le profiling, c'est-à-dire la comptabilisation du temps consommé :
• avec on : efface les données de profiling précédemment collectées
• avec resume : reprend le profiling qui a été précédemment suspendu avec profile off, sans effacer données collectées
- profile off ou profile('off')
- Interrompt ou suspend la collecte de données de profiling, afin d'en exploiter les données
- stats_struct = profile('status')
- Retourne la structure stats_struct dans laquelle le champ ProfilerStatus indique si le profiling est couramment activé (on) ou stoppé (off)
- prof_struct = profile('info')
- Récupère sur la structure prof_struct les données de profiling collectées
- profile clear
- Efface toutes les données de profiling collectées
Explorer sous MATLAB les données de profiling :
profile viewer ou profile('viewer') ou profview
- Interrompt le profiling (effectue implicitement un profile off) et ouvre l'explorateur de profiling (le "Profiler"). Il s'agit d'une fenêtre MATLAB spécifique dans laquelle les données de profiling sont hiérarchiquement présentées sous forme HTML à l'aide de liens hyper-textes.
Explorer sous Octave les données de profiling :
profexport(path, {nom,} prof_struct) (depuis Octave 4.2)
- Exporte les données de profiling sous forme d'un ensemble de fichiers HTML dans le répertoire path. On peut alors explorer ces données confortablement en chargeant le fichier path/index.html dans un navigateur web et en suivant les liens hyper-textes.
profshow(prof_struct {, N })
- Affiche sous forme de tableau, dans l'ordre descendant du temps consommé, les données de profiling prof_struct. On peut spécifier le nombre N de fonctions/instructions affichées. Si N n'est pas spécifié, ce sera par défaut 20.
profexplore(prof_struct)
- Explore interactivement, dans la fenêtre de commande Octave, les données hiérarchiques de profiling prof_struct
• help : aide en-ligne sur les commandes disponibles
• opt : descend d'un niveau dans l'option opt spécifiée
• up {nb_niv} : remonte d'un niveau, ou de nb_niv niveaux ; si l'on est au premier niveau, retourne au prompt Octave
• exit : interrompt cette exploration interactive
Illustration par un exemple
Soit le script et les 2 fonctions suivants :
Script demo_profiling.m :
%DEMO_PROFILING Script illustrant, par
% profiling, l'utilité de vectoriser son code !
t0=cputime; % compteur temps CPU consommé
% Génération matrice aléatoire
nb_l = 1000;
nb_c = 500;
v_min = -10 ;
v_max = 30 ;
mat = matrice_alea(nb_l, nb_c, v_min, v_max) ;
% Extraction de certains éléments de mat
vmin = 0 ;
vmax = 20 ;
vec = extrait_matrice(mat, vmin, vmax) ;
% Calcul de la moyenne des éléments extraits
if CODE_VECTORISE % Code vectorisé :-)
moyenne_vect = mean(vec) ;
else % Code non vectorisé :-(
somme_elem = 0 ;
for indice=1:length(vec)
somme_elem = somme_elem + vec(indice) ;
end
moyenne_vect = somme_elem / length(vec) ;
end
% Affichages...
moyenne_vect
duree_calcul=cputime-t0
|
Fonction matrice_alea.m :
function [matrice]=matrice_alea(nb_l,nb_c,v_min,v_max)
% MATRICE_ALEA(NB_L, NB_C, V_MIN, V_MAX)
% Generation matrice de dimension (NB_L, NB_C)
% de nb aleatoires compris entre V_MIN et V_MAX
global CODE_VECTORISE
v_range = v_max - v_min ;
if CODE_VECTORISE % Code vectorisé :-)
matrice = (rand(nb_l, nb_c) * v_range) + v_min ;
else % Code non vectorisé :-(
for lig=1:nb_l
for col=1:nb_c
matrice(lig,col) = (rand()*v_range) + v_min ;
end
end
end
return
Fonction extrait_matrice.m :
function [vecteur] = extrait_matrice(mat, vmin, vmax)
% EXTRAIT_MATRICE(MAT, VMIN, VMAX)
% Extrait de la matrice MAT, parcourue col. apres
% colonne, tous les elements dont la val. est
% comprise entre VMIN et VMAX, et les retourne
% sur un vecteur colonne
global CODE_VECTORISE
if CODE_VECTORISE % Code vectorisé (index. logique) :-)
vecteur=mat(mat>=vmin & mat<=vmax);
else % Code non vectorisé :-(
indice = 1;
for col=1:size(mat,2)
for lig=1:size(mat,1)
if (mat(lig,col) >= vmin) && (mat(lig,col) <= vmax)
vecteur(indice) = mat(lig,col) ;
indice = indice + 1 ;
end
end
end
vecteur = vecteur' ;
end
return
|
Vous constatez que, pour les besoins de l'exemple, ces codes comportent du code vectorisé (usage de fonctions vectorisées, indexation logique...) et du code non vectorisé (usage de boucles for/end). Nous avons délimité ces deux catégories de codes par des structures if/else/end s'appuyant sur une variable globale nommée CODE_VECTORISE.
Réalisons maintenant le profiling de ce code. En premier lieu, il faut rendre globale au niveau workspace et du script la variable CODE_VECTORISE (ceci est déjà fait dans les fonctions), avec l'instruction :
- global CODE_VECTORISE % il est important de faire cette déclaration avant d'affecter une valeur à cette variable !
Les durées d'exécution indiquées ci-dessous se rapportent à une machine Intel Pentium Core 2 Duo E6850 @ 3 GHz avec MATLAB R2012 et GNU Octave 3.6.2 MinGW.
Commençons par l'examen du code non vectorisé :
- CODE_VECTORISE=false; % on choisit donc d'utiliser ici le code non vectorisé
- profile on % démarrage du profiling
- profile status % contrôle du statut du profiling (il est bien à on)
- demo_profiling % exécution de notre script
- profile off % interruption de la collecte de données de profiling
- profile status % contrôle du statut du profiling (il est bien à off)
Puis sous MATLAB :
profile viewer % ouverture de l'explorateur de profiling
Constatations :
- le script s'est exécuté en 9 sec sous Linux/Ubuntu, et étonnamment en 1 sec sous Windows !
Ou sous Octave :
prof_struct=profile('info'); % récupération des données de profiling
profexport('profiling', prof_struct) % génération, dans le sous-répertoire profiling, d'un rapport que l'on peut explorer interactivement
depuis un navigateur web en chargeant le fichier index.html
ou profshow(prof_struct) % affichage tabulaire des 20 plus importants données (en temps) de profiling
puis profexplore(prof_struct) % exploration interactive des données de profiling ; "descendre" ensuite avec 1 dans "demo_profiling", puis dans
les fonctions "matrice_alea" et "extrait_matrice" ...
Constatations :
- le script s'est exécuté en 14 sec sous Linux, et en 23 sec sous Windows
- sous Windows p.ex. 12 sec sont consommées par la fonction "matrice_alea" (dont 4.5 sec par la fonction "rand"), 9 sec par la fonction "extrait_matrice"
- on constate notamment que la fonction "rand" est appelée 500'000x
- on voit donc ce qu'il y a lieu d'optimiser...
Essayez de relancer ce script sans profiling. Vous constaterez qu'il s'exécute un peu plus rapidement, ce qui montre que le profiling lui-même consomme du temps CPU, et donc qu'il ne faut l'activer que si l'objectif est bien de collecter des données de performance !
Poursuivons maintenant avec l'examen du code vectorisé (remplacement des boucles par des fonctions vectorisées et l'indexation logique) :
- CODE_VECTORISE=true; % on choisit donc d'utiliser ici le code vectorisé
- profile on % démarrage du profiling
- demo_profiling % exécution de notre script
- profile off % interruption de la collecte de données de profiling
Puis sous MATLAB :
profile viewer % ouverture de l'explorateur de profiling
Constatations :
- le script s'est exécuté en 0.1 sec sous Linux/Ubuntu et sous Windows !
- on a donc gagné d'un facteur de plus de 100x par rapport à la version non vectorisée !
Ou sous Octave :
prof_struct=profile('info'); % récupération des données de profiling
profexport('profiling', prof_struct) % génération, dans le sous-répertoire profiling, d'un rapport que l'on peut explorer interactivement
depuis un navigateur web en chargeant le fichier index.html
ou profshow(prof_struct) % affichage tabulaire des 20 plus importants données (en temps) de profiling
puis profexplore(prof_struct) % exploration interactive des données de profiling (comme plus haut...)
Constatations :
- le script s'est exécuté en 0.1 sec sous Windows, et 0.05 sec sous Linux/Ubuntu !
- on a donc gagné d'un facteur de plus de 200x par rapport à la version non vectorisée !
7.10.3 Optimisation
Il existe de nombreuses techniques pour optimiser un code MATLAB/Octave en terme d'utilisation des ressources processeur et mémoire. Nous donnons ci-après quelques conseils de base. Notez cependant qu'il faut parfois faire la balance entre en optimisation et lisibilité du code.
Optimisation du temps de calcul (CPU)
Évitez autant que possible les boucles for, while... en utilisant massivement les capacités vectorisées de MATLAB/Octave (la plupart des fonctions admettant comme arguments des tableaux). Pensez aussi à l'"indexation logique".
Redimensionner dynamiquement un tableau dans une boucle peut être très coûteux en temps CPU. Il est plus efficace de pré-allouer l'espace du tableau avant d'entrer dans la boucle, quitte à libérer ensuite l'espace non utilisé. Considérons par exemple la boucle ci-dessous :
t0=cputime; for k=1:10000000, mat(k)=k; end; fprintf('%6.2f secondes\n', cputime-t0)
Le fait de pré-allouer l'espace de mat, en exécutant par exemple mat=zeros(1,10000000); ou mat(10000000)=1; avant ce code, accélérera celui-ci d'un facteur 40x sous MATLAB 8.3 et 5x sous Octave 3.8
- S'agissant de matrices particulières (symétriques, diagonales...), il existe des fonctions MATLAB/Octave spécifiques qui sont plus efficaces que les fonctions standards. Pensez aussi au stockage sparse de matrices creuses (voir ci-après).
Optimisation de l'utilisation mémoire (RAM)
Les nombres sont par défaut stockés en virgule flottante "double précision" (16 chiffres significatifs) occupant en mémoire 8 octets par nombre (64 bits). Si vous gérez des gros tableaux de nombres qui ne nécessitent pas cette précision, un gain de place important peut être obtenu en initialisant ces tableaux en virgule flottante "simple précision" (7 chiffres significatifs) occupant 4 octets par élément. S'il s'agit de nombre entiers, envisagez les types entiers 32 bits (plage de -2'147'483'648 à 2'147'483'647), 16 bits (de -32'768 à 32'767) ou 8 bits (-128 à 127). Voyez pour cela notre chapitre "Types de nombres".
Si vous manipulez des matrices comportant beaucoup d'éléments nuls (vous pouvez visualiser cela avec spy(matrice)), pensez à les stocker sous forme sparse (conversion avec sparse(matrice), et conversion inverse avec full(sparse)). Elles occuperont moins d'espace mémoire, et les opérations de calcul s'en trouveront accélérées (réduction du nombre d'opérations, celles portant sur les zéros n'étant pas effectuées).
Documentation © CC BY-SA 4.0 /
/ EPFL /
Rév. 13-01-2025
↵ Table des matières