d7.01.01 Développer une nouvelle commande#
Résumé :
Ce document vient en complément des présentations de la formation au développement. Il détaille les étapes nécessaires à la réalisation de l’exercice d’ajout d’une nouvelle commande.
On propose de créer une nouvelle commande qui permet la translation d’un maillage d’un vecteur 2D ou 3D.
L’objectif est de savoir :
écrire le catalogue de la commande ;
utiliser les routines d’interrogation des mots-clés depuis le fortran ;
utiliser des routines utilitaires du gestionnaire de mémoire Jeveux ;
extraire des informations d’une structure de données à l’aide des utilitaires existants ;
construire et tester son développement.
Remarque :
Les prérequis indispensables avant de commencer à développer est de connaître:
le Fortran,
le Python (y compris la Programmation Orientée Objet),
le gestionnaire de version Git,
l’interface GitLab.
Un squelette des fichiers nécessaires est fourni lors de la formation afin de guider les nouveaux développeurs.
Il est conseillé de commencer par la Compilation de la version de référence, puis les Fichiers des exercices.
Catalogue de la commande#
Voici le squelette à compléter du catalogue code_aster/Cata/Commands/modi_mail.py:
from ..Commons import *
from ..Language.DataStructure import *
from ..Language.Syntax import *
MODI_MAIL = OPER(
nom="MODI_MAIL",
op=190,
sd_prod=maillage_sdaster,
fr=tr("Modifier un maillage par translation"),
reentrant="o:MAILLAGE",
reuse=SIMP(statut="c", typ=CO),
MAILLAGE=...,
TRANSLATION=...,
INFO=SIMP(statut="f", typ="I", defaut=1, into=(1, 2)),
)
Description du catalogue#
Les imports des autres modules dans tout le package code_aster doivent être
faits en relatif (from ..Commons et non from code_aster.Cata.Commons).
import * est interdit sauf dans les catalogues de commandes.
nom doit être identique à ce qu’il y a à gauche du signe égal, en autres,
pour automatiser l’import des commandes. On note que ce catalogue sera automatiquement
importé sans autre ajout. Le nom du fichier est également cohérent (en minuscules).
op indique le numéro de la routine fortran qui est appelée.
Pour une nouvelle commande, choisir un numéro libre dans ex0000.F90.
sd_prod définit le type de l’objet en sortie.
Le catalogue est aussi utilisé par AsterStudy qui ne peut pas importer tout code_aster.
Il s’agit donc d’une DataStructure légère, définie dans code_aster/Cata/DataStructure.py,
et non de l’objet qui sera produit dans le fichier de commandes.
Ici, maillage_sdaster vs Mesh. AsterStudy manipule un objet maillage_sdaster
qui ne contient pas beaucoup plus que son type et le fichier de commandes manipule
de vrais objets Mesh.
La correspondance est faite ainsi:
maillage_sdaster.getType() == Mesh().getType() == "MAILLAGE_SDASTER"
reentrant indique que la commande est obligatoirement (“o”) « réentrante », c’est à dire
qu’elle modifie l’objet fourni au mot-clé MAILLAGE.
Si elle était réentrante selon les mots-clés fournis, on indiquerait “f” (facultatif)
ou “n” si elle ne l’est jamais.
Si elle peut être réentrante, il faut ajouter le mot-clé caché reuse, toujours défini ainsi.
Le mot-clé INFO est présent dans la majorité des commandes. Il vaut 1 (verbosité normale) ou 2 (impressions détaillées supplémentaires).
Mots-clés à ajouter#
MAILLAGE est un mot-clé simple. Il faut définir son caractère facultatif ou obligatoire, le type attendu.
Pour cet exercice, on souhaite que TRANSLATION soit un mot-clé facteur qui ne contient qu’un seul mot-clé simple VECTEUR. Ces mots-clés sont obligatoires. Il est plus naturel pour l’utilisateur de fournir 2 valeurs réelles pour définir le vecteur de translation en 2D et 3 en 3D.
Exécuteur de la commande#
Le terme « exécuteur » n’est pas très joli mais il représente la classe de base qui permet d’exécuter toutes les commandes en factorisant les tâches communes. Dans d’anciennes versions, on parlait de « superviseur » qui ajoutait une couche intermédiaire entre le fichier de commandes et la commande elle-même. Aujourd’hui, une commande est une fonction Python.
Le fichier code_aster/CodeCommands/modi_mail.py est prêt à l’emploi:
from ..Supervis import ExecuteCommand
class TransformMesh(ExecuteCommand):
"""Command that changes a :class:`~code_aster.Objects.Mesh`."""
command_name = "MODI_MAIL"
def create_result(self, keywords):
"""Initialize the result.
Arguments:
keywords (dict): Keywords arguments of user's keywords.
- def add_dependencies(self, keywords):
« « »Register input DataStructure objects as dependencies.
- Arguments:
keywords (dict): User’s keywords.
- def post_exec(self, keywords):
« « »Execute the command.
- Arguments:
keywords (dict): User’s keywords.
MODI_MAIL = TransformMesh.run
Description#
MODI_MAIL est la fonction Python de la commande. C’est la fonction exécutée dans le
fichier de commandes.
On voit qu’il s’agit de la méthode run de la classe TransformMesh qui
hérite de la classe de base est ExecuteCommand.
Ce mécanisme permet de mutualiser les tâches communes à toutes les commandes: vérification générale, mesure des temps, vérification de la syntaxe, affichage de la syntaxe, création du résultat, exécution proprement dite, traitements finaux.
Dans ce fichier, on particularise seulement le nécessaire.
command_name permet en autres d’associer automatiquement le catalogue de commande.
create_result initialise l’objet produit par la commande.
Dans notre cas, la commande modifie toujours l’objet fourni sous MAILLAGE.
La plupart du temps, on crée un nouvel objet soit vide, soit à partir des mots-clés.
Le contenu de cet objet sera complété par l’opérateur Fortran.
add_dependencies est ici surchargée et n’ajoute aucune dépendance à l’objet produit.
La méthode par défaut ajoute une dépendance vers chaque structure de données fournie
par les mots-clés.
Il faut veiller à ne pas ajouter de dépendance superflue pour ne pas encombrer la mémoire
d’objets inutiles.
L’ajout de dépendance de cette manière devrait disparaître quand tous les objets porteront
par nature (dans leur classe C++) des pointeurs vers les objets parents.
post_exec permet d’appeler des instructions après l’exécution de la partie fortran,
souvent pour compléter le contenu de l’objet C++ pour permettre l’accès depuis Python.
Pour que cette fonction MODI_MAIL soit accessible de le fichier de commandes comme
toutes les autres, elle doit être ajoutée dans code_aster/CodeCommands/__init__.py.
Ainsi quand l’utilisateur fait :
from code_aster.Commands import *
MODI_MAIL est disponible.
Écriture de l’opérateur Fortran#
Il s’agit de la routine Fortran principale de la commande, celle dont on a choisi
le nom lors de l’écriture du Catalogue de la commande.
Lors de l’ajout d’une nouvelle commande, il est nécessaire de déclarer l”op associé
dans la routine d’aiguillage ex0000.F90 (fourni).
Chaque routine fortran est complétée par un fichier d’entête à inclure dans chaque
appelant de manière à vérifier les arguments lors de la compilation.
Il s’agit ici de bibfor/include/asterfort/op0190.h.
Le squelette à compléter de la routine est bibfor/op/op0190.F90 :
subroutine op0190()
implicit none
#include "asterc/getfac.h"
#include "asterc/getres.h"
#include "asterfort/as_allocate.h"
#include "asterfort/as_deallocate.h"
#include "asterfort/assert.h"
#include "asterfort/dismoi.h"
#include "asterfort/getvid.h"
#include "asterfort/getvr8.h"
#include "asterfort/jedema.h"
#include "asterfort/jemarq.h"
#include "asterfort/jeveuo.h"
character(len=8) :: mesh, result
character(len=16) :: concep, nomcmd
character(len=19) :: chcoord
character(len=24) :: vect_coord
integer :: i, j, iret, dim, nbocc, nbnodes
real(kind=8), pointer :: vect(:) => null()
real(kind=8) :: transl(3)
real(kind=8), pointer :: coord(:) => null()
call jemarq()
! read the input mesh name: maillage_sdaster, see d6.03.01, §2.1.1
call getvid(..., nbret=iret)
ASSERT(iret == 1)
! read the mesh result (must be identical to the input), see d6.03.01, §2.1.5
call getres(...)
ASSERT(result == mesh)
! check that TRANSLATION exists, see d6.03.01, §2.1.6
call getfac('TRANSLATION', nbocc)
ASSERT(nbocc == 1)
! get the size of the translation vector for a dynamic allocation, see d6.03.01, §2.1.1
call getvr8(..., nbval=0, nbret=dim)
dim = -dim
ASSERT(2 <= dim .and. dim <= 3)
! allocate the vector of size 'dim'
AS_ALLOCATE(...)
! read the translation vector values
call getvr8(..., nbval=dim, vect=vect)
! name of the jeveux vector containing the coordinates of the mesh
! see d4.06.01 for the COORDO object and d4.06.05 for its VALE vector
chcoord = ...
vect_coord = ...
! get the address and the size of this vector
call jeveuo(...)
call dismoi(...)
! loop on the nodes
...
call jedema()
end subroutine
Description détaillé#
Voir les Règles de programmation.
On débute par l’inclusion des fichiers d’interface, puis la déclaration des
variables nécessaires.
Quand la routine a des arguments, on les déclare en premier. La déclaration doit
être identique à celle du fichier d’interface.
On doit préciser si l’argument est en entrée (integer, intent(in) :: size),
en sortie (real(kind=8), intent(out) :: residual) ou modifié inout.
Un argument peut être optionnel et doit être déclaré comme tel (optional).
Toute routine utilisant des objets Jeveux doivent être encadrée par
call jemarq()/call jedema(). Ceci permet de libérer automatiquement et
au bon moment les objets non utilisés.
Il est recommandé de lire les valeurs des mots-clés dans un endroit unique,
pas au fond de la programmation, éventuellement dans une routine dédiée à la commande
si celle-ci est conséquente.
On utilise les interfaces entre Fortran et le fichier de commandes : routines getvxx,
getres, getfac…
ASSERT permet de vérifier qu’il n’y a pas d’erreur de programmation.
Par exemple, on attend un maillage et un seul. Cela est normalement imposé par le
catalogue de commande. Si iret est différent de 1, c’est une erreur de programmation,
pas d’utilisation.
AS_ALLOCATE permet de faire de l’allocation dynamique (dont la taille n’est pas fixée).
C’est simplement allocate de Fortran qui est enrobé pour détecter l’oubli de la libération.
Pour accéder aux valeurs des coordonnées, il faut trouver dans la documentation le nom de l’objet du maillage qui les contient, et dans cet objet, le nom du vecteur qui contient les valeurs.
On récupère l’adresse du vecteur avec jeveuo (chercher un exemple d’utilisation
dans le code).
On appelle l’utilitaire dismoi pour récupérer le nombre de noeuds du maillage
(chercher la documentation de cet utilitaire).
Complétez le squelette et testez votre code : Construire et tester.
Compiler et tester#
Compilation de la version de référence#
L’ensemble des opérations de construction sont nécessairement réalisées dans un shell du conteneur contenant la dernière version des prérequis.
Il est recommandé de vérifier que la compilation est correcte avec la version de référence. Pour cela, il suffit de faire :
cd $HOME/dev/codeaster/src
git checkout main
git pull
make bootstrap
On se place sur la branche main, on la met à jour avec le dépôt distant.
make bootstrap est un raccourci qui fait ./configure, make (identique à make install)
et make doc.
Ceci est équivalent aux commandes ./waf configure, ./waf install et ./waf doc.
Il suffit de faire export BUILD=debug avant de lancer make bootstrap
pour construire une version avec les symboles de debug.
Il est conseillé de lancer install_env (ou ../devtools/bin/install_env)
depuis src pour terminer l’installation des dépôts et installer les
hooks automatiquement.
Fichiers des exercices#
Les fichiers préremplis sont fournis dans une branche nommée tp-dev-cmd
du dépôt Git. Pour récupérer cette branche (et la renommer avec vos initiales
xy pour simuler une future demande d’intégration):
Le nom différent utilisé pour la branche locale permet de distinguer les différents développeurs si on fait des exercices sur GitLab par exemple.
Pour l’exercice d7.01.02 Développer une nouvelle macro-commande, utiliser le nom de branche tp-dev-macro
et xy-dev-macro pour la branche locale.
cd $HOME/dev/codeaster/src
git fetch origin tp-dev-cmd:xy-dev-cmd
git checkout xy-dev-cmd
Construire et tester#
Il n’est pas nécessaire de faire la configuration à chaque fois (uniquement quand on récupère des révisions du dépôt distant).
Il suffit de faire :
cd $HOME/dev/codeaster/src
make
Et pour lancer un cas-test :
make <nom-du-test>
# soit
make tpdev01a
# ou
make tpdev01b
Le cas-test doit être OK, sans alarme. Le code doit compiler sans warning.
Vérifier que l’output est telle qu’on l’attend.
Vérifier le source avec aslint.
Règles de programmation#
Fortran#
Le suffixe des fichiers Fortran est nécessairement .F90.
Les règles de programmation impose l’écriture en minuscules, une indentation des
blocs de 4 caractères (idem en Python et C++).
implicit none est obligatoire.
Autres règles de programmation générale :
une seule routine par fichier,
les types dérivés doivent être définis dans des fichiers suffixés
_type.F90et les modules nommésx_module.F90,une routine ne peut dépasser 500 lignes, 20 arguments au maximum,
utiliser des noms de routines (on n’est plus limité à 6 caractères) et de variables explicites,
ne pas passer d’arguments bidons, utiliser
optional,commenter le rôle de chaque argument,
commenter le code (en anglais),
utiliser
do/enddo(pas dedo/continue, pas degoto),ranger intelligemment les nouvelles routines dans un des répertoires de
bibfor/le code est systématiquement remis en forme avec fprettify.
Certaines de ces règles (et d’autres) sont vérifiées au moment de la soumission par l’utilitaire aslint.
Python#
L’essentiel des règles de programmation sont celles de la PEP8 (Style Guide for Python Code). L’historique du code fait que les anciennes fonctions ne respectent pas encore toutes les règles. Chacun doit contribuer dans ce sens.
Le code est systématiquement remis en forme avec black.
Certaines de ces règles (et d’autres) sont vérifiées au moment de la soumission par l’utilitaire aslint.
aslint#
Vous pouvez, à tout moment vérifier ces règles en lançant aslint en ligne de commande.
Pour que aslint soit accessible, ajouter source $HOME/dev/codeaster/devtools/etc/env.sh
dans le fichier $HOME/.bashrc ou équivalent.
Voir aslint --help pour les détails.
En particulier, vérifier les fichiers suivis par Git actuellement modifiés (liste
retournée par git status):
aslint --git
Pour vérifier tous les fichiers suivis par Git modifiés depuis la branche main:
aslint --git origin/main
aslint peut corriger certains manques (absence de la licence, mise à jour du copyright…) :
aslint --fix --git
# ou
aslint --fix --git origin/main
# ou encore
aslint --fix bibfor/op/op0190.F90
aslint est lancé lors de chaque commit grace aux hooks
(mis en place par install_env, voir Compilation de la version de référence).
Remarque
Si votre branche n’a pas démarré depuis
origin/main, il faut changer avec le nom de la branche de départ du développement ou l’identifiant de révision (point de bifurcation).
Fichiers corrigés#
Catalogue de commandes :
from ..Commons import *
from ..Language.DataStructure import *
from ..Language.Syntax import *
MODI_MAIL = OPER(
nom="MODI_MAIL",
op=190,
sd_prod=maillage_sdaster,
fr=tr("Modifier un maillage par translation"),
reentrant="o:MAILLAGE",
reuse=SIMP(statut="c", typ=CO),
MAILLAGE=SIMP(statut="o", typ=maillage_sdaster),
TRANSLATION=FACT(statut="o", VECTEUR=SIMP(statut="o", typ="R", min=2, max=3)),
INFO=SIMP(statut="f", typ="I", defaut=1, into=(1, 2)),
)
Routine de la commande bibfor/op/op0190.F90 :
subroutine op0190()
implicit none
#include "asterc/getfac.h"
#include "asterc/getres.h"
#include "asterfort/as_allocate.h"
#include "asterfort/as_deallocate.h"
#include "asterfort/assert.h"
#include "asterfort/dismoi.h"
#include "asterfort/getvid.h"
#include "asterfort/getvr8.h"
#include "asterfort/jedema.h"
#include "asterfort/jemarq.h"
#include "asterfort/jeveuo.h"
character(len=8) :: mesh, result
character(len=16) :: concep, nomcmd
character(len=19) :: chcoord
character(len=24) :: vect_coord
integer :: i, j, iret, dim, nbocc, nbnodes
real(kind=8), pointer :: vect(:) => null()
real(kind=8) :: transl(3)
real(kind=8), pointer :: coord(:) => null()
call jemarq()
! read the input mesh name: maillage_sdaster, see d6.03.01, §2.1.1
call getvid(" ", "MAILLAGE", scal=mesh, nbret=iret)
ASSERT(iret == 1)
! read the mesh result (must be identical to the input), see d6.03.01, §2.1.5
call getres(result, concep, nomcmd)
ASSERT(result == mesh)
! check that TRANSLATION exists, see d6.03.01, §2.1.6
call getfac('TRANSLATION', nbocc)
ASSERT(nbocc == 1)
! get the size of the translation vector for a dynamic allocation, see d6.03.01, §2.1.1
call getvr8("TRANSLATION", "VECTEUR", iocc=1, nbval=0, nbret=dim)
dim = -dim
ASSERT(2 <= dim .and. dim <= 3)
! allocate the vector of size 'dim'
AS_ALLOCATE(vr=vect, size=dim)
! read the translation vector values
call getvr8("TRANSLATION", "VECTEUR", iocc=1, nbval=dim, vect=vect)
! name of the jeveux vector containing the coordinates of the mesh
! see d4.06.01 for the COORDO object and d4.06.05 for its VALE vector
chcoord = mesh//".COORDO"
vect_coord = chcoord//".VALE"
! get the address and the size of this vector
call jeveuo(vect_coord, 'E', vr=coord)
call dismoi('NB_NO_MAILLA', mesh, 'MAILLAGE', repi=nbnodes)
! translate the mesh
transl = 0.d0
transl = vect
! loop on the nodes
do i = 1, nbnodes
! always 3 coordinates in .COORDO
do j = 1, 3
coord((i-1)*3+j) = coord((i-1)*3+j)+transl(j)
end do
end do
AS_DEALLOCATE(vr=vect)
call jedema()
end subroutine