Suivi du projet amélioration de la gestion des traceurs
Voici les caractéristiques du nouveau "traceur.def", qui répond aux caractéristiques fixées au cours discussion du bocal du 3 octobre:
- fichier texte de même nom lu par un parseur dédié
- possibilité d'ajouter des commentaires (ici: toutes les chaînes précédées par le caractère "#" sont des commentaires).
- subdivision en sections, signalées par le caractère "&" en première position (inspiré des namelists fortran).
- "version", donnant le numéro de version du fichier présent
- "default", donnant les paramètres par défaut sous la forme <clef>=<valeur>
- -... une section par composante. Le nombre de traceurs en entête est supprimé.
- dans chaque section "composante":
- OBLIGATOIRE: nom du(des) traceur(s) (séparateur: "," si plusieurs traceurs). Premier élément de la ligne.
- OPTIONNEL (la valeur correspondante de la section "default" est adoptée si elle est absente):
- nom du(des) parent(s) (séparateur: "," si plusieurs parents, ce qui n'est possible que pour des traceurs de tagging)
- hadv et vadv (schéma d'advection horizontal et vertical)
- phases: "g"as, "l"iquid, "s"olid
- type de traceur (pour l'instant: "tracer" ou "tag" uniquement)
Ce format permet de compacter fortement les listes et d'éviter des erreurs. Il laisse la possibilité d'ajouter d'autres clefs par la suite (les coefficients de fractionnement notamment).
- on peut utiliser la clef "trac_type" de "run.def" pour lister les composantes qu'il faut lire et fusionner les sections. Exemple: trac_type=merge:repro,inca
Par défaut, les listes sont fusionnées ("merge"): les traceurs de même noms sont fusionnés. Avec plutôt trac_type=cumulate:..., on choisit de les cumuler: le nom de section est ajouté en suffixe aux noms de traceurs identiques dans plusieurs sections.
Quelques décisions complémentaires:
- utilisation de noms plus explicite pour les isotopes: l'atome de masse non nominale est précédé par sa masse atomique entre crochets. Ainsi:
O17 => H2[17]O O18 => H2[18]O DHO => H[2]HO THO => H[3]HO
- utilisation des suffixes "-g", "-l", "-s" pour les phases "g"aseuse, "l"iquide et "s"olide, plus généraux que "v" et "i" ("v"apour et "i"ce)
Le séparateur "-" permet d'éviter de possibles confusions entre une lettre désignant la phase et un nom d'élément (le "l" de Cl - chlore - par exemple)
- absence de chaînage des noms avec la liste complète des ancêtres, rendue possible par l'utilisation de noms plus explicites pour les traceurs (dont la hiérarchie est connue par ailleurs).
- possibilité de tagger à toute génération sans que le tagging ne concerne nécessairement une famille complète ou une génération complète.
En effet (remarque de Frédéric), sommer des traceurs géographiques de différentes régions ne donne pas nécessairement la même chose que le traceur de la somme des régions, car le transport n'est pas nécessairement linéaire.
- le tagging concerne par contre toutes les phases d'une espèce donnée.
- le mot-clef spécial "all" utilisé comme nom de parent d'un traceur de type "tag" permet de tagger tous les traceurs.
Exemple de liste (volontairement en désordre pour illustrer que le parseur s'en sort bien) au nouveau format:
&version=1.0 &default # Note: currently, the 2 recognized types are "tracer" and "tag" parent=air hadv=10 vadv=10 phases=g type=tracer &lmdz blue,red parent=H[2]HO,H2O type=tag # tagging tracer of gen 1&2 tracers H2O phases=g hadv=14 vadv=14 # water vapour water,H2[18]O,H[2]HO,H[3]HO parent=H2O # H2O childs yellow parent=H[3]HO type=tag # tagging tracer for single isotope H2O phases=ls # H2O in liquid and solid phases
Le parseur en donne la version décompactée qui suit (sorties textes effectives telles qu'elles apparaîtront dans LMDZ):
infotrac_init: Information stored in infotrac : infotrac_init: iq | tname | ttext | iadv | niadv | ipar infotrac_init: --------------------------------------------------------------------------- infotrac_init: 1 | H2O-g | H2O-gVLH | 14 | 1 | 0 infotrac_init: 2 | H2O-l | H2O-lVL1 | 10 | 2 | 0 infotrac_init: 3 | H2O-s | H2O-sVL1 | 10 | 3 | 0 infotrac_init: 4 | H2O-g_blue | H2O-g_blueVL1 | 10 | 4 | 1 infotrac_init: 5 | H2O-l_blue | H2O-l_blueVL1 | 10 | 5 | 2 infotrac_init: 6 | H2O-s_blue | H2O-s_blueVL1 | 10 | 6 | 3 infotrac_init: 7 | H2O-g_red | H2O-g_redVL1 | 10 | 7 | 1 infotrac_init: 8 | H2O-l_red | H2O-l_redVL1 | 10 | 8 | 2 infotrac_init: 9 | H2O-s_red | H2O-s_redVL1 | 10 | 9 | 3 infotrac_init: 10 | water-g | water-gVL1 | 10 | 10 | 1 infotrac_init: 11 | water-l | water-lVL1 | 10 | 11 | 2 infotrac_init: 12 | water-s | water-sVL1 | 10 | 12 | 3 infotrac_init: 13 | H2[18]O-g | H2[18]O-gVL1 | 10 | 13 | 1 infotrac_init: 14 | H2[18]O-l | H2[18]O-lVL1 | 10 | 14 | 2 infotrac_init: 15 | H2[18]O-s | H2[18]O-sVL1 | 10 | 15 | 3 infotrac_init: 16 | H[2]HO-g | H[2]HO-gVL1 | 10 | 16 | 1 infotrac_init: 17 | H[2]HO-l | H[2]HO-lVL1 | 10 | 17 | 2 infotrac_init: 18 | H[2]HO-s | H[2]HO-sVL1 | 10 | 18 | 3 infotrac_init: 19 | H[3]HO-g | H[3]HO-gVL1 | 10 | 19 | 1 infotrac_init: 20 | H[3]HO-l | H[3]HO-lVL1 | 10 | 20 | 2 infotrac_init: 21 | H[3]HO-s | H[3]HO-sVL1 | 10 | 21 | 3 infotrac_init: 22 | H[2]HO-g_blue | H[2]HO-g_blueVL1 | 10 | 22 | 16 infotrac_init: 23 | H[2]HO-l_blue | H[2]HO-l_blueVL1 | 10 | 23 | 17 infotrac_init: 24 | H[2]HO-s_blue | H[2]HO-s_blueVL1 | 10 | 24 | 18 infotrac_init: 25 | H[2]HO-g_red | H[2]HO-g_redVL1 | 10 | 25 | 16 infotrac_init: 26 | H[2]HO-l_red | H[2]HO-l_redVL1 | 10 | 26 | 17 infotrac_init: 27 | H[2]HO-s_red | H[2]HO-s_redVL1 | 10 | 27 | 18 infotrac_init: 28 | H[3]HO-g_yellow | H[3]HO-g_yellowVL1 | 10 | 28 | 19 infotrac_init: 29 | H[3]HO-l_yellow | H[3]HO-l_yellowVL1 | 10 | 29 | 20 infotrac_init: 30 | H[3]HO-s_yellow | H[3]HO-s_yellowVL1 | 10 | 30 | 21 infotrac_init: fin
On peut ajouter d'autres clefs, notamment les ratios de fractionnement.
A DISCUTER ENCORE: Savoir si changer les noms de l'eau dans ses trois phases: H2Ov H2Ol H2Oi -> H2O-g, H2O-l H2O-s n'implique pas trop de contraintes du côté des chaînes de post-traitement.
A AJOUTER DANS LE PARSEUR:
- Gestion de base de donnée, afin de pouvoir, indépendamment du module infotrac, accéder à n'importe quelle clef, à la manière de la routine "getin" d'IOIPSL.
- Gestion des listes de traceurs non fusionnées, mais cumulées (cf. ci-dessus).
A NE PAS OUBLIER: Pour mémoire, il y a aussi le ticket #32 (sur la conservation des traceurs)
16/12/2019: MISE À JOUR
Les deux points à ajouter l'ont été:
- Possibilité de cumuler plutôt que fusionner des sections de traceurs. Le nom de section est alors ajouté en suffixe (avant l'éventuel suffixe de phase) pour permettre de distinguer les différents traceurs homonymes. Activation avec tracs_merge=.FALSE. (.TRUE. par défaut), paramètre "en dur" de readTracFiles_Mod.f90, qui peut être transformé si nécessaire en clef de fichier de configuration *.def. NB: Abandon de l'inclusion de cette clef dans la clef "trac_type", qui était assez lourde.
- Gestion de base de données:
- Le parseur peut gérer plusieurs sections de traceurs par fichier, mais par mesure de simplicité (question tranchée avec Laurent Fairhead), on n'en lit qu'une, en fonction de la clef "type_trac".
Exemple: pour type_trac=lmdz,inca: on lit les sections "&lmdz" et "&inca" respectivement dans "tracer_lmdz.def" et "tracer_inca.def", puis on les fusionne ou les cumule. - L'ancienne gestion de traceurs (un seul fichier nommé "traceur.def") n'est a priori plus utile que pour INCA et pourra peut-être être supprimée, car le nouveau format s'appliquera indifféremment à INCA: Anne Cozic est d'accord pour passer à un fichier de traceurs standard, sans nécessité d'une initialisation spécifique à INCA.
- L'information lue dans chaque fichier est stockée dans une composante de dBase(:) (la base de données privée), vecteur de type dérivé "db" (pour database) :
TYPE db !--- Type for tracers sections CHARACTER(LEN=128) :: name TYPE(tr), ALLOCATABLE :: trac(:) END TYPE db TYPE(db), ALLOCATABLE, TARGET, SAVE :: dBase(:) !--- Private sections database
La section "default", en première position, est stockée sous la forme d'un "traceur virtuel" du même nom. Les autres traceurs sont stockés à la suite, décompactés (parents, enfants et phases) et vérifiés. On procède ensuite à la fusion ou au cumul en fonction de la valeur de "tracs_merge".
/!\ La fusion n'est pas effectuée sur les sections "default", utiles uniquement pour déterminer les valeurs par défaut de la liste de traceurs de leur fichier tracer_*.def. Le résultat final (sans aucune section "default") est stocké dans la variable publique "tracers":TYPE(tr), ALLOCATABLE, TARGET, SAVE :: tracers(:) !--- Public tracers list
- Le parseur peut gérer plusieurs sections de traceurs par fichier, mais par mesure de simplicité (question tranchée avec Laurent Fairhead), on n'en lit qu'une, en fonction de la clef "type_trac".
Chaque composante est de type "tr":
TYPE tr CHARACTER(LEN=128) :: name = '' !--- Name CHARACTER(LEN=128) :: prnt = '' !--- Parent name CHARACTER(LEN=128) :: lnam = '' !--- Long name (with adv. scheme) CHARACTER(LEN=16) :: type = 'tracer' !--- Type (so far: 'tracer'/'tag') CHARACTER(LEN=16) :: phas = 'g' !--- Phase ('g'as/'l'iquid/'s'olid) INTEGER :: hadv = 10 !--- Horizontal advection scheme used INTEGER :: vadv = 10 !--- Vertical advection scheme used INTEGER :: iadv = 10 !--- Advection scheme used INTEGER :: igen = 1 !--- Generation number (<=1) INTEGER :: iparnt = 0 !--- Parent index INTEGER, ALLOCATABLE :: ichild(:) !--- Childs index CHARACTER(LEN=128), ALLOCATABLE :: key(:) !--- Keys string list CHARACTER(LEN=128), ALLOCATABLE :: val(:) !--- Corresponding values string list END TYPE tr
Après fusion ou cumul, la base dBase(:) n'est plus utile ; elle n'est donc pas accessible en dehors du module de lecture ("PRIVATE"). Par contre, la base après fusion/cumul et vérification tracers(:) l'est et peut être l'interrogée grâce à quelques routines visibles dans readTracFiles. La principal est getKey(tra, keyn, val[, def_val, t]), qui permet de récupérer la valeur d'une clef associée à un traceur:
- "tra" indique le traceur, soit son indice dans la liste "tracers(:)", soit son nom.
- "keyn" indique le nom de la clef voulue.
- "val" est le résultat (chaîne, entier ou réel ; routine générique).
Les deux derniers arguments sont optionnels:
- "def_val" est une valeur par défaut, au cas où la clef n'apparaîtrait pas dans le fichier tracer_*.def, ni dans la section de traceurs, ni dans la section "default".
- "t" est la base de données (vecteur de type "tr") dans laquelle chercher l'information (par défaut "tracers(:)"). Cela ne sert en principe qu'en interne pour manipuler "dBase(:)".
Parmi les autres outils, on trouve aliasTracer() pour créer un pointeur de traceur en fonction de son nom et idxTracer() pour obtenir le(s) indice(s) d'un ou plusieurs traceurs.
- À noter que le module "strings_mod.f90" regroupe des outils couramment utiles pour les chaînes et les affichages, en particulier un équivalent du "find" de matlab (indices d'un ou plusieurs éléments dans un vecteur) et une routine d'affichage "propre" de tableaux (chaînes, entiers ou réels).
SUITE PROBABLE:
- Il n'est pas nécessaire de substituer aux principaux tableaux (qx et tr_seri notamment) des types dérivés tr étendus par ajout d'une composante tableau: on peut conserver le tableau lui-même inchangé, tout en disposant du descripteur associé (de type tr), accessible par indice (numéro de traceur dans qx ou tr_seri) ou par nom de traceur (utilisation de idxTracer()).
Ces descripteurs présentent l'avantage de regrouper en une seule variable vectorielle les informations utiles pour tous les traceurs.
- De nombreuses variables possèdent un pendant isotopique (souvent repéré par un x additionnel dans le préfixe) ; les opérations s'appliquant à ces variables comme à leurs descendantes forcent pour l'instant la création explicite de ces descendantes ainsi que la duplication des lignes de code correspondant aux opérations en question.
L'utilisation d'un pointeur semble toute indiquée. Par exemple, pour q_seri (exemple purement illustratif: qx doit être mis à jour en fin de pas de temps, non pas à chaque ajout de tendance comme q_seri: q_seri ne peut donc être un alias de qx, mais doit être sa copie partielle en début de pas de temps !) étendue à ses isotopes, pour l'exemple donné plus haut, il faudrait pouvoir écrire:
q_seri => qx(:,:,[1,(k,k=10:21)])
Cette extension de la fonction aliasTracer() est hélas illicite en fortran: un pointeur ne peut être associé à des sections non contigües d'un tableau.
Abandonner le classement des traceurs dans qx par génération croissante pourrait résoudre le problème, mais rendrait l'organisation bien moins claire (resterait aussi à savoir comment ranger les traceurs de tagging).
Il semble préférable de se contenter de définir q_seri (et les autres variables analogues) sous la forme d'un objet de type dérivé muni d'une composante contenant le vecteur d'indices des traceurs de qx requis. Les opérations répétées sur la variable principale et ses variables isotopiques le sont par simple bouclage sur ce vecteur d'indices.
On définit donc le type dérivé suivant:
TYPE tr2 CHARACTER(LEN=1) :: typ REAL, POINTER :: v(:,:) REAL, ALLOCATABLE :: f(:,:) INTEGER :: iq END TYPE tr2
Lorsque typ=='a', on se contente d'un alias (POINTER), alors que pour typ=='c', on copie la variable concernée (ALLOCATABLE).
Dans les deux cas, le contenu est accessible via les composantes out(:)%v.
La fonction defVar encapsule les opérations nécessaires à la création de l'objet voulu:
FUNCTION defVar(name,typ,nGen) RESULT(out) TYPE(tr2), ALLOCATABLE :: out(:) !--- Output object CHARACTER(LEN=*), INTENT(IN) :: name !--- Tracer name CHARACTER(LEN=*), OPTIONAL, INTENT(IN) :: typ !--- 'a'lias or 'c'opy INTEGER, OPTIONAL, INTENT(IN) :: nGen !--- Number of generations kept INTEGER :: iq, ng, k CHARACTER(LEN=1) :: tp LOGICAL, ALLOCATABLE :: ll(:) tp = 'c'; IF(PRESENT(typ)) tp = typ ng = 2 ; IF(PRESENT(nGen)) ng = nGen ALLOCATE(ll(nqtrue)); ll(idxTracer(name)) = .TRUE. !--- Main tracer index DO iq=1,nqtrue !--- Identify the kept tracers IF(tracers(iq)%igen>ng) EXIT IF(ll(idxTracer(tracers(iq)%prnt))) ll(iq)=.TRUE. !--- Tracer "name" is an ancestor END DO ALLOCATE(out(COUNT(ll))); out(:)%typ = typ; k = 0 DO iq=1,nqtrue; IF(.NOT.ll(iq)) CYCLE k = k + 1; out(k)%iq = iq IF(tp=='p') THEN; out(k)%v => qx(:,:,iq) ELSE; out(k)%f = qx(:,:,iq); out(k)%v => out(k)%f; END IF k = k+1 END DO END FUNCTION defVar
Et pour q_seri, la syntaxe se réduit à (par défaut: copie de variables, 2 générations utilisées):
TYPE(tr2) :: q_seri q_seri = defVar('H2O-g')
Si les opérations à pratiquer sur la vapeur et ses descendants sont identiques, la boucle suivante suffit:
DO k=1,SIZE(t_seri, DIM=1) Opérations sur t_seri(k)%v(:,:) END DO
Les informations des traceurs constituant cet objet sont accessibles via l'indice iq et le descripteur de traceurs tracers(:).
Par exemple, le nom de la kième composante de t_seri est: tracers(t_seri(k)%iq)%name
.
- Ajout de caractéristiques des isotopes dans le fichier tracer_*.def directement, en tant que clefs supplémentaires, accessibles grâce à la routine getKey().
Notamment, tnat et alpha_ideal me semblent ici plus à leur place qu'en dur dans le code. L'exemple donné plus haut devient:
&version=1.0 &default # Note: currently, the 2 recognized types are "tracer" and "tag" parent=air hadv=10 vadv=10 phases=g type=tracer &lmdz blue,red parent=H[2]HO,H2O type=tag # Tagging tracer of gen 1&2 tracers H2O phases=g hadv=14 vadv=14 # Water vapour water parent=H2O tnat=1.0 alpha=1.0 # [1]H[1]H[16]O ~ H2O total H2[18]O parent=H2O tnat=2.00052e-3 alpha=1.006 # [1]H[1]H[18]O H[2]HO parent=H2O tnat=1.5576e-4 alpha=1.01 # [1]H[2]H[16]O H[3]HO parent=H2O tnat=0. alpha=1.0 # [1]H[3]H[16]O yellow parent=H[3]HO type=tag # Tagging tracer for single isotope H2O phases=ls # H2O in liquid and solid phases
Deux remarques:
1) On perd l'avantage de la factorisation en fonction des parents à cause de ces paramètres ; on pourrait le garder avec cette convention:
water,H2[18]O,H[2]HO,H[3]HO parent=H2O tnat=1.0,2.00052e-3,1.5576e-4,0. alpha=1.0,1.006,1.01,1.0
mais ça n'est pas très lisible. Tant que le nombre d'isotopes n'est pas excessif, l'inconvénient de cette perte me semble mineur.
2) Un fichier tracer_*.def ne contenant pas nécessairement tous les isotopes utilisables, et ne constitue pas une base de données complète pour des paramètres tels que tnat et alpha_ideal.
On peut pallier à ce défaut par exemple:
- en créant une base de données séparée sous la forme de sections supplémentaires ; exemple:
&tnat water,1.0 H2[17]O,4.E-5 H2[18]O,2.00052e-3 H[2]HO,1.5576e-4 H[3]HO,0. &alpha water,1.0 H2[17]O,1.003 H2[18]O,1.006 H[2]HO,1.01 H[3]HO,1.0
Ça me semble relativement peu lisible...
- en ajoutant une clef "activate" pour indiquer lorsqu'un traceur/isotope est ou n'est pas activé:
&version=1.0 &default type_trac=lmdz parent=air hadv=10 vadv=10 phases=g type=tracer activate=y &lmdz H2O phases=g hadv=14 vadv=14 # vapour water H2O phases=l # liquid water water parent=H2O tnat=1.0 alpha=1.0 # [1]H[1]H[16]O ~ H2O total H2[17]O parent=H2O tnat=4.E-5 alpha=1.003 activate=n # [1]H[1]H[17]O H2[18]O parent=H2O tnat=2.00052e-3 alpha=1.006 # [1]H[1]H[18]O H[2]HO parent=H2O tnat=1.5576e-4 alpha=1.01 # [1]H[2]H[16]O H[3]HO parent=H2O tnat=0. alpha=1.0 # [1]H[3]H[16]O
Ça me semble un peu lourd, et on ne sait pas au premier coup d'œil ce qui est vraiment utilisé dans le modèle.
La solution initiale (pas de base de données complète dans tout fichier tracer_*.def concernant des paramètres supplémentaires du type tnat ou alpha_ideal) semble donc préférable.
=> Avis de Camille: les tnat sont des constantes immuables, et les alpha ne servent que pour l'initialisation: autant les garder en dur dans le code.
=> Idée: garder séparément d'infotrac, assez générique, toutes les grandeurs spécifiques. On pourrait donc imaginer un fichier isotopes_params_mod.F90 où regrouper ces informations (tnat et alpha notamment), ou encore un fichier de paramètres (à lire comme tracers_*.def) qui ne serait pas susceptible d'être modifié.
À discuter.