Salome HOME
c77797d0f62ca8573132ed8951307a6df9759212
[modules/adao.git] / src / daComposant / daCore / AssimilationStudy.py
1 #-*-coding:iso-8859-1-*-
2 #
3 #  Copyright (C) 2008-2011  EDF R&D
4 #
5 #  This library is free software; you can redistribute it and/or
6 #  modify it under the terms of the GNU Lesser General Public
7 #  License as published by the Free Software Foundation; either
8 #  version 2.1 of the License.
9 #
10 #  This library is distributed in the hope that it will be useful,
11 #  but WITHOUT ANY WARRANTY; without even the implied warranty of
12 #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13 #  Lesser General Public License for more details.
14 #
15 #  You should have received a copy of the GNU Lesser General Public
16 #  License along with this library; if not, write to the Free Software
17 #  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
18 #
19 #  See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
20 #
21 #  Author: Jean-Philippe Argaud, jean-philippe.argaud@edf.fr, EDF R&D
22
23 __doc__ = """
24     Définit les outils généraux élémentaires.
25     
26     Ce module est destiné à etre appelée par AssimilationStudy pour constituer
27     les objets élémentaires de l'étude.
28 """
29 __author__ = "Jean-Philippe ARGAUD"
30
31 import os, sys
32 import numpy
33 import Logging ; Logging.Logging() # A importer en premier
34 import Persistence
35 from BasicObjects import Operator
36
37 # ==============================================================================
38 class AssimilationStudy:
39     """
40     Cette classe sert d'interface pour l'utilisation de l'assimilation de
41     données. Elle contient les méthodes ou accesseurs nécessaires à la
42     construction d'un calcul d'assimilation.
43     """
44     def __init__(self, name=""):
45         """
46         Prévoit de conserver l'ensemble des variables nécssaires à un algorithme
47         élémentaire. Ces variables sont ensuite disponibles pour implémenter un
48         algorithme élémentaire particulier.
49
50         Background............: vecteur Xb
51         Observation...........: vecteur Y (potentiellement temporel)
52             d'observations
53         State.................: vecteur d'état dont une partie est le vecteur de
54             contrôle. Cette information n'est utile que si l'on veut faire des
55             calculs sur l'état complet, mais elle n'est pas indispensable pour
56             l'assimilation.
57         Control...............: vecteur X contenant toutes les variables de
58             contrôle, i.e. les paramètres ou l'état dont on veut estimer la
59             valeur pour obtenir les observations
60         ObservationOperator...: opérateur d'observation H
61
62         Les observations présentent une erreur dont la matrice de covariance est
63         R. L'ébauche du vecteur de contrôle présente une erreur dont la matrice
64         de covariance est B.
65         """
66         self.__name = str(name)
67         self.__Xb = None
68         self.__Y  = None
69         self.__B  = None
70         self.__R  = None
71         self.__Q  = None
72         self.__H  = {}
73         self.__M  = {}
74         #
75         self.__X  = Persistence.OneVector()
76         self.__Parameters        = {}
77         self.__StoredDiagnostics = {}
78         self.__StoredInputs      = {}
79         #
80         # Variables temporaires
81         self.__algorithm         = {}
82         self.__algorithmFile     = None
83         self.__algorithmName     = None
84         self.__diagnosticFile    = None
85         #
86         # Récupère le chemin du répertoire parent et l'ajoute au path
87         # (Cela complète l'action de la classe PathManagement dans PlatformInfo,
88         # qui est activée dans Persistence)
89         self.__parent = os.path.abspath(os.path.join(os.path.dirname(__file__),".."))
90         sys.path.insert(0, self.__parent)
91         sys.path = list(set(sys.path)) # Conserve en unique exemplaire chaque chemin
92
93     # ---------------------------------------------------------
94     def setBackground(self,
95             asVector           = None,
96             asPersistentVector = None,
97             Scheduler          = None,
98             toBeStored         = False,
99             ):
100         """
101         Permet de définir l'estimation a priori :
102         - asVector : entrée des données, comme un vecteur compatible avec le
103           constructeur de numpy.matrix
104         - asPersistentVector : entrée des données, comme un vecteur de type
105           persistent contruit avec la classe ad-hoc "Persistence"
106         - Scheduler est le contrôle temporel des données
107         - toBeStored : booléen indiquant si la donnée d'entrée est sauvée pour
108           être rendue disponible au même titre que les variables de calcul
109         """
110         if asVector is not None:
111             if type( asVector ) is type( numpy.matrix([]) ):
112                 self.__Xb = numpy.matrix( asVector.A1, numpy.float ).T
113             else:
114                 self.__Xb = numpy.matrix( asVector,    numpy.float ).T
115         elif asPersistentVector is not None:
116             self.__Xb = asPersistentVector
117         else:
118             raise ValueError("Error: improperly defined background")
119         if toBeStored:
120            self.__StoredInputs["Background"] = self.__Xb
121         return 0
122     
123     def setBackgroundError(self,
124             asCovariance = None,
125             toBeStored   = False,
126             ):
127         """
128         Permet de définir la covariance des erreurs d'ébauche :
129         - asCovariance : entrée des données, comme une matrice compatible avec
130           le constructeur de numpy.matrix
131         - toBeStored : booléen indiquant si la donnée d'entrée est sauvée pour
132           être rendue disponible au même titre que les variables de calcul
133         """
134         self.__B  = numpy.matrix( asCovariance, numpy.float )
135         if toBeStored:
136             self.__StoredInputs["BackgroundError"] = self.__B
137         return 0
138
139     # -----------------------------------------------------------
140     def setObservation(self,
141             asVector           = None,
142             asPersistentVector = None,
143             Scheduler          = None,
144             toBeStored         = False,
145             ):
146         """
147         Permet de définir les observations :
148         - asVector : entrée des données, comme un vecteur compatible avec le
149           constructeur de numpy.matrix
150         - asPersistentVector : entrée des données, comme un vecteur de type
151           persistent contruit avec la classe ad-hoc "Persistence"
152         - Scheduler est le contrôle temporel des données disponibles
153         - toBeStored : booléen indiquant si la donnée d'entrée est sauvée pour
154           être rendue disponible au même titre que les variables de calcul
155         """
156         if asVector is not None:
157             if type( asVector ) is type( numpy.matrix([]) ):
158                 self.__Y = numpy.matrix( asVector.A1, numpy.float ).T
159             else:
160                 self.__Y = numpy.matrix( asVector,    numpy.float ).T
161         elif asPersistentVector is not None:
162             self.__Y = asPersistentVector
163         else:
164             raise ValueError("Error: improperly defined observations")
165         if toBeStored:
166             self.__StoredInputs["Observation"] = self.__Y
167         return 0
168
169     def setObservationError(self,
170             asCovariance = None,
171             toBeStored   = False,
172             ):
173         """
174         Permet de définir la covariance des erreurs d'observations :
175         - asCovariance : entrée des données, comme une matrice compatible avec
176           le constructeur de numpy.matrix
177         - toBeStored : booléen indiquant si la donnée d'entrée est sauvée pour
178           être rendue disponible au même titre que les variables de calcul
179         """
180         self.__R  = numpy.matrix( asCovariance, numpy.float )
181         if toBeStored:
182             self.__StoredInputs["ObservationError"] = self.__R
183         return 0
184
185     def setObservationOperator(self,
186             asFunction = {"Direct":None, "Tangent":None, "Adjoint":None},
187             asMatrix   = None,
188             appliedToX = None,
189             toBeStored = False,
190             ):
191         """
192         Permet de définir un opérateur d'observation H. L'ordre de priorité des
193         définitions et leur sens sont les suivants :
194         - si asFunction["Tangent"] et asFunction["Adjoint"] ne sont pas None
195           alors on définit l'opérateur à l'aide de fonctions. Si la fonction
196           "Direct" n'est pas définie, on prend la fonction "Tangent".
197         - si les fonctions ne sont pas disponibles et si asMatrix n'est pas
198           None, alors on définit l'opérateur "Direct" et "Tangent" à l'aide de
199           la matrice, et l'opérateur "Adjoint" à l'aide de la transposée. La
200           matrice fournie doit être sous une forme compatible avec le
201           constructeur de numpy.matrix.
202         - si l'argument "appliedToX" n'est pas None, alors on définit, pour des
203           X divers, l'opérateur par sa valeur appliquée à cet X particulier,
204           sous la forme d'un dictionnaire appliedToX[NAME] avec NAME un nom.
205           L'opérateur doit néanmoins déjà avoir été défini comme d'habitude.
206         - toBeStored : booléen indiquant si la donnée d'entrée est sauvée pour
207           être rendue disponible au même titre que les variables de calcul
208         """
209         if (type(asFunction) is type({})) and (asFunction["Tangent"] is not None) and (asFunction["Adjoint"] is not None):
210             if not asFunction.has_key("Direct") or (asFunction["Direct"] is None):
211                 self.__H["Direct"]  = Operator( fromMethod = asFunction["Tangent"]  )
212             else:
213                 self.__H["Direct"] = Operator( fromMethod = asFunction["Direct"]  )
214             self.__H["Tangent"]    = Operator( fromMethod = asFunction["Tangent"] )
215             self.__H["Adjoint"]    = Operator( fromMethod = asFunction["Adjoint"] )
216         elif asMatrix is not None:
217             mat = numpy.matrix( asMatrix, numpy.float )
218             self.__H["Direct"]  = Operator( fromMatrix = mat )
219             self.__H["Tangent"] = Operator( fromMatrix = mat )
220             self.__H["Adjoint"] = Operator( fromMatrix = mat.T )
221         else:
222             raise ValueError("Error: improperly defined observation operator")
223         #
224         if appliedToX is not None:
225             self.__H["AppliedToX"] = {}
226             if type(appliedToX) is not dict:
227                 raise ValueError("Error: observation operator defined by \"appliedToX\" need a dictionary as argument.")
228             for key in appliedToX.keys():
229                 if type( appliedToX[key] ) is type( numpy.matrix([]) ):
230                     # Pour le cas où l'on a une vraie matrice
231                     self.__H["AppliedToX"][key] = numpy.matrix( appliedToX[key].A1, numpy.float ).T
232                 elif type( appliedToX[key] ) is type( numpy.array([]) ) and len(appliedToX[key].shape) > 1:
233                     # Pour le cas où l'on a un vecteur représenté en array avec 2 dimensions
234                     self.__H["AppliedToX"][key] = numpy.matrix( appliedToX[key].reshape(len(appliedToX[key]),), numpy.float ).T
235                 else:
236                     self.__H["AppliedToX"][key] = numpy.matrix( appliedToX[key],    numpy.float ).T
237         else:
238             self.__H["AppliedToX"] = None
239         #
240         if toBeStored:
241             self.__StoredInputs["ObservationOperator"] = self.__H
242         return 0
243
244     # -----------------------------------------------------------
245     def setEvolutionModel(self,
246             asFunction = {"Direct":None, "Tangent":None, "Adjoint":None},
247             asMatrix   = None,
248             Scheduler  = None,
249             toBeStored = False,
250             ):
251         """
252         Permet de définir un opérateur d'évolution M. L'ordre de priorité des
253         définitions et leur sens sont les suivants :
254         - si asFunction["Tangent"] et asFunction["Adjoint"] ne sont pas None
255           alors on définit l'opérateur à l'aide de fonctions. Si la fonction
256           "Direct" n'est pas définie, on prend la fonction "Tangent".
257         - si les fonctions ne sont pas disponibles et si asMatrix n'est pas
258           None, alors on définit l'opérateur "Direct" et "Tangent" à l'aide de
259           la matrice, et l'opérateur "Adjoint" à l'aide de la transposée. La
260           matrice fournie doit être sous une forme compatible avec le
261           constructeur de numpy.matrix.
262         - toBeStored : booléen indiquant si la donnée d'entrée est sauvée pour
263           être rendue disponible au même titre que les variables de calcul
264         """
265         if (type(asFunction) is type({})) and (asFunction["Tangent"] is not None) and (asFunction["Adjoint"] is not None):
266             if not asFunction.has_key("Direct") or (asFunction["Direct"] is None):
267                 self.__M["Direct"] = Operator( fromMethod = asFunction["Tangent"]  )
268             else:
269                 self.__M["Direct"] = Operator( fromMethod = asFunction["Direct"]  )
270             self.__M["Tangent"]    = Operator( fromMethod = asFunction["Tangent"] )
271             self.__M["Adjoint"]    = Operator( fromMethod = asFunction["Adjoint"] )
272         elif asMatrix is not None:
273             matrice = numpy.matrix( asMatrix, numpy.float )
274             self.__M["Direct"]  = Operator( fromMatrix = matrice )
275             self.__M["Tangent"] = Operator( fromMatrix = matrice )
276             self.__M["Adjoint"] = Operator( fromMatrix = matrice.T )
277         else:
278             raise ValueError("Error: improperly defined evolution operator")
279         #
280         if toBeStored:
281             self.__StoredInputs["EvolutionModel"] = self.__M
282         return 0
283
284     def setEvolutionError(self,
285             asCovariance = None,
286             toBeStored   = False,
287             ):
288         """
289         Permet de définir la covariance des erreurs de modèle :
290         - asCovariance : entrée des données, comme une matrice compatible avec
291           le constructeur de numpy.matrix
292         - toBeStored : booléen indiquant si la donnée d'entrée est sauvée pour
293           être rendue disponible au même titre que les variables de calcul
294         """
295         self.__Q  = numpy.matrix( asCovariance, numpy.float )
296         if toBeStored:
297             self.__StoredInputs["EvolutionError"] = self.__Q
298         return 0
299
300     # -----------------------------------------------------------
301     def setControls (self,
302             asVector = None,
303             toBeStored   = False,
304             ):
305         """
306         Permet de définir la valeur initiale du vecteur X contenant toutes les
307         variables de contrôle, i.e. les paramètres ou l'état dont on veut
308         estimer la valeur pour obtenir les observations. C'est utile pour un
309         algorithme itératif/incrémental
310         - asVector : entrée des données, comme un vecteur compatible avec le
311           constructeur de numpy.matrix.
312         - toBeStored : booléen indiquant si la donnée d'entrée est sauvée pour
313           être rendue disponible au même titre que les variables de calcul
314         """
315         if asVector is not None:
316             self.__X.store( asVector )
317         if toBeStored:
318             self.__StoredInputs["Controls"] = self.__X
319         return 0
320
321     # -----------------------------------------------------------
322     def setAlgorithm(self, choice = None ):
323         """
324         Permet de sélectionner l'algorithme à utiliser pour mener à bien l'étude
325         d'assimilation. L'argument est un champ caractère se rapportant au nom
326         d'un fichier contenu dans "../daAlgorithms" et réalisant l'opération
327         d'assimilation sur les arguments (Xb,Y,H,R,B,Xa).
328         """
329         if choice is None:
330             raise ValueError("Error: algorithm choice has to be given")
331         if self.__algorithmName is not None:
332             raise ValueError("Error: algorithm choice has already been done as \"%s\", it can't be changed."%self.__algorithmName)
333         daDirectory = "daAlgorithms"
334         #
335         # Recherche explicitement le fichier complet
336         # ------------------------------------------
337         module_path = None
338         for directory in sys.path:
339             if os.path.isfile(os.path.join(directory, daDirectory, str(choice)+'.py')):
340                 module_path = os.path.abspath(os.path.join(directory, daDirectory))
341         if module_path is None:
342             raise ImportError("No algorithm module named \"%s\" was found in a \"%s\" subdirectory\n             The search path is %s"%(choice, daDirectory, sys.path))
343         #
344         # Importe le fichier complet comme un module
345         # ------------------------------------------
346         try:
347             sys_path_tmp = sys.path ; sys.path.insert(0,module_path)
348             self.__algorithmFile = __import__(str(choice), globals(), locals(), [])
349             self.__algorithmName = str(choice)
350             sys.path = sys_path_tmp ; del sys_path_tmp
351         except ImportError, e:
352             raise ImportError("The module named \"%s\" was found, but is incorrect at the import stage.\n             The import error message is: %s"%(choice,e))
353         #
354         # Instancie un objet du type élémentaire du fichier
355         # -------------------------------------------------
356         self.__algorithm = self.__algorithmFile.ElementaryAlgorithm()
357         self.__StoredInputs["AlgorithmName"] = str(choice)
358         return 0
359
360     def setAlgorithmParameters(self, asDico=None):
361         """
362         Permet de définir les paramètres de l'algorithme, sous la forme d'un
363         dictionnaire.
364         """
365         if asDico is not None:
366             self.__Parameters = dict( asDico )
367         else:
368             self.__Parameters = {}
369         self.__StoredInputs["AlgorithmParameters"] = self.__Parameters
370         return 0
371
372     # -----------------------------------------------------------
373     def setDiagnostic(self, choice = None, name = "", unit = "", basetype = None, parameters = {} ):
374         """
375         Permet de sélectionner un diagnostic a effectuer.
376         """
377         if choice is None:
378             raise ValueError("Error: diagnostic choice has to be given")
379         daDirectory = "daDiagnostics"
380         #
381         # Recherche explicitement le fichier complet
382         # ------------------------------------------
383         module_path = None
384         for directory in sys.path:
385             if os.path.isfile(os.path.join(directory, daDirectory, str(choice)+'.py')):
386                 module_path = os.path.abspath(os.path.join(directory, daDirectory))
387         if module_path is None:
388             raise ImportError("No diagnostic module named \"%s\" was found in a \"%s\" subdirectory\n             The search path is %s"%(choice, daDirectory, sys.path))
389         #
390         # Importe le fichier complet comme un module
391         # ------------------------------------------
392         try:
393             sys_path_tmp = sys.path ; sys.path.insert(0,module_path)
394             self.__diagnosticFile = __import__(str(choice), globals(), locals(), [])
395             sys.path = sys_path_tmp ; del sys_path_tmp
396         except ImportError, e:
397             raise ImportError("The module named \"%s\" was found, but is incorrect at the import stage.\n             The import error message is: %s"%(choice,e))
398         #
399         # Instancie un objet du type élémentaire du fichier
400         # -------------------------------------------------
401         if self.__StoredInputs.has_key(name):
402             raise ValueError("A default input with the same name \"%s\" already exists."%str(name))
403         elif self.__StoredDiagnostics.has_key(name):
404             raise ValueError("A diagnostic with the same name \"%s\" already exists."%str(name))
405         else:
406             self.__StoredDiagnostics[name] = self.__diagnosticFile.ElementaryDiagnostic(
407                 name       = name,
408                 unit       = unit,
409                 basetype   = basetype,
410                 parameters = parameters )
411         return 0
412
413     # -----------------------------------------------------------
414     def shape_validate(self):
415         """
416         Validation de la correspondance correcte des tailles des variables et
417         des matrices s'il y en a.
418         """
419         if self.__Xb is None:                  __Xb_shape = (0,)
420         elif hasattr(self.__Xb,"shape"):
421             if type(self.__Xb.shape) is tuple: __Xb_shape = self.__Xb.shape
422             else:                              __Xb_shape = self.__Xb.shape()
423         else: raise TypeError("Xb has no attribute of shape: problem !")
424         #
425         if self.__Y is None:                  __Y_shape = (0,)
426         elif hasattr(self.__Y,"shape"):
427             if type(self.__Y.shape) is tuple: __Y_shape = self.__Y.shape
428             else:                             __Y_shape = self.__Y.shape()
429         else: raise TypeError("Y has no attribute of shape: problem !")
430         #
431         if self.__B is None:                  __B_shape = (0,0)
432         elif hasattr(self.__B,"shape"):
433             if type(self.__B.shape) is tuple: __B_shape = self.__B.shape
434             else:                             __B_shape = self.__B.shape()
435         else: raise TypeError("B has no attribute of shape: problem !")
436         #
437         if self.__R is None:                  __R_shape = (0,0)
438         elif hasattr(self.__R,"shape"):
439             if type(self.__R.shape) is tuple: __R_shape = self.__R.shape
440             else:                             __R_shape = self.__R.shape()
441         else: raise TypeError("R has no attribute of shape: problem !")
442         #
443         if self.__Q is None:                  __Q_shape = (0,0)
444         elif hasattr(self.__Q,"shape"):
445             if type(self.__Q.shape) is tuple: __Q_shape = self.__Q.shape
446             else:                             __Q_shape = self.__Q.shape()
447         else: raise TypeError("Q has no attribute of shape: problem !")
448         #
449         if len(self.__H) == 0:                          __H_shape = (0,0)
450         elif type(self.__H) is type({}):                __H_shape = (0,0)
451         elif hasattr(self.__H["Direct"],"shape"):
452             if type(self.__H["Direct"].shape) is tuple: __H_shape = self.__H["Direct"].shape
453             else:                                       __H_shape = self.__H["Direct"].shape()
454         else: raise TypeError("H has no attribute of shape: problem !")
455         #
456         if len(self.__M) == 0:                          __M_shape = (0,0)
457         elif type(self.__M) is type({}):                __M_shape = (0,0)
458         elif hasattr(self.__M["Direct"],"shape"):
459             if type(self.__M["Direct"].shape) is tuple: __M_shape = self.__M["Direct"].shape
460             else:                                       __M_shape = self.__M["Direct"].shape()
461         else: raise TypeError("M has no attribute of shape: problem !")
462         #
463         # Vérification des conditions
464         # ---------------------------
465         if not( len(__Xb_shape) == 1 or min(__Xb_shape) == 1 ):
466             raise ValueError("Shape characteristic of Xb is incorrect: \"%s\""%(__Xb_shape,))
467         if not( len(__Y_shape) == 1 or min(__Y_shape) == 1 ):
468             raise ValueError("Shape characteristic of Y is incorrect: \"%s\""%(__Y_shape,))
469         #
470         if not( min(__B_shape) == max(__B_shape) ):
471             raise ValueError("Shape characteristic of B is incorrect: \"%s\""%(__B_shape,))
472         if not( min(__R_shape) == max(__R_shape) ):
473             raise ValueError("Shape characteristic of R is incorrect: \"%s\""%(__R_shape,))
474         if not( min(__Q_shape) == max(__Q_shape) ):
475             raise ValueError("Shape characteristic of Q is incorrect: \"%s\""%(__Q_shape,))
476         if not( min(__M_shape) == max(__M_shape) ):
477             raise ValueError("Shape characteristic of M is incorrect: \"%s\""%(__M_shape,))
478         #
479         if len(self.__H) > 0 and not(type(self.__H) is type({})) and not( __H_shape[1] == max(__Xb_shape) ):
480             raise ValueError("Shape characteristic of H \"%s\" and X \"%s\" are incompatible"%(__H_shape,__Xb_shape))
481         if len(self.__H) > 0 and not(type(self.__H) is type({})) and not( __H_shape[0] == max(__Y_shape) ):
482             raise ValueError("Shape characteristic of H \"%s\" and Y \"%s\" are incompatible"%(__H_shape,__Y_shape))
483         if len(self.__H) > 0 and not(type(self.__H) is type({})) and len(self.__B) > 0 and not( __H_shape[1] == __B_shape[0] ):
484             raise ValueError("Shape characteristic of H \"%s\" and B \"%s\" are incompatible"%(__H_shape,__B_shape))
485         if len(self.__H) > 0 and not(type(self.__H) is type({})) and len(self.__R) > 0 and not( __H_shape[0] == __R_shape[1] ):
486             raise ValueError("Shape characteristic of H \"%s\" and R \"%s\" are incompatible"%(__H_shape,__R_shape))
487         #
488         if self.__B is not None and len(self.__B) > 0 and not( __B_shape[1] == max(__Xb_shape) ):
489             raise ValueError("Shape characteristic of B \"%s\" and Xb \"%s\" are incompatible"%(__B_shape,__Xb_shape))
490         #
491         if self.__R is not None and len(self.__R) > 0 and not( __R_shape[1] == max(__Y_shape) ):
492             raise ValueError("Shape characteristic of R \"%s\" and Y \"%s\" are incompatible"%(__R_shape,__Y_shape))
493         #
494         if self.__M is not None and len(self.__M) > 0 and not(type(self.__M) is type({})) and not( __M_shape[1] == max(__Xb_shape) ):
495             raise ValueError("Shape characteristic of M \"%s\" and X \"%s\" are incompatible"%(__M_shape,__Xb_shape))
496         #
497         return 1
498
499     # -----------------------------------------------------------
500     def analyze(self):
501         """
502         Permet de lancer le calcul d'assimilation.
503         
504         Le nom de la méthode à activer est toujours "run". Les paramètres en
505         arguments de la méthode sont fixés. En sortie, on obtient les résultats
506         dans la variable de type dictionnaire "StoredVariables", qui contient en
507         particulier des objets de Persistence pour les analyses, OMA...
508         """
509         self.shape_validate()
510         #
511         self.__algorithm.run(
512             Xb         = self.__Xb,
513             Y          = self.__Y,
514             H          = self.__H,
515             M          = self.__M,
516             R          = self.__R,
517             B          = self.__B,
518             Q          = self.__Q,
519             Parameters = self.__Parameters,
520             )
521         return 0
522
523     # -----------------------------------------------------------
524     def get(self, key=None):
525         """
526         Renvoie les résultats disponibles après l'exécution de la méthode
527         d'assimilation, ou les diagnostics disponibles. Attention, quand un
528         diagnostic porte le même nom qu'une variable stockée, c'est la variable
529         stockée qui est renvoyée, et le diagnostic est inatteignable.
530         """
531         if key is not None:
532             if self.__algorithm.has_key(key):
533                 return self.__algorithm.get( key )
534             elif self.__StoredInputs.has_key(key):
535                 return self.__StoredInputs[key]
536             elif self.__StoredDiagnostics.has_key(key):
537                 return self.__StoredDiagnostics[key]
538             else:
539                 raise ValueError("The requested key \"%s\" does not exists as an input, a diagnostic or a stored variable."%key)
540         else:
541             allvariables = self.__algorithm.get()
542             allvariables.update( self.__StoredDiagnostics )
543             allvariables.update( self.__StoredInputs )
544             return allvariables
545     
546     def get_available_variables(self):
547         """
548         Renvoie les variables potentiellement utilisables pour l'étude,
549         initialement stockées comme données d'entrées ou dans les algorithmes,
550         identifiés par les chaînes de caractères. L'algorithme doit avoir été
551         préalablement choisi sinon la méthode renvoie "None".
552         """
553         if len( self.__algorithm.keys()) == 0 and len( self.__StoredInputs.keys() ) == 0:
554             return None
555         else:
556             variables = []
557             if len( self.__algorithm.keys()) > 0:
558                 variables.extend( self.__algorithm.get().keys() )
559             if len( self.__StoredInputs.keys() ) > 0:
560                 variables.extend( self.__StoredInputs.keys() )
561             variables.sort()
562             return variables
563     
564     def get_available_algorithms(self):
565         """
566         Renvoie la liste des algorithmes potentiellement utilisables, identifiés
567         par les chaînes de caractères.
568         """
569         files = []
570         for directory in sys.path:
571             if os.path.isdir(os.path.join(directory,"daAlgorithms")):
572                 for fname in os.listdir(os.path.join(directory,"daAlgorithms")):
573                     root, ext = os.path.splitext(fname)
574                     if ext == '.py' and root != '__init__':
575                         files.append(root)
576         files.sort()
577         return files
578         
579     def get_available_diagnostics(self):
580         """
581         Renvoie la liste des diagnostics potentiellement utilisables, identifiés
582         par les chaînes de caractères.
583         """
584         files = []
585         for directory in sys.path:
586             if os.path.isdir(os.path.join(directory,"daDiagnostics")):
587                 for fname in os.listdir(os.path.join(directory,"daDiagnostics")):
588                     root, ext = os.path.splitext(fname)
589                     if ext == '.py' and root != '__init__':
590                         files.append(root)
591         files.sort()
592         return files
593
594     # -----------------------------------------------------------
595     def get_algorithms_main_path(self):
596         """
597         Renvoie le chemin pour le répertoire principal contenant les algorithmes
598         dans un sous-répertoire "daAlgorithms"
599         """
600         return self.__parent
601
602     def add_algorithms_path(self, asPath=None):
603         """
604         Ajoute au chemin de recherche des algorithmes un répertoire dans lequel
605         se trouve un sous-répertoire "daAlgorithms"
606         
607         Remarque : si le chemin a déjà été ajouté pour les diagnostics, il n'est
608         pas indispensable de le rajouter ici.
609         """
610         if not os.path.isdir(asPath):
611             raise ValueError("The given "+asPath+" argument must exist as a directory")
612         if not os.path.isdir(os.path.join(asPath,"daAlgorithms")):
613             raise ValueError("The given \""+asPath+"\" argument must contain a subdirectory named \"daAlgorithms\"")
614         if not os.path.isfile(os.path.join(asPath,"daAlgorithms","__init__.py")):
615             raise ValueError("The given \""+asPath+"/daAlgorithms\" path must contain a file named \"__init__.py\"")
616         sys.path.insert(0, os.path.abspath(asPath))
617         sys.path = list(set(sys.path)) # Conserve en unique exemplaire chaque chemin
618         return 1
619
620     def get_diagnostics_main_path(self):
621         """
622         Renvoie le chemin pour le répertoire principal contenant les diagnostics
623         dans un sous-répertoire "daDiagnostics"
624         """
625         return self.__parent
626
627     def add_diagnostics_path(self, asPath=None):
628         """
629         Ajoute au chemin de recherche des algorithmes un répertoire dans lequel
630         se trouve un sous-répertoire "daDiagnostics"
631         
632         Remarque : si le chemin a déjà été ajouté pour les algorithmes, il n'est
633         pas indispensable de le rajouter ici.
634         """
635         if not os.path.isdir(asPath):
636             raise ValueError("The given "+asPath+" argument must exist as a directory")
637         if not os.path.isdir(os.path.join(asPath,"daDiagnostics")):
638             raise ValueError("The given \""+asPath+"\" argument must contain a subdirectory named \"daDiagnostics\"")
639         if not os.path.isfile(os.path.join(asPath,"daDiagnostics","__init__.py")):
640             raise ValueError("The given \""+asPath+"/daDiagnostics\" path must contain a file named \"__init__.py\"")
641         sys.path.insert(0, os.path.abspath(asPath))
642         sys.path = list(set(sys.path)) # Conserve en unique exemplaire chaque chemin
643         return 1
644
645     # -----------------------------------------------------------
646     def setDataObserver(self,
647             VariableName   = None,
648             HookFunction   = None,
649             HookParameters = None,
650             Scheduler      = None,
651             ):
652         """
653         Permet d'associer un observer à une ou des variables nommées gérées en
654         interne, activable selon des règles définies dans le Scheduler.
655         """
656         # 
657         if type( self.__algorithm ) is dict:
658             raise ValueError("No observer can be build before choosing an algorithm.")
659         #
660         # Vérification du nom de variable et typage
661         # -----------------------------------------
662         if type( VariableName ) is str:
663             VariableNames = [VariableName,]
664         elif type( VariableName ) is list:
665             VariableNames = map( str, VariableName )
666         else:
667             raise ValueError("The observer requires a name or a list of names of variables.")
668         #
669         # Association interne de l'observer à la variable
670         # -----------------------------------------------
671         for n in VariableNames:
672             if not self.__algorithm.has_key( n ):
673                 raise ValueError("An observer requires to be set on a variable named %s which does not exist."%n)
674         else:
675             self.__algorithm.StoredVariables[ n ].setDataObserver(
676                 Scheduler      = Scheduler,
677                 HookFunction   = HookFunction,
678                 HookParameters = HookParameters,
679                 )
680
681     def removeDataObserver(self,
682             VariableName   = None,
683             HookFunction   = None,
684             ):
685         """
686         Permet de retirer un observer à une ou des variable nommée.
687         """
688         # 
689         if type( self.__algorithm ) is dict:
690             raise ValueError("No observer can be removed before choosing an algorithm.")
691         #
692         # Vérification du nom de variable et typage
693         # -----------------------------------------
694         if type( VariableName ) is str:
695             VariableNames = [VariableName,]
696         elif type( VariableName ) is list:
697             VariableNames = map( str, VariableName )
698         else:
699             raise ValueError("The observer requires a name or a list of names of variables.")
700         #
701         # Association interne de l'observer à la variable
702         # -----------------------------------------------
703         for n in VariableNames:
704             if not self.__algorithm.has_key( n ):
705                 raise ValueError("An observer requires to be removed on a variable named %s which does not exist."%n)
706         else:
707             self.__algorithm.StoredVariables[ n ].removeDataObserver(
708                 HookFunction   = HookFunction,
709                 )
710
711     # -----------------------------------------------------------
712     def setDebug(self, level=10):
713         """
714         Utiliser par exemple "import logging ; level = logging.DEBUG" avant cet
715         appel pour changer le niveau de verbosité, avec :
716         NOTSET=0 < DEBUG=10 < INFO=20 < WARNING=30 < ERROR=40 < CRITICAL=50
717         """
718         import logging
719         log = logging.getLogger()
720         log.setLevel( level )
721
722     def unsetDebug(self):
723         """
724         Remet le logger au niveau par défaut
725         """
726         import logging
727         log = logging.getLogger()
728         log.setLevel( logging.WARNING )
729
730     def prepare_to_pickle(self):
731         self.__algorithmFile = None
732         self.__diagnosticFile = None
733         self.__H  = {}
734
735 # ==============================================================================
736 if __name__ == "__main__":
737     print '\n AUTODIAGNOSTIC \n'
738     
739     ADD = AssimilationStudy("Ma premiere etude BLUE")
740     
741     ADD.setBackground         (asVector     = [0, 1, 2])
742     ADD.setBackgroundError    (asCovariance = "1 0 0;0 1 0;0 0 1")
743     ADD.setObservation        (asVector     = [0.5, 1.5, 2.5])
744     ADD.setObservationError   (asCovariance = "1 0 0;0 1 0;0 0 1")
745     ADD.setObservationOperator(asMatrix     = "1 0 0;0 1 0;0 0 1")
746     
747     ADD.setAlgorithm(choice="Blue")
748     
749     ADD.analyze()
750     
751     print "Nombre d'analyses  :", ADD.get("Analysis").stepnumber()
752     print "Ebauche            :", [0, 1, 2]
753     print "Observation        :", [0.5, 1.5, 2.5]
754     print "Demi-somme         :", list((numpy.array([0, 1, 2])+numpy.array([0.5, 1.5, 2.5]))/2)
755     print "  qui doit être identique à :"
756     print "Analyse résultante :", ADD.get("Analysis").valueserie(0)
757     print "Innovation         :", ADD.get("Innovation").valueserie(0)
758     print
759     
760     print "Algorithmes disponibles.......................:", ADD.get_available_algorithms()
761     # print " Chemin des algorithmes.....................:", ADD.get_algorithms_main_path()
762     print "Diagnostics types disponibles.................:", ADD.get_available_diagnostics()
763     # print " Chemin des diagnostics.....................:", ADD.get_diagnostics_main_path()
764     print "Variables disponibles.........................:", ADD.get_available_variables()
765     print
766
767     ADD.setDiagnostic("RMS", "Ma RMS")
768     
769     liste = ADD.get().keys()
770     liste.sort()
771     print "Variables et diagnostics nommés disponibles...:", liste
772
773     print
774     print "Exemple de mise en place d'un observeur :"
775     def obs(var=None,info=None):
776         print "  ---> Mise en oeuvre de l'observer"
777         print "       var  =",var.valueserie(-1)
778         print "       info =",info
779     ADD.setDataObserver( 'Analysis', HookFunction=obs, Scheduler = [2, 4], HookParameters = "Second observer")
780     # Attention, il faut décaler le stockage de 1 pour suivre le pas interne
781     # car le pas 0 correspond à l'analyse ci-dessus.
782     for i in range(1,6):
783         print
784         print "Action sur la variable observée, étape :",i
785         ADD.get('Analysis').store( [i, i, i] )
786     print
787
788     print "Mise en debug et hors debug"
789     print "Nombre d'analyses  :", ADD.get("Analysis").stepnumber()
790     ADD.setDebug()
791     ADD.analyze()
792     ADD.unsetDebug()
793     print "Nombre d'analyses  :", ADD.get("Analysis").stepnumber()
794     ADD.analyze()
795     print "Nombre d'analyses  :", ADD.get("Analysis").stepnumber()
796     print