Salome HOME
Minor documentation and code review corrections (18)
[modules/adao.git] / doc / fr / ref_operator_requirements.rst
1 ..
2    Copyright (C) 2008-2022 EDF R&D
3
4    This file is part of SALOME ADAO module.
5
6    This library is free software; you can redistribute it and/or
7    modify it under the terms of the GNU Lesser General Public
8    License as published by the Free Software Foundation; either
9    version 2.1 of the License, or (at your option) any later version.
10
11    This library is distributed in the hope that it will be useful,
12    but WITHOUT ANY WARRANTY; without even the implied warranty of
13    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14    Lesser General Public License for more details.
15
16    You should have received a copy of the GNU Lesser General Public
17    License along with this library; if not, write to the Free Software
18    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
19
20    See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
21
22    Author: Jean-Philippe Argaud, jean-philippe.argaud@edf.fr, EDF R&D
23
24 .. _section_ref_operator_requirements:
25
26 Conditions requises pour les fonctions décrivant un opérateur
27 -------------------------------------------------------------
28
29 .. index:: single: setObservationOperator
30 .. index:: single: setEvolutionModel
31 .. index:: single: setControlModel
32
33 Les opérateurs d'observation et d'évolution sont nécessaires pour mettre en
34 oeuvre les procédures d'assimilation de données ou d'optimisation. Ils
35 comprennent la simulation physique par des calculs numériques, mais aussi le
36 filtrage et de restriction pour comparer la simulation à l'observation.
37 L'opérateur d'évolution est ici considéré dans sa forme incrémentale, qui
38 représente la transition entre deux états successifs, et il est alors similaire
39 à l'opérateur d'observation.
40
41 Schématiquement, un opérateur :math:`O` a pour objet de restituer une solution
42 pour des paramètres d'entrée spécifiés. Une partie des paramètres d'entrée peut
43 être modifiée au cours de la procédure d'optimisation. Ainsi, la représentation
44 mathématique d'un tel processus est une fonction. Il a été brièvement décrit
45 dans la section :ref:`section_theory` et il est généralisé ici par la relation:
46
47 .. math:: \mathbf{y} = O( \mathbf{x} )
48
49 entre les pseudo-observations en sortie :math:`\mathbf{y}` et les paramètres
50 d'entrée :math:`\mathbf{x}` en utilisant l'opérateur d'observation ou
51 d'évolution :math:`O`. La même représentation fonctionnelle peut être utilisée
52 pour le modèle linéaire tangent :math:`\mathbf{O}` de :math:`O` et son adjoint
53 :math:`\mathbf{O}^*` qui sont aussi requis par certains algorithmes
54 d'assimilation de données ou d'optimisation.
55
56 En entrée et en sortie de ces opérateurs, les variables :math:`\mathbf{x}` et
57 :math:`\mathbf{y}`, ou leurs incréments, sont mathématiquement des vecteurs, et
58 ils peuvent donc être donnés par l'utilisateur comme des vecteurs non-orientés
59 (de type liste ou vecteur Numpy) ou orientés (de type matrice Numpy).
60
61 Ainsi, **pour décrire de manière complète un opérateur, l'utilisateur n'a qu'à
62 fournir une fonction qui réalise complètement et uniquement l'opération
63 fonctionnelle**.
64
65 Cette fonction est généralement donnée comme une fonction ou un script Python,
66 qui peuvent en particulier être exécuté comme une fonction Python indépendante
67 ou dans un noeud YACS. Cette fonction ou ce script peuvent, sans différences,
68 lancer des codes externes ou utiliser des appels et des méthodes internes
69 Python ou SALOME. Si l'algorithme nécessite les 3 aspects de l'opérateur (forme
70 directe, forme tangente et forme adjointe), l'utilisateur doit donner les 3
71 fonctions ou les approximer grâce à ADAO.
72
73 Il existe pour l'utilisateur 3 méthodes effectives de fournir une représentation
74 fonctionnelle de l'opérateur, qui diffèrent selon le type d'argument choisi:
75
76 - :ref:`section_ref_operator_one`
77 - :ref:`section_ref_operator_funcs`
78 - :ref:`section_ref_operator_switch`
79
80 Dans le cas de l'interface textuelle d'ADAO (TUI), seules les deux premières
81 sont nécessaires car la troisième est incluse dans la seconde. Dans le cas de
82 l'interface graphique EFICAS d'ADAO, ces méthodes sont choisies dans le champ
83 "*FROM*" de chaque opérateur ayant une valeur "*Function*" comme
84 "*INPUT_TYPE*", comme le montre la figure suivante :
85
86   .. eficas_operator_function:
87   .. image:: images/eficas_operator_function.png
88     :align: center
89     :width: 100%
90   .. centered::
91     **Choisir graphiquement une représentation fonctionnelle de l'opérateur**
92
93 En interface textuelle d'ADAO (TUI), dans le cas précis illustré ci-dessus, on
94 réalise la même démarche en écrivant :
95 ::
96
97     ...
98     case.set( 'ObservationOperator',
99         OneFunction = True,
100         Script = 'scripts_for_JDC.py'
101         )
102     ...
103
104 .. _section_ref_operator_one:
105
106 Première forme fonctionnelle : un seul opérateur direct
107 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
108
109 .. index:: single: OneFunction
110 .. index:: single: ScriptWithOneFunction
111 .. index:: single: DirectOperator
112 .. index:: single: DifferentialIncrement
113 .. index:: single: CenteredFiniteDifference
114
115 La première consiste à ne fournir qu'une seule fonction, potentiellement non
116 linéaire, et à approximer les opérateurs tangent et adjoint associés.
117
118 Ceci est fait dans ADAO en utilisant, dans l'interface graphique EFICAS d'ADAO,
119 le mot-clé "*ScriptWithOneFunction*" pour la description par un script. Dans
120 l'interface textuelle, c'est le mot-clé "*OneFunction*", éventuellement combiné
121 avec le mot-clé "*Script*" selon que c'est une fonction ou un script. Si c'est
122 par script externe, l'utilisateur doit fournir un fichier contenant une
123 fonction qui porte le nom obligatoire "*DirectOperator*". Par exemple, un
124 script externe peut suivre le modèle générique suivant::
125
126     def DirectOperator( X ):
127         """ Opérateur direct de simulation non-linéaire """
128         ...
129         ...
130         ...
131         return Y=O(X)
132
133 Dans ce cas, l'utilisateur doit aussi fournir une valeur pour l'incrément
134 différentiel (ou conserver la valeur par défaut), en utilisant dans l'interface
135 graphique (GUI) ou textuelle (TUI) le mot-clé "*DifferentialIncrement*" comme
136 paramètre, qui a une valeur par défaut de 1%. Ce coefficient est utilisé dans
137 l'approximation différences finies pour construire les opérateurs tangent et
138 adjoint. L'ordre de l'approximation différences finies peut aussi être choisi à
139 travers l'interface, en utilisant le mot-clé "*CenteredFiniteDifference*", avec
140 0 pour un schéma non centré du premier ordre (qui est la valeur par défaut), et
141 avec 1 pour un schéma centré du second ordre (et qui coûte numériquement deux
142 fois plus cher que le premier ordre). Si nécessaire et si possible, on peut
143 :ref:`subsection_ref_parallel_df`. Dans tous les cas, un mécanisme de cache
144 interne permet de limiter le nombre d'évaluations de l'opérateur au minimum
145 possible du point de vue de l'exécution séquentielle ou parallèle des
146 approximations numériques des opérateurs tangent et adjoint, pour éviter des
147 calculs redondants. On se reportera à la partie permettant de
148 :ref:`subsection_iterative_convergence_control` pour connaître l'interaction
149 avec les paramètres relatifs à la convergence.
150
151 Cette première forme de définition de l'opérateur permet aisément de tester la
152 forme fonctionnelle avant son usage dans un cas ADAO, réduisant notablement la
153 complexité de l'implémentation de l'opérateur. On peut ainsi utiliser
154 l'algorithme ADAO de vérification "*FunctionTest*" (voir la section sur
155 l':ref:`section_ref_algorithm_FunctionTest`) pour ce test.
156
157 **Avertissement important :** le nom "*DirectOperator*" est obligatoire, et le
158 type de l'argument ``X`` peut être une liste de valeur réelles, un vecteur
159 Numpy ou une matrice Numpy. La fonction utilisateur doit accepter et traiter
160 tous ces cas.
161
162 .. _section_ref_operator_funcs:
163
164 Seconde forme fonctionnelle : trois opérateurs direct, tangent et adjoint
165 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
166
167 .. index:: single: ThreeFunctions
168 .. index:: single: ScriptWithFunctions
169 .. index:: single: DirectOperator
170 .. index:: single: TangentOperator
171 .. index:: single: AdjointOperator
172
173 .. warning::
174
175   En général, il est recommandé d'utiliser la première forme fonctionnelle
176   plutôt que la seconde. Un petit accroissement de performances n'est pas une
177   bonne raison pour utiliser l'implémentation détaillée de cette seconde forme
178   fonctionnelle.
179
180 La seconde consiste à fournir directement les trois opérateurs liés :math:`O`,
181 :math:`\mathbf{O}` et :math:`\mathbf{O}^*`. C'est effectué en utilisant le
182 mot-clé "*ScriptWithFunctions*" pour la description de l'opérateur choisi dans
183 l'interface graphique EFICAS d'ADAO. Dans l'interface textuelle, c'est le
184 mot-clé "*ThreeFunctions*", éventuellement combiné avec le mot-clé "*Script*"
185 selon que c'est une fonction ou un script. L'utilisateur doit fournir dans un
186 script trois fonctions, avec les trois noms obligatoires "*DirectOperator*",
187 "*TangentOperator*" et "*AdjointOperator*". Par exemple, le script externe peut
188 suivre le squelette suivant::
189
190     def DirectOperator( X ):
191         """ Opérateur direct de simulation non-linéaire """
192         ...
193         ...
194         ...
195         return "un vecteur similaire à Y"
196
197     def TangentOperator( paire = (X, dX) ):
198         """ Opérateur linéaire tangent, autour de X, appliqué à dX """
199         X, dX = paire
200         ...
201         ...
202         ...
203         return "un vecteur similaire à Y"
204
205     def AdjointOperator( paire = (X, Y) ):
206         """ Opérateur adjoint, autour de X, appliqué à Y """
207         X, Y = paire
208         ...
209         ...
210         ...
211         return "un vecteur similaire à X"
212
213 Un nouvelle fois, cette seconde définition d'opérateur permet aisément de tester
214 les formes fonctionnelles avant de les utiliser dans le cas ADAO, réduisant la
215 complexité de l'implémentation de l'opérateur.
216
217 Pour certains algorithmes (en particulier les filtres non ensemblistes), il
218 faut que les fonctions tangente et adjointe puisse renvoyer les matrices
219 équivalentes à l'opérateur linéaire. Dans ce cas, lorsque, respectivement, les
220 arguments ``dX`` ou ``Y`` valent ``None``, le script de l'utilisateur doit
221 renvoyer la matrice associée. Les squelettes des fonctions "*TangentOperator*"
222 et "*AdjointOperator*" deviennent alors les suivants::
223
224     def TangentOperator( paire = (X, dX) ):
225         """ Opérateur linéaire tangent, autour de X, appliqué à dX """
226         X, dX = paire
227         ...
228         ...
229         ...
230         if dX is None or len(dX) == 0:
231             return "la matrice de l'opérateur linéaire tangent"
232         else:
233             return "un vecteur similaire à Y"
234
235     def AdjointOperator( paire = (X, Y) ):
236         """ Opérateur adjoint, autour de X, appliqué à Y """
237         X, Y = paire
238         ...
239         ...
240         ...
241         if Y is None or len(Y) == 0:
242             return "la matrice de l'opérateur linéaire adjoint"
243         else:
244             return "un vecteur similaire à X"
245
246 **Avertissement important :** les noms "*DirectOperator*", "*TangentOperator*"
247 et "*AdjointOperator*" sont obligatoires, et le type des arguments ``X``,
248 ``Y``, ``dX`` peut être une liste de valeur réelles, un vecteur Numpy ou une
249 matrice Numpy. La fonction utilisateur doit accepter et traiter tous ces cas.
250
251 .. _section_ref_operator_switch:
252
253 Troisième forme fonctionnelle : trois opérateurs avec un branchement
254 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
255
256 .. index:: single: ScriptWithSwitch
257 .. index:: single: DirectOperator
258 .. index:: single: TangentOperator
259 .. index:: single: AdjointOperator
260
261 .. warning::
262
263   Il est recommandé de ne pas utiliser cette troisième forme fonctionnelle sans
264   une solide raison numérique ou physique. Un accroissement de performances
265   n'est pas une bonne raison pour utiliser la complexité de cette troisième
266   forme fonctionnelle. Seule une impossibilité à utiliser les première ou
267   seconde formes justifie l'usage de la troisième.
268
269 La troisième forme donne de plus grandes possibilités de contrôle de
270 l'exécution des trois fonctions représentant l'opérateur, permettant un usage
271 et un contrôle avancés sur chaque exécution du code de simulation. C'est
272 réalisable en utilisant le mot-clé "*ScriptWithSwitch*" pour la description de
273 l'opérateur à travers l'interface graphique EFICAS d'ADAO. Dans l'interface
274 textuelle, il suffit d'utiliser le mot-clé "*ThreeFunctions*" précédent pour
275 définir aussi ce cas, en indiquant les fonctions adéquates. L'utilisateur doit
276 fournir un script unique aiguillant, selon un contrôle, l'exécution des formes
277 directe, tangente et adjointe du code de simulation. L'utilisateur peut alors,
278 par exemple, utiliser des approximations pour les codes tangent et adjoint, ou
279 introduire une plus grande complexité du traitement des arguments des
280 fonctions. Mais cette démarche sera plus difficile à implémenter et à déboguer.
281
282 Toutefois, si vous souhaitez utiliser cette troisième forme, on recommande de
283 se baser sur le modèle suivant pour le script d'aiguillage. Il nécessite un
284 fichier script ou un code externe nommé ici
285 "*Physical_simulation_functions.py*", contenant trois fonctions nommées
286 "*DirectOperator*", "*TangentOperator*" et "*AdjointOperator*" comme
287 précédemment. Voici le squelette d'aiguillage:
288 ::
289
290     import Physical_simulation_functions
291     import numpy, logging, codecs, pickle
292     def loads( data ):
293         return pickle.loads(codecs.decode(data.encode(), "base64"))
294     #
295     method = ""
296     for param in computation["specificParameters"]:
297         if param["name"] == "method":
298             method = loads(param["value"])
299     if method not in ["Direct", "Tangent", "Adjoint"]:
300         raise ValueError("No valid computation method is given")
301     logging.info("Found method is \'%s\'"%method)
302     #
303     logging.info("Loading operator functions")
304     Function = Physical_simulation_functions.DirectOperator
305     Tangent  = Physical_simulation_functions.TangentOperator
306     Adjoint  = Physical_simulation_functions.AdjointOperator
307     #
308     logging.info("Executing the possible computations")
309     data = []
310     if method == "Direct":
311         logging.info("Direct computation")
312         Xcurrent = computation["inputValues"][0][0][0]
313         data = Function(numpy.matrix( Xcurrent ).T)
314     if method == "Tangent":
315         logging.info("Tangent computation")
316         Xcurrent  = computation["inputValues"][0][0][0]
317         dXcurrent = computation["inputValues"][0][0][1]
318         data = Tangent(numpy.matrix(Xcurrent).T, numpy.matrix(dXcurrent).T)
319     if method == "Adjoint":
320         logging.info("Adjoint computation")
321         Xcurrent = computation["inputValues"][0][0][0]
322         Ycurrent = computation["inputValues"][0][0][1]
323         data = Adjoint((numpy.matrix(Xcurrent).T, numpy.matrix(Ycurrent).T))
324     #
325     logging.info("Formatting the output")
326     it = numpy.ravel(data)
327     outputValues = [[[[]]]]
328     for val in it:
329       outputValues[0][0][0].append(val)
330     #
331     result = {}
332     result["outputValues"]        = outputValues
333     result["specificOutputInfos"] = []
334     result["returnCode"]          = 0
335     result["errorMessage"]        = ""
336
337 Toutes les modifications envisageables peuvent être faites à partir de cette
338 hypothèse de squelette.
339
340 .. _section_ref_operator_control:
341
342 Cas spécial d'un opérateur d'évolution avec contrôle
343 ++++++++++++++++++++++++++++++++++++++++++++++++++++
344
345 Dans certains cas, l'opérateur d'évolution ou d'observation doit être contrôlé
346 par un contrôle d'entrée externe, qui est donné *a priori*. Dans ce cas, la
347 forme générique du modèle incrémental :math:`O` est légèrement modifiée comme
348 suit :
349
350 .. math:: \mathbf{y} = O( \mathbf{x}, \mathbf{u})
351
352 où :math:`\mathbf{u}` est le contrôle sur l'incrément d'état. En effet,
353 l'opérateur direct doit être appliqué à une paire de variables :math:`(X,U)`.
354 Schématiquement, l'opérateur :math:`O` doit être construit comme une fonction
355 applicable sur une paire :math:`\mathbf{(X, U)}` comme suit :
356 ::
357
358     def DirectOperator( paire = (X, U) ):
359         """ Opérateur direct de simulation non-linéaire """
360         X, U = paire
361         ...
362         ...
363         ...
364         return quelque chose comme X(n+1) (évolution) ou Y(n+1) (observation)
365
366 Les opérateurs tangent et adjoint ont la même signature que précédemment, en
367 notant que les dérivées doivent être faites seulement partiellement par rapport
368 à :math:`\mathbf{x}`. Dans un tel cas de contrôle explicite, seule la deuxième
369 forme fonctionnelle (en utilisant "*ScriptWithFunctions*") et la troisième
370 forme fonctionnelle (en utilisant "*ScriptWithSwitch*") peuvent être utilisées.
371
372 .. _section_ref_operator_dimensionless:
373
374 Remarques complémentaires sur l'adimensionnement des opérateurs
375 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
376
377 .. index:: single: Adimensionnement
378 .. index:: single: Sans dimension
379
380 Il est fréquent que les grandeurs physiques, en entrée ou en sortie des
381 opérateurs, présentent des différences notables d'ordre de grandeur ou de taux
382 de variation. Une manière d'éviter des difficultés numériques est d'utiliser,
383 ou d'établir, un adimensionnement des calculs menés dans les opérateurs
384 [WikipediaND]_. Par principe, dans la mesure où la simulation de la physique
385 devrait être la plus adimensionnée possible, il est en premier lieu recommandé
386 d'utiliser les capacités existantes d'adimensionnement du code de calcul.
387
388 Néanmoins, dans le cas courant où l'on ne peut en disposer, il est souvent
389 utile d'environner le calcul pour l'adimensionner en entrée ou en sortie. Une
390 manière simple de faire cela en entrée consiste à transformer les paramètres
391 :math:`\mathbf{x}` en argument d'une fonction comme "*DirectOperator*". On
392 utilise le plus souvent comme référence les valeurs par défaut
393 :math:`\mathbf{x}^b` (ébauche, ou valeur nominale). Pourvu que chaque
394 composante de :math:`\mathbf{x}^b` soit non nulle, on peut ensuite procéder par
395 correction multiplicative. Pour cela, on peut par exemple poser :
396
397 .. math:: \mathbf{x} = \mathbf{\alpha}\mathbf{x}^b
398
399 et optimiser ensuite le paramètre multiplicatif :math:`\mathbf{\alpha}`. Ce
400 paramètre a pour valeur par défaut (ou pour ébauche) un vecteur de 1. De
401 manière similaire, on peut procéder par correction additive si c'est plus
402 judicieux pour la physique sous-jacente. Ainsi, dans ce cas, on peut poser :
403
404 .. math:: \mathbf{x} =\mathbf{x}^b + \mathbf{\alpha}
405
406 et optimiser ensuite le paramètre additif :math:`\mathbf{\alpha}`. Cette fois,
407 ce paramètre a pour valeur d'ébauche un vecteur de 0.
408
409 Attention, l'application d'une démarche d'adimensionnement nécessite aussi la
410 modification des covariances d'erreurs associées dans la formulation globale du
411 problème d'optimisation.
412
413 Une telle démarche suffit rarement à éviter tous les problèmes numériques, mais
414 permet souvent d'améliorer beaucoup le conditionnement numérique de
415 l'optimisation.
416
417 .. index:: single: InputFunctionAsMulti
418
419 Gestion explicite de fonctions "multiples"
420 ++++++++++++++++++++++++++++++++++++++++++
421
422 .. warning::
423
424   Il est fortement recommandé de ne pas utiliser cette gestion explicite de
425   fonctions "multiples" sans une très solide raison informatique pour le faire.
426   Cette gestion est déjà effectuée par défaut dans ADAO pour l'amélioration des
427   performances. Seul l'utilisateur très averti, cherchant à gérer des cas
428   particulièrement difficiles, peut s'intéresser à cette extension. En dépit de
429   sa simplicité, c'est au risque explicite de dégrader notablement les
430   performances, ou d'avoir des erreurs d'exécution étranges.
431
432 Il est possible, lorsque l'on fournit des fonctions d'opérateurs, de les
433 définir comme des fonctions qui traitent non pas un seul argument, mais une
434 série d'arguments, pour restituer en sortie la série des valeurs
435 correspondantes. En pseudo-code, la fonction "multiple", ici nommée
436 ``MultiFunctionO``, représentant l'opérateur classique :math:`O` nommé
437 "*DirectOperator*", effectue :
438 ::
439
440     def MultiFunctionO( Inputs ):
441         """ Multiple ! """
442         Outputs = []
443         for X in Inputs:
444             Y = DirectOperator( X )
445             Outputs.append( Y )
446         return Outputs
447
448 La longueur de la sortie (c'est-à-dire le nombre de valeurs calculées) est
449 égale à la longueur de l'entrée (c'est-à-dire le nombre d'états dont on veut
450 calculer la valeur par l'opérateur).
451
452 Cette possibilité n'est disponible que dans l'interface textuelle TUI d'ADAO.
453 Pour cela, lors de la définition d'une fonction d'opérateur, en même temps que
454 l'on définit de manière habituelle la fonction ou le script externe, il suffit
455 d'indiquer en plus en argument par un booléen supplémentaire
456 "*InputFunctionAsMulti*" que la définition est celle d'une fonction "multiple".
457 Par exemple, si c'est l'opérateur d'observation que l'on définit de cette
458 manière, il faut écrire (sachant que toutes les autres commandes optionnelles
459 restent inchangées) :
460 ::
461
462     case.set( 'ObservationOperator',
463         OneFunction          = MultiFunctionO,
464         ...
465         InputFunctionAsMulti = True,
466         )