Salome HOME
7640288c163c3f8fb1d78f54b6d556b8663342cd
[modules/adao.git] / src / daComposant / daCore / AssimilationStudy.py
1 #-*-coding:iso-8859-1-*-
2 #
3 # Copyright (C) 2008-2013 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     Classe principale pour la préparation, la réalisation et la restitution de
25     calculs d'assimilation de données.
26     
27     Ce module est destiné à être appelé par AssimilationStudy pour constituer
28     les objets élémentaires de l'étude.
29 """
30 __author__ = "Jean-Philippe ARGAUD"
31
32 import os, sys
33 import numpy
34 import Logging ; Logging.Logging() # A importer en premier
35 import scipy.optimize
36 import Persistence
37 from BasicObjects import Operator, Covariance
38 from PlatformInfo import uniq
39
40 # ==============================================================================
41 class AssimilationStudy:
42     """
43     Cette classe sert d'interface pour l'utilisation de l'assimilation de
44     données. Elle contient les méthodes ou accesseurs nécessaires à la
45     construction d'un calcul d'assimilation.
46     """
47     def __init__(self, name=""):
48         """
49         Prévoit de conserver l'ensemble des variables nécssaires à un algorithme
50         élémentaire. Ces variables sont ensuite disponibles pour implémenter un
51         algorithme élémentaire particulier.
52
53         - Background : vecteur Xb
54         - Observation : vecteur Y (potentiellement temporel) d'observations
55         - State : vecteur d'état dont une partie est le vecteur de contrôle.
56           Cette information n'est utile que si l'on veut faire des calculs sur
57           l'état complet, mais elle n'est pas indispensable pour l'assimilation.
58         - Control : vecteur X contenant toutes les variables de contrôle, i.e.
59           les paramètres ou l'état dont on veut estimer la valeur pour obtenir
60           les observations
61         - ObservationOperator...: opérateur d'observation H
62
63         Les observations présentent une erreur dont la matrice de covariance est
64         R. L'ébauche du vecteur de contrôle présente une erreur dont la matrice
65         de covariance est B.
66         """
67         self.__name = str(name)
68         self.__Xb  = None
69         self.__Y   = None
70         self.__U   = None
71         self.__B   = None
72         self.__R   = None
73         self.__Q   = None
74         self.__HO  = {}
75         self.__EM  = {}
76         self.__CM  = {}
77         #
78         self.__X  = Persistence.OneVector()
79         self.__Parameters        = {}
80         self.__StoredDiagnostics = {}
81         self.__StoredInputs      = {}
82         #
83         # Variables temporaires
84         self.__algorithm         = {}
85         self.__algorithmFile     = None
86         self.__algorithmName     = None
87         self.__diagnosticFile    = None
88         #
89         # Récupère le chemin du répertoire parent et l'ajoute au path
90         # (Cela complète l'action de la classe PathManagement dans PlatformInfo,
91         # qui est activée dans Persistence)
92         self.__parent = os.path.abspath(os.path.join(os.path.dirname(__file__),".."))
93         sys.path.insert(0, self.__parent)
94         sys.path = uniq( sys.path ) # Conserve en unique exemplaire chaque chemin
95
96     # ---------------------------------------------------------
97     def setBackground(self,
98             asVector           = None,
99             asPersistentVector = None,
100             Scheduler          = None,
101             toBeStored         = False,
102             ):
103         """
104         Permet de définir l'estimation a priori :
105         - asVector : entrée des données, comme un vecteur compatible avec le
106           constructeur de numpy.matrix
107         - asPersistentVector : entrée des données, comme un vecteur de type
108           persistent contruit avec la classe ad-hoc "Persistence"
109         - Scheduler est le contrôle temporel des données
110         - toBeStored : booléen indiquant si la donnée d'entrée est sauvée pour
111           être rendue disponible au même titre que les variables de calcul
112         """
113         if asVector is not None:
114             if type( asVector ) is type( numpy.matrix([]) ):
115                 self.__Xb = numpy.matrix( asVector.A1, numpy.float ).T
116             else:
117                 self.__Xb = numpy.matrix( asVector,    numpy.float ).T
118         elif asPersistentVector is not None:
119             if type(asPersistentVector) in [type([]),type(()),type(numpy.array([])),type(numpy.matrix([]))]:
120                 self.__Xb = Persistence.OneVector("Background", basetype=numpy.matrix)
121                 for member in asPersistentVector:
122                     self.__Xb.store( numpy.matrix( numpy.asmatrix(member).A1, numpy.float ).T )
123             else:
124                 self.__Xb = asPersistentVector
125         else:
126             raise ValueError("Error: improperly defined background, it requires at minima either a vector, a list/tuple of vectors or a persistent object")
127         if toBeStored:
128            self.__StoredInputs["Background"] = self.__Xb
129         return 0
130     
131     def setBackgroundError(self,
132             asCovariance  = None,
133             asEyeByScalar = None,
134             asEyeByVector = None,
135             toBeStored    = False,
136             ):
137         """
138         Permet de définir la covariance des erreurs d'ébauche :
139         - asCovariance : entrée des données, comme une matrice compatible avec
140           le constructeur de numpy.matrix
141         - asEyeByScalar : entrée des données comme un seul scalaire de variance,
142           multiplicatif d'une matrice de corrélation identité, aucune matrice
143           n'étant donc explicitement à donner
144         - asEyeByVector : entrée des données comme un seul vecteur de variance,
145           à mettre sur la diagonale d'une matrice de corrélation, aucune matrice
146           n'étant donc explicitement à donner
147         - toBeStored : booléen indiquant si la donnée d'entrée est sauvée pour
148           être rendue disponible au même titre que les variables de calcul
149         """
150         self.__B = Covariance(
151             name          = "BackgroundError",
152             asCovariance  = asCovariance,
153             asEyeByScalar = asEyeByScalar,
154             asEyeByVector = asEyeByVector,
155             )
156         if toBeStored:
157             self.__StoredInputs["BackgroundError"] = self.__B
158         return 0
159
160     # -----------------------------------------------------------
161     def setObservation(self,
162             asVector           = None,
163             asPersistentVector = None,
164             Scheduler          = None,
165             toBeStored         = False,
166             ):
167         """
168         Permet de définir les observations :
169         - asVector : entrée des données, comme un vecteur compatible avec le
170           constructeur de numpy.matrix
171         - asPersistentVector : entrée des données, comme un vecteur de type
172           persistent contruit avec la classe ad-hoc "Persistence"
173         - Scheduler est le contrôle temporel des données disponibles
174         - toBeStored : booléen indiquant si la donnée d'entrée est sauvée pour
175           être rendue disponible au même titre que les variables de calcul
176         """
177         if asVector is not None:
178             if type( asVector ) is type( numpy.matrix([]) ):
179                 self.__Y = numpy.matrix( asVector.A1, numpy.float ).T
180             else:
181                 self.__Y = numpy.matrix( asVector,    numpy.float ).T
182         elif asPersistentVector is not None:
183             if type(asPersistentVector) in [type([]),type(()),type(numpy.array([])),type(numpy.matrix([]))]:
184                 self.__Y = Persistence.OneVector("Observation", basetype=numpy.matrix)
185                 for member in asPersistentVector:
186                     self.__Y.store( numpy.matrix( numpy.asmatrix(member).A1, numpy.float ).T )
187             else:
188                 self.__Y = asPersistentVector
189         else:
190             raise ValueError("Error: improperly defined observations, it requires at minima either a vector, a list/tuple of vectors or a persistent object")
191         if toBeStored:
192             self.__StoredInputs["Observation"] = self.__Y
193         return 0
194
195     def setObservationError(self,
196             asCovariance  = None,
197             asEyeByScalar = None,
198             asEyeByVector = None,
199             toBeStored    = False,
200             ):
201         """
202         Permet de définir la covariance des erreurs d'observations :
203         - asCovariance : entrée des données, comme une matrice compatible avec
204           le constructeur de numpy.matrix
205         - asEyeByScalar : entrée des données comme un seul scalaire de variance,
206           multiplicatif d'une matrice de corrélation identité, aucune matrice
207           n'étant donc explicitement à donner
208         - asEyeByVector : entrée des données comme un seul vecteur de variance,
209           à mettre sur la diagonale d'une matrice de corrélation, aucune matrice
210           n'étant donc explicitement à donner
211         - toBeStored : booléen indiquant si la donnée d'entrée est sauvée pour
212           être rendue disponible au même titre que les variables de calcul
213         """
214         self.__R = Covariance(
215             name          = "ObservationError",
216             asCovariance  = asCovariance,
217             asEyeByScalar = asEyeByScalar,
218             asEyeByVector = asEyeByVector,
219             )
220         if toBeStored:
221             self.__StoredInputs["ObservationError"] = self.__R
222         return 0
223
224     def setObservationOperator(self,
225             asFunction = None,
226             asMatrix   = None,
227             appliedToX = None,
228             toBeStored = False,
229             ):
230         """
231         Permet de définir un opérateur d'observation H. L'ordre de priorité des
232         définitions et leur sens sont les suivants :
233         - si asFunction["Tangent"] et asFunction["Adjoint"] ne sont pas None
234           alors on définit l'opérateur à l'aide de fonctions. Si la fonction
235           "Direct" n'est pas définie, on prend la fonction "Tangent".
236           Si "useApproximatedDerivatives" est vrai, on utilise une approximation
237           des opérateurs tangents et adjoints. On utilise par défaut des
238           différences finies non centrées ou centrées (si "withCenteredDF" est
239           vrai) avec un incrément multiplicatif "withIncrement" de 1% autour
240           du point courant ou sur le point fixe "withdX".
241         - si les fonctions ne sont pas disponibles et si asMatrix n'est pas
242           None, alors on définit l'opérateur "Direct" et "Tangent" à l'aide de
243           la matrice, et l'opérateur "Adjoint" à l'aide de la transposée. La
244           matrice fournie doit être sous une forme compatible avec le
245           constructeur de numpy.matrix.
246         - si l'argument "appliedToX" n'est pas None, alors on définit, pour des
247           X divers, l'opérateur par sa valeur appliquée à cet X particulier,
248           sous la forme d'un dictionnaire appliedToX[NAME] avec NAME un nom.
249           L'opérateur doit néanmoins déjà avoir été défini comme d'habitude.
250         - toBeStored : booléen indiquant si la donnée d'entrée est sauvée pour
251           être rendue disponible au même titre que les variables de calcul
252         L'argument "asFunction" peut prendre la forme complète suivante, avec
253         les valeurs par défaut standards :
254           asFunction = {"Direct":None, "Tangent":None, "Adjoint":None,
255                         "useApproximatedDerivatives":False,
256                         "withCenteredDF"            :False,
257                         "withIncrement"             :0.01,
258                         "withdX"                    :None,
259                         "withAvoidingRedundancy"    :True,
260                         "withToleranceInRedundancy" :1.e-18,
261                         "withLenghtOfRedundancy"    :-1,
262                        }
263         """
264         if (type(asFunction) is type({})) and \
265                 asFunction.has_key("useApproximatedDerivatives") and bool(asFunction["useApproximatedDerivatives"]) and \
266                 asFunction.has_key("Direct") and (asFunction["Direct"] is not None):
267             if not asFunction.has_key("withCenteredDF"):            asFunction["withCenteredDF"]            = False
268             if not asFunction.has_key("withIncrement"):             asFunction["withIncrement"]             = 0.01
269             if not asFunction.has_key("withdX"):                    asFunction["withdX"]                    = None
270             if not asFunction.has_key("withAvoidingRedundancy"):    asFunction["withAvoidingRedundancy"]    = True
271             if not asFunction.has_key("withToleranceInRedundancy"): asFunction["withToleranceInRedundancy"] = 1.e-18
272             if not asFunction.has_key("withLenghtOfRedundancy"):    asFunction["withLenghtOfRedundancy"]    = -1
273             from daNumerics.ApproximatedDerivatives import FDApproximation
274             FDA = FDApproximation(
275                 Function              = asFunction["Direct"],
276                 centeredDF            = asFunction["withCenteredDF"],
277                 increment             = asFunction["withIncrement"],
278                 dX                    = asFunction["withdX"],
279                 avoidingRedundancy    = asFunction["withAvoidingRedundancy"],
280                 toleranceInRedundancy = asFunction["withToleranceInRedundancy"],
281                 lenghtOfRedundancy    = asFunction["withLenghtOfRedundancy"],
282                 )
283             self.__HO["Direct"]  = Operator( fromMethod = FDA.DirectOperator  )
284             self.__HO["Tangent"] = Operator( fromMethod = FDA.TangentOperator )
285             self.__HO["Adjoint"] = Operator( fromMethod = FDA.AdjointOperator )
286         elif (type(asFunction) is type({})) and \
287                 asFunction.has_key("Tangent") and asFunction.has_key("Adjoint") and \
288                 (asFunction["Tangent"] is not None) and (asFunction["Adjoint"] is not None):
289             if not asFunction.has_key("Direct") or (asFunction["Direct"] is None):
290                 self.__HO["Direct"] = Operator( fromMethod = asFunction["Tangent"] )
291             else:
292                 self.__HO["Direct"] = Operator( fromMethod = asFunction["Direct"]  )
293             self.__HO["Tangent"]    = Operator( fromMethod = asFunction["Tangent"] )
294             self.__HO["Adjoint"]    = Operator( fromMethod = asFunction["Adjoint"] )
295         elif asMatrix is not None:
296             matrice = numpy.matrix( asMatrix, numpy.float )
297             self.__HO["Direct"]  = Operator( fromMatrix = matrice )
298             self.__HO["Tangent"] = Operator( fromMatrix = matrice )
299             self.__HO["Adjoint"] = Operator( fromMatrix = matrice.T )
300             del matrice
301         else:
302             raise ValueError("Improperly defined observation operator, it requires at minima either a matrix, a Direct for approximate derivatives or a Tangent/Adjoint pair.")
303         #
304         if appliedToX is not None:
305             self.__HO["AppliedToX"] = {}
306             if type(appliedToX) is not dict:
307                 raise ValueError("Error: observation operator defined by \"appliedToX\" need a dictionary as argument.")
308             for key in appliedToX.keys():
309                 if type( appliedToX[key] ) is type( numpy.matrix([]) ):
310                     # Pour le cas où l'on a une vraie matrice
311                     self.__HO["AppliedToX"][key] = numpy.matrix( appliedToX[key].A1, numpy.float ).T
312                 elif type( appliedToX[key] ) is type( numpy.array([]) ) and len(appliedToX[key].shape) > 1:
313                     # Pour le cas où l'on a un vecteur représenté en array avec 2 dimensions
314                     self.__HO["AppliedToX"][key] = numpy.matrix( appliedToX[key].reshape(len(appliedToX[key]),), numpy.float ).T
315                 else:
316                     self.__HO["AppliedToX"][key] = numpy.matrix( appliedToX[key],    numpy.float ).T
317         else:
318             self.__HO["AppliedToX"] = None
319         #
320         if toBeStored:
321             self.__StoredInputs["ObservationOperator"] = self.__HO
322         return 0
323
324     # -----------------------------------------------------------
325     def setEvolutionModel(self,
326             asFunction = None,
327             asMatrix   = None,
328             Scheduler  = None,
329             toBeStored = False,
330             ):
331         """
332         Permet de définir un opérateur d'évolution M. L'ordre de priorité des
333         définitions et leur sens sont les suivants :
334         - si asFunction["Tangent"] et asFunction["Adjoint"] ne sont pas None
335           alors on définit l'opérateur à l'aide de fonctions. Si la fonction
336           "Direct" n'est pas définie, on prend la fonction "Tangent".
337           Si "useApproximatedDerivatives" est vrai, on utilise une approximation
338           des opérateurs tangents et adjoints. On utilise par défaut des
339           différences finies non centrées ou centrées (si "withCenteredDF" est
340           vrai) avec un incrément multiplicatif "withIncrement" de 1% autour
341           du point courant ou sur le point fixe "withdX".
342         - si les fonctions ne sont pas disponibles et si asMatrix n'est pas
343           None, alors on définit l'opérateur "Direct" et "Tangent" à l'aide de
344           la matrice, et l'opérateur "Adjoint" à l'aide de la transposée. La
345           matrice fournie doit être sous une forme compatible avec le
346           constructeur de numpy.matrix.
347         - toBeStored : booléen indiquant si la donnée d'entrée est sauvée pour
348           être rendue disponible au même titre que les variables de calcul
349         L'argument "asFunction" peut prendre la forme complète suivante, avec
350         les valeurs par défaut standards :
351           asFunction = {"Direct":None, "Tangent":None, "Adjoint":None,
352                         "useApproximatedDerivatives":False,
353                         "withCenteredDF"            :False,
354                         "withIncrement"             :0.01,
355                         "withdX"                    :None,
356                         "withAvoidingRedundancy"    :True,
357                         "withToleranceInRedundancy" :1.e-18,
358                         "withLenghtOfRedundancy"    :-1,
359                        }
360         """
361         if (type(asFunction) is type({})) and \
362                 asFunction.has_key("useApproximatedDerivatives") and bool(asFunction["useApproximatedDerivatives"]) and \
363                 asFunction.has_key("Direct") and (asFunction["Direct"] is not None):
364             if not asFunction.has_key("withCenteredDF"):            asFunction["withCenteredDF"]            = False
365             if not asFunction.has_key("withIncrement"):             asFunction["withIncrement"]             = 0.01
366             if not asFunction.has_key("withdX"):                    asFunction["withdX"]                    = None
367             if not asFunction.has_key("withAvoidingRedundancy"):    asFunction["withAvoidingRedundancy"]    = True
368             if not asFunction.has_key("withToleranceInRedundancy"): asFunction["withToleranceInRedundancy"] = 1.e-18
369             if not asFunction.has_key("withLenghtOfRedundancy"):    asFunction["withLenghtOfRedundancy"]    = -1
370             from daNumerics.ApproximatedDerivatives import FDApproximation
371             FDA = FDApproximation(
372                 Function              = asFunction["Direct"],
373                 centeredDF            = asFunction["withCenteredDF"],
374                 increment             = asFunction["withIncrement"],
375                 dX                    = asFunction["withdX"],
376                 avoidingRedundancy    = asFunction["withAvoidingRedundancy"],
377                 toleranceInRedundancy = asFunction["withToleranceInRedundancy"],
378                 lenghtOfRedundancy    = asFunction["withLenghtOfRedundancy"],
379                 )
380             self.__EM["Direct"]  = Operator( fromMethod = FDA.DirectOperator  )
381             self.__EM["Tangent"] = Operator( fromMethod = FDA.TangentOperator )
382             self.__EM["Adjoint"] = Operator( fromMethod = FDA.AdjointOperator )
383         elif (type(asFunction) is type({})) and \
384                 asFunction.has_key("Tangent") and asFunction.has_key("Adjoint") and \
385                 (asFunction["Tangent"] is not None) and (asFunction["Adjoint"] is not None):
386             if not asFunction.has_key("Direct") or (asFunction["Direct"] is None):
387                 self.__EM["Direct"] = Operator( fromMethod = asFunction["Tangent"]  )
388             else:
389                 self.__EM["Direct"] = Operator( fromMethod = asFunction["Direct"]  )
390             self.__EM["Tangent"]    = Operator( fromMethod = asFunction["Tangent"] )
391             self.__EM["Adjoint"]    = Operator( fromMethod = asFunction["Adjoint"] )
392         elif asMatrix is not None:
393             matrice = numpy.matrix( asMatrix, numpy.float )
394             self.__EM["Direct"]  = Operator( fromMatrix = matrice )
395             self.__EM["Tangent"] = Operator( fromMatrix = matrice )
396             self.__EM["Adjoint"] = Operator( fromMatrix = matrice.T )
397             del matrice
398         else:
399             raise ValueError("Improperly defined evolution model, it requires at minima either a matrix, a Direct for approximate derivatives or a Tangent/Adjoint pair.")
400         #
401         if toBeStored:
402             self.__StoredInputs["EvolutionModel"] = self.__EM
403         return 0
404
405     def setEvolutionError(self,
406             asCovariance  = None,
407             asEyeByScalar = None,
408             asEyeByVector = None,
409             toBeStored    = False,
410             ):
411         """
412         Permet de définir la covariance des erreurs de modèle :
413         - asCovariance : entrée des données, comme une matrice compatible avec
414           le constructeur de numpy.matrix
415         - asEyeByScalar : entrée des données comme un seul scalaire de variance,
416           multiplicatif d'une matrice de corrélation identité, aucune matrice
417           n'étant donc explicitement à donner
418         - asEyeByVector : entrée des données comme un seul vecteur de variance,
419           à mettre sur la diagonale d'une matrice de corrélation, aucune matrice
420           n'étant donc explicitement à donner
421         - toBeStored : booléen indiquant si la donnée d'entrée est sauvée pour
422           être rendue disponible au même titre que les variables de calcul
423         """
424         self.__Q = Covariance(
425             name          = "ObservationError",
426             asCovariance  = asCovariance,
427             asEyeByScalar = asEyeByScalar,
428             asEyeByVector = asEyeByVector,
429             )
430         if toBeStored:
431             self.__StoredInputs["EvolutionError"] = self.__Q
432         return 0
433
434     # -----------------------------------------------------------
435     def setControlModel(self,
436             asFunction = {"Direct":None, "Tangent":None, "Adjoint":None,
437                           "useApproximatedDerivatives":False,
438                           "withCenteredDF"            :False,
439                           "withIncrement"             :0.01,
440                           "withdX"                    :None,
441                          },
442             asMatrix   = None,
443             Scheduler  = None,
444             toBeStored = False,
445             ):
446         """
447         Permet de définir un opérateur de controle C. L'ordre de priorité des
448         définitions et leur sens sont les suivants :
449         - si asFunction["Tangent"] et asFunction["Adjoint"] ne sont pas None
450           alors on définit l'opérateur à l'aide de fonctions. Si la fonction
451           "Direct" n'est pas définie, on prend la fonction "Tangent".
452           Si "useApproximatedDerivatives" est vrai, on utilise une approximation
453           des opérateurs tangents et adjoints. On utilise par défaut des
454           différences finies non centrées ou centrées (si "withCenteredDF" est
455           vrai) avec un incrément multiplicatif "withIncrement" de 1% autour
456           du point courant ou sur le point fixe "withdX".
457         - si les fonctions ne sont pas disponibles et si asMatrix n'est pas
458           None, alors on définit l'opérateur "Direct" et "Tangent" à l'aide de
459           la matrice, et l'opérateur "Adjoint" à l'aide de la transposée. La
460           matrice fournie doit être sous une forme compatible avec le
461           constructeur de numpy.matrix.
462         - toBeStored : booléen indiquant si la donnée d'entrée est sauvée pour
463           être rendue disponible au même titre que les variables de calcul
464         """
465         if (type(asFunction) is type({})) and \
466                 asFunction.has_key("useApproximatedDerivatives") and bool(asFunction["useApproximatedDerivatives"]) and \
467                 asFunction.has_key("Direct") and (asFunction["Direct"] is not None):
468             if not asFunction.has_key("withCenteredDF"): asFunction["withCenteredDF"] = False
469             if not asFunction.has_key("withIncrement"):  asFunction["withIncrement"]  = 0.01
470             if not asFunction.has_key("withdX"):         asFunction["withdX"]         = None
471             from daNumerics.ApproximatedDerivatives import FDApproximation
472             FDA = FDApproximation(
473                 Function   = asFunction["Direct"],
474                 centeredDF = asFunction["withCenteredDF"],
475                 increment  = asFunction["withIncrement"],
476                 dX         = asFunction["withdX"] )
477             self.__CM["Direct"]  = Operator( fromMethod = FDA.DirectOperator  )
478             self.__CM["Tangent"] = Operator( fromMethod = FDA.TangentOperator )
479             self.__CM["Adjoint"] = Operator( fromMethod = FDA.AdjointOperator )
480         elif (type(asFunction) is type({})) and \
481                 asFunction.has_key("Tangent") and asFunction.has_key("Adjoint") and \
482                 (asFunction["Tangent"] is not None) and (asFunction["Adjoint"] is not None):
483             if not asFunction.has_key("Direct") or (asFunction["Direct"] is None):
484                 self.__CM["Direct"] = Operator( fromMethod = asFunction["Tangent"]  )
485             else:
486                 self.__CM["Direct"] = Operator( fromMethod = asFunction["Direct"]  )
487             self.__CM["Tangent"]    = Operator( fromMethod = asFunction["Tangent"] )
488             self.__CM["Adjoint"]    = Operator( fromMethod = asFunction["Adjoint"] )
489         elif asMatrix is not None:
490             matrice = numpy.matrix( asMatrix, numpy.float )
491             self.__CM["Direct"]  = Operator( fromMatrix = matrice )
492             self.__CM["Tangent"] = Operator( fromMatrix = matrice )
493             self.__CM["Adjoint"] = Operator( fromMatrix = matrice.T )
494             del matrice
495         else:
496             raise ValueError("Improperly defined input control model, it requires at minima either a matrix, a Direct for approximate derivatives or a Tangent/Adjoint pair.")
497         #
498         if toBeStored:
499             self.__StoredInputs["ControlModel"] = self.__CM
500         return 0
501
502     def setControlInput(self,
503             asVector           = None,
504             asPersistentVector = None,
505             Scheduler          = None,
506             toBeStored         = False,
507             ):
508         """
509         Permet de définir le controle en entree :
510         - asVector : entrée des données, comme un vecteur compatible avec le
511           constructeur de numpy.matrix
512         - asPersistentVector : entrée des données, comme un vecteur de type
513           persistent contruit avec la classe ad-hoc "Persistence"
514         - Scheduler est le contrôle temporel des données disponibles
515         - toBeStored : booléen indiquant si la donnée d'entrée est sauvée pour
516           être rendue disponible au même titre que les variables de calcul
517         """
518         if asVector is not None:
519             if isinstance(asVector,numpy.matrix):
520                 self.__U = numpy.matrix( asVector.A1, numpy.float ).T
521             else:
522                 self.__U = numpy.matrix( asVector,    numpy.float ).T
523         elif asPersistentVector is not None:
524             if type(asPersistentVector) in [type([]),type(()),type(numpy.array([])),type(numpy.matrix([]))]:
525                 self.__U = Persistence.OneVector("ControlInput", basetype=numpy.matrix)
526                 for member in asPersistentVector:
527                     self.__U.store( numpy.matrix( numpy.asmatrix(member).A1, numpy.float ).T )
528             else:
529                 self.__U = asPersistentVector
530         else:
531             raise ValueError("Error: improperly defined control input, it requires at minima either a vector, a list/tuple of vectors or a persistent object")
532         if toBeStored:
533             self.__StoredInputs["ControlInput"] = self.__U
534         return 0
535
536     # -----------------------------------------------------------
537     def setControls (self,
538             asVector = None,
539             toBeStored   = False,
540             ):
541         """
542         Permet de définir la valeur initiale du vecteur X contenant toutes les
543         variables de contrôle, i.e. les paramètres ou l'état dont on veut
544         estimer la valeur pour obtenir les observations. C'est utile pour un
545         algorithme itératif/incrémental.
546         - asVector : entrée des données, comme un vecteur compatible avec le
547           constructeur de numpy.matrix.
548         - toBeStored : booléen indiquant si la donnée d'entrée est sauvée pour
549           être rendue disponible au même titre que les variables de calcul
550         """
551         if asVector is not None:
552             self.__X.store( asVector )
553         if toBeStored:
554             self.__StoredInputs["Controls"] = self.__X
555         return 0
556
557     # -----------------------------------------------------------
558     def setAlgorithm(self, choice = None ):
559         """
560         Permet de sélectionner l'algorithme à utiliser pour mener à bien l'étude
561         d'assimilation. L'argument est un champ caractère se rapportant au nom
562         d'un fichier contenu dans "../daAlgorithms" et réalisant l'opération
563         d'assimilation sur les arguments fixes.
564         """
565         if choice is None:
566             raise ValueError("Error: algorithm choice has to be given")
567         if self.__algorithmName is not None:
568             raise ValueError("Error: algorithm choice has already been done as \"%s\", it can't be changed."%self.__algorithmName)
569         daDirectory = "daAlgorithms"
570         #
571         # Recherche explicitement le fichier complet
572         # ------------------------------------------
573         module_path = None
574         for directory in sys.path:
575             if os.path.isfile(os.path.join(directory, daDirectory, str(choice)+'.py')):
576                 module_path = os.path.abspath(os.path.join(directory, daDirectory))
577         if module_path is None:
578             raise ImportError("No algorithm module named \"%s\" was found in a \"%s\" subdirectory\n             The search path is %s"%(choice, daDirectory, sys.path))
579         #
580         # Importe le fichier complet comme un module
581         # ------------------------------------------
582         try:
583             sys_path_tmp = sys.path ; sys.path.insert(0,module_path)
584             self.__algorithmFile = __import__(str(choice), globals(), locals(), [])
585             self.__algorithmName = str(choice)
586             sys.path = sys_path_tmp ; del sys_path_tmp
587         except ImportError, e:
588             raise ImportError("The module named \"%s\" was found, but is incorrect at the import stage.\n             The import error message is: %s"%(choice,e))
589         #
590         # Instancie un objet du type élémentaire du fichier
591         # -------------------------------------------------
592         self.__algorithm = self.__algorithmFile.ElementaryAlgorithm()
593         self.__StoredInputs["AlgorithmName"] = self.__algorithmName
594         return 0
595
596     def setAlgorithmParameters(self, asDico=None):
597         """
598         Permet de définir les paramètres de l'algorithme, sous la forme d'un
599         dictionnaire.
600         """
601         if asDico is not None:
602             self.__Parameters.update( dict( asDico ) )
603         #
604         self.__StoredInputs["AlgorithmParameters"] = self.__Parameters
605         return 0
606     
607     def getAlgorithmParameters(self, noDetails=True):
608         """
609         Renvoie la liste des paramètres requis selon l'algorithme
610         """
611         return self.__algorithm.getRequiredParameters(noDetails)
612
613     # -----------------------------------------------------------
614     def setDiagnostic(self, choice = None, name = "", unit = "", basetype = None, parameters = {} ):
615         """
616         Permet de sélectionner un diagnostic a effectuer.
617         """
618         if choice is None:
619             raise ValueError("Error: diagnostic choice has to be given")
620         daDirectory = "daDiagnostics"
621         #
622         # Recherche explicitement le fichier complet
623         # ------------------------------------------
624         module_path = None
625         for directory in sys.path:
626             if os.path.isfile(os.path.join(directory, daDirectory, str(choice)+'.py')):
627                 module_path = os.path.abspath(os.path.join(directory, daDirectory))
628         if module_path is None:
629             raise ImportError("No diagnostic module named \"%s\" was found in a \"%s\" subdirectory\n             The search path is %s"%(choice, daDirectory, sys.path))
630         #
631         # Importe le fichier complet comme un module
632         # ------------------------------------------
633         try:
634             sys_path_tmp = sys.path ; sys.path.insert(0,module_path)
635             self.__diagnosticFile = __import__(str(choice), globals(), locals(), [])
636             sys.path = sys_path_tmp ; del sys_path_tmp
637         except ImportError, e:
638             raise ImportError("The module named \"%s\" was found, but is incorrect at the import stage.\n             The import error message is: %s"%(choice,e))
639         #
640         # Instancie un objet du type élémentaire du fichier
641         # -------------------------------------------------
642         if self.__StoredInputs.has_key(name):
643             raise ValueError("A default input with the same name \"%s\" already exists."%str(name))
644         elif self.__StoredDiagnostics.has_key(name):
645             raise ValueError("A diagnostic with the same name \"%s\" already exists."%str(name))
646         else:
647             self.__StoredDiagnostics[name] = self.__diagnosticFile.ElementaryDiagnostic(
648                 name       = name,
649                 unit       = unit,
650                 basetype   = basetype,
651                 parameters = parameters )
652         return 0
653
654     # -----------------------------------------------------------
655     def shape_validate(self):
656         """
657         Validation de la correspondance correcte des tailles des variables et
658         des matrices s'il y en a.
659         """
660         if self.__Xb is None:                  __Xb_shape = (0,)
661         elif hasattr(self.__Xb,"size"):        __Xb_shape = (self.__Xb.size,)
662         elif hasattr(self.__Xb,"shape"):
663             if type(self.__Xb.shape) is tuple: __Xb_shape = self.__Xb.shape
664             else:                              __Xb_shape = self.__Xb.shape()
665         else: raise TypeError("Xb has no attribute of shape: problem !")
666         #
667         if self.__Y is None:                  __Y_shape = (0,)
668         elif hasattr(self.__Y,"size"):        __Y_shape = (self.__Y.size,)
669         elif hasattr(self.__Y,"shape"):
670             if type(self.__Y.shape) is tuple: __Y_shape = self.__Y.shape
671             else:                             __Y_shape = self.__Y.shape()
672         else: raise TypeError("Y has no attribute of shape: problem !")
673         #
674         if self.__U is None:                  __U_shape = (0,)
675         elif hasattr(self.__U,"size"):        __U_shape = (self.__U.size,)
676         elif hasattr(self.__U,"shape"):
677             if type(self.__U.shape) is tuple: __U_shape = self.__U.shape
678             else:                             __U_shape = self.__U.shape()
679         else: raise TypeError("U has no attribute of shape: problem !")
680         #
681         if self.__B is None:                  __B_shape = (0,0)
682         elif hasattr(self.__B,"shape"):
683             if type(self.__B.shape) is tuple: __B_shape = self.__B.shape
684             else:                             __B_shape = self.__B.shape()
685         else: raise TypeError("B has no attribute of shape: problem !")
686         #
687         if self.__R is None:                  __R_shape = (0,0)
688         elif hasattr(self.__R,"shape"):
689             if type(self.__R.shape) is tuple: __R_shape = self.__R.shape
690             else:                             __R_shape = self.__R.shape()
691         else: raise TypeError("R has no attribute of shape: problem !")
692         #
693         if self.__Q is None:                  __Q_shape = (0,0)
694         elif hasattr(self.__Q,"shape"):
695             if type(self.__Q.shape) is tuple: __Q_shape = self.__Q.shape
696             else:                             __Q_shape = self.__Q.shape()
697         else: raise TypeError("Q has no attribute of shape: problem !")
698         #
699         if len(self.__HO) == 0:                          __HO_shape = (0,0)
700         elif type(self.__HO) is type({}):                __HO_shape = (0,0)
701         elif hasattr(self.__HO["Direct"],"shape"):
702             if type(self.__HO["Direct"].shape) is tuple: __HO_shape = self.__HO["Direct"].shape
703             else:                                       __HO_shape = self.__HO["Direct"].shape()
704         else: raise TypeError("H has no attribute of shape: problem !")
705         #
706         if len(self.__EM) == 0:                          __EM_shape = (0,0)
707         elif type(self.__EM) is type({}):                __EM_shape = (0,0)
708         elif hasattr(self.__EM["Direct"],"shape"):
709             if type(self.__EM["Direct"].shape) is tuple: __EM_shape = self.__EM["Direct"].shape
710             else:                                       __EM_shape = self.__EM["Direct"].shape()
711         else: raise TypeError("EM has no attribute of shape: problem !")
712         #
713         if len(self.__CM) == 0:                          __CM_shape = (0,0)
714         elif type(self.__CM) is type({}):                __CM_shape = (0,0)
715         elif hasattr(self.__CM["Direct"],"shape"):
716             if type(self.__CM["Direct"].shape) is tuple: __CM_shape = self.__CM["Direct"].shape
717             else:                                       __CM_shape = self.__CM["Direct"].shape()
718         else: raise TypeError("CM has no attribute of shape: problem !")
719         #
720         # Vérification des conditions
721         # ---------------------------
722         if not( len(__Xb_shape) == 1 or min(__Xb_shape) == 1 ):
723             raise ValueError("Shape characteristic of Xb is incorrect: \"%s\""%(__Xb_shape,))
724         if not( len(__Y_shape) == 1 or min(__Y_shape) == 1 ):
725             raise ValueError("Shape characteristic of Y is incorrect: \"%s\""%(__Y_shape,))
726         #
727         if not( min(__B_shape) == max(__B_shape) ):
728             raise ValueError("Shape characteristic of B is incorrect: \"%s\""%(__B_shape,))
729         if not( min(__R_shape) == max(__R_shape) ):
730             raise ValueError("Shape characteristic of R is incorrect: \"%s\""%(__R_shape,))
731         if not( min(__Q_shape) == max(__Q_shape) ):
732             raise ValueError("Shape characteristic of Q is incorrect: \"%s\""%(__Q_shape,))
733         if not( min(__EM_shape) == max(__EM_shape) ):
734             raise ValueError("Shape characteristic of EM is incorrect: \"%s\""%(__EM_shape,))
735         #
736         if len(self.__HO) > 0 and not(type(self.__HO) is type({})) and not( __HO_shape[1] == max(__Xb_shape) ):
737             raise ValueError("Shape characteristic of H \"%s\" and X \"%s\" are incompatible"%(__HO_shape,__Xb_shape))
738         if len(self.__HO) > 0 and not(type(self.__HO) is type({})) and not( __HO_shape[0] == max(__Y_shape) ):
739             raise ValueError("Shape characteristic of H \"%s\" and Y \"%s\" are incompatible"%(__HO_shape,__Y_shape))
740         if len(self.__HO) > 0 and not(type(self.__HO) is type({})) and len(self.__B) > 0 and not( __HO_shape[1] == __B_shape[0] ):
741             raise ValueError("Shape characteristic of H \"%s\" and B \"%s\" are incompatible"%(__HO_shape,__B_shape))
742         if len(self.__HO) > 0 and not(type(self.__HO) is type({})) and len(self.__R) > 0 and not( __HO_shape[0] == __R_shape[1] ):
743             raise ValueError("Shape characteristic of H \"%s\" and R \"%s\" are incompatible"%(__HO_shape,__R_shape))
744         #
745         if self.__B is not None and len(self.__B) > 0 and not( __B_shape[1] == max(__Xb_shape) ):
746             if self.__StoredInputs["AlgorithmName"] in ["EnsembleBlue",]:
747                 asPersistentVector = self.__Xb.reshape((-1,min(__B_shape)))
748                 self.__Xb = Persistence.OneVector("Background", basetype=numpy.matrix)
749                 for member in asPersistentVector:
750                     self.__Xb.store( numpy.matrix( numpy.ravel(member), numpy.float ).T )
751                 __Xb_shape = min(__B_shape)
752             else:
753                 raise ValueError("Shape characteristic of B \"%s\" and Xb \"%s\" are incompatible"%(__B_shape,__Xb_shape))
754         #
755         if self.__R is not None and len(self.__R) > 0 and not( __R_shape[1] == max(__Y_shape) ):
756             raise ValueError("Shape characteristic of R \"%s\" and Y \"%s\" are incompatible"%(__R_shape,__Y_shape))
757         #
758         if self.__EM is not None and len(self.__EM) > 0 and not(type(self.__EM) is type({})) and not( __EM_shape[1] == max(__Xb_shape) ):
759             raise ValueError("Shape characteristic of EM \"%s\" and X \"%s\" are incompatible"%(__EM_shape,__Xb_shape))
760         #
761         if self.__CM is not None and len(self.__CM) > 0 and not(type(self.__CM) is type({})) and not( __CM_shape[1] == max(__U_shape) ):
762             raise ValueError("Shape characteristic of CM \"%s\" and U \"%s\" are incompatible"%(__CM_shape,__U_shape))
763         #
764         return 1
765
766     # -----------------------------------------------------------
767     def analyze(self):
768         """
769         Permet de lancer le calcul d'assimilation.
770         
771         Le nom de la méthode à activer est toujours "run". Les paramètres en
772         arguments de la méthode sont fixés. En sortie, on obtient les résultats
773         dans la variable de type dictionnaire "StoredVariables", qui contient en
774         particulier des objets de Persistence pour les analyses, OMA...
775         """
776         self.shape_validate()
777         #
778         self.__algorithm.run(
779             Xb         = self.__Xb,
780             Y          = self.__Y,
781             U          = self.__U,
782             HO         = self.__HO,
783             EM         = self.__EM,
784             CM         = self.__CM,
785             R          = self.__R,
786             B          = self.__B,
787             Q          = self.__Q,
788             Parameters = self.__Parameters,
789             )
790         return 0
791
792     # -----------------------------------------------------------
793     def get(self, key=None):
794         """
795         Renvoie les résultats disponibles après l'exécution de la méthode
796         d'assimilation, ou les diagnostics disponibles. Attention, quand un
797         diagnostic porte le même nom qu'une variable stockée, c'est la variable
798         stockée qui est renvoyée, et le diagnostic est inatteignable.
799         """
800         if key is not None:
801             if self.__algorithm.has_key(key):
802                 return self.__algorithm.get( key )
803             elif self.__StoredInputs.has_key(key):
804                 return self.__StoredInputs[key]
805             elif self.__StoredDiagnostics.has_key(key):
806                 return self.__StoredDiagnostics[key]
807             else:
808                 raise ValueError("The requested key \"%s\" does not exists as an input, a diagnostic or a stored variable."%key)
809         else:
810             allvariables = self.__algorithm.get()
811             allvariables.update( self.__StoredDiagnostics )
812             allvariables.update( self.__StoredInputs )
813             return allvariables
814     
815     def get_available_variables(self):
816         """
817         Renvoie les variables potentiellement utilisables pour l'étude,
818         initialement stockées comme données d'entrées ou dans les algorithmes,
819         identifiés par les chaînes de caractères. L'algorithme doit avoir été
820         préalablement choisi sinon la méthode renvoie "None".
821         """
822         if len( self.__algorithm.keys()) == 0 and len( self.__StoredInputs.keys() ) == 0:
823             return None
824         else:
825             variables = []
826             if len( self.__algorithm.keys()) > 0:
827                 variables.extend( self.__algorithm.get().keys() )
828             if len( self.__StoredInputs.keys() ) > 0:
829                 variables.extend( self.__StoredInputs.keys() )
830             variables.sort()
831             return variables
832     
833     def get_available_algorithms(self):
834         """
835         Renvoie la liste des algorithmes potentiellement utilisables, identifiés
836         par les chaînes de caractères.
837         """
838         files = []
839         for directory in sys.path:
840             if os.path.isdir(os.path.join(directory,"daAlgorithms")):
841                 for fname in os.listdir(os.path.join(directory,"daAlgorithms")):
842                     root, ext = os.path.splitext(fname)
843                     if ext == '.py' and root != '__init__':
844                         files.append(root)
845         files.sort()
846         return files
847         
848     def get_available_diagnostics(self):
849         """
850         Renvoie la liste des diagnostics potentiellement utilisables, identifiés
851         par les chaînes de caractères.
852         """
853         files = []
854         for directory in sys.path:
855             if os.path.isdir(os.path.join(directory,"daDiagnostics")):
856                 for fname in os.listdir(os.path.join(directory,"daDiagnostics")):
857                     root, ext = os.path.splitext(fname)
858                     if ext == '.py' and root != '__init__':
859                         files.append(root)
860         files.sort()
861         return files
862
863     # -----------------------------------------------------------
864     def get_algorithms_main_path(self):
865         """
866         Renvoie le chemin pour le répertoire principal contenant les algorithmes
867         dans un sous-répertoire "daAlgorithms"
868         """
869         return self.__parent
870
871     def add_algorithms_path(self, asPath=None):
872         """
873         Ajoute au chemin de recherche des algorithmes un répertoire dans lequel
874         se trouve un sous-répertoire "daAlgorithms"
875         
876         Remarque : si le chemin a déjà été ajouté pour les diagnostics, il n'est
877         pas indispensable de le rajouter ici.
878         """
879         if not os.path.isdir(asPath):
880             raise ValueError("The given "+asPath+" argument must exist as a directory")
881         if not os.path.isdir(os.path.join(asPath,"daAlgorithms")):
882             raise ValueError("The given \""+asPath+"\" argument must contain a subdirectory named \"daAlgorithms\"")
883         if not os.path.isfile(os.path.join(asPath,"daAlgorithms","__init__.py")):
884             raise ValueError("The given \""+asPath+"/daAlgorithms\" path must contain a file named \"__init__.py\"")
885         sys.path.insert(0, os.path.abspath(asPath))
886         sys.path = uniq( sys.path ) # Conserve en unique exemplaire chaque chemin
887         return 1
888
889     def get_diagnostics_main_path(self):
890         """
891         Renvoie le chemin pour le répertoire principal contenant les diagnostics
892         dans un sous-répertoire "daDiagnostics"
893         """
894         return self.__parent
895
896     def add_diagnostics_path(self, asPath=None):
897         """
898         Ajoute au chemin de recherche des algorithmes un répertoire dans lequel
899         se trouve un sous-répertoire "daDiagnostics"
900         
901         Remarque : si le chemin a déjà été ajouté pour les algorithmes, il n'est
902         pas indispensable de le rajouter ici.
903         """
904         if not os.path.isdir(asPath):
905             raise ValueError("The given "+asPath+" argument must exist as a directory")
906         if not os.path.isdir(os.path.join(asPath,"daDiagnostics")):
907             raise ValueError("The given \""+asPath+"\" argument must contain a subdirectory named \"daDiagnostics\"")
908         if not os.path.isfile(os.path.join(asPath,"daDiagnostics","__init__.py")):
909             raise ValueError("The given \""+asPath+"/daDiagnostics\" path must contain a file named \"__init__.py\"")
910         sys.path.insert(0, os.path.abspath(asPath))
911         sys.path = uniq( sys.path ) # Conserve en unique exemplaire chaque chemin
912         return 1
913
914     # -----------------------------------------------------------
915     def setDataObserver(self,
916             VariableName   = None,
917             HookFunction   = None,
918             HookParameters = None,
919             Scheduler      = None,
920             ):
921         """
922         Permet d'associer un observer à une ou des variables nommées gérées en
923         interne, activable selon des règles définies dans le Scheduler. A chaque
924         pas demandé dans le Scheduler, il effectue la fonction HookFunction avec
925         les arguments (variable persistante VariableName, paramètres HookParameters).
926         """
927         # 
928         if type( self.__algorithm ) is dict:
929             raise ValueError("No observer can be build before choosing an algorithm.")
930         #
931         # Vérification du nom de variable et typage
932         # -----------------------------------------
933         if type( VariableName ) is str:
934             VariableNames = [VariableName,]
935         elif type( VariableName ) is list:
936             VariableNames = map( str, VariableName )
937         else:
938             raise ValueError("The observer requires a name or a list of names of variables.")
939         #
940         # Association interne de l'observer à la variable
941         # -----------------------------------------------
942         for n in VariableNames:
943             if not self.__algorithm.has_key( n ):
944                 raise ValueError("An observer requires to be set on a variable named %s which does not exist."%n)
945         else:
946             self.__algorithm.StoredVariables[ n ].setDataObserver(
947                 Scheduler      = Scheduler,
948                 HookFunction   = HookFunction,
949                 HookParameters = HookParameters,
950                 )
951
952     def removeDataObserver(self,
953             VariableName   = None,
954             HookFunction   = None,
955             ):
956         """
957         Permet de retirer un observer à une ou des variable nommée.
958         """
959         # 
960         if type( self.__algorithm ) is dict:
961             raise ValueError("No observer can be removed before choosing an algorithm.")
962         #
963         # Vérification du nom de variable et typage
964         # -----------------------------------------
965         if type( VariableName ) is str:
966             VariableNames = [VariableName,]
967         elif type( VariableName ) is list:
968             VariableNames = map( str, VariableName )
969         else:
970             raise ValueError("The observer requires a name or a list of names of variables.")
971         #
972         # Association interne de l'observer à la variable
973         # -----------------------------------------------
974         for n in VariableNames:
975             if not self.__algorithm.has_key( n ):
976                 raise ValueError("An observer requires to be removed on a variable named %s which does not exist."%n)
977         else:
978             self.__algorithm.StoredVariables[ n ].removeDataObserver(
979                 HookFunction   = HookFunction,
980                 )
981
982     # -----------------------------------------------------------
983     def setDebug(self, level=10):
984         """
985         Utiliser par exemple "import logging ; level = logging.DEBUG" avant cet
986         appel pour changer le niveau de verbosité, avec :
987         NOTSET=0 < DEBUG=10 < INFO=20 < WARNING=30 < ERROR=40 < CRITICAL=50
988         """
989         import logging
990         log = logging.getLogger()
991         log.setLevel( level )
992
993     def unsetDebug(self):
994         """
995         Remet le logger au niveau par défaut
996         """
997         import logging
998         log = logging.getLogger()
999         log.setLevel( logging.WARNING )
1000
1001     def prepare_to_pickle(self):
1002         self.__algorithmFile = None
1003         self.__diagnosticFile = None
1004         self.__HO  = {}
1005         self.__EM  = {}
1006         self.__CM  = {}
1007
1008 # ==============================================================================
1009 if __name__ == "__main__":
1010     print '\n AUTODIAGNOSTIC \n'
1011     
1012     ADD = AssimilationStudy("Ma premiere etude BLUE")
1013     
1014     ADD.setBackground         (asVector     = [0, 1, 2])
1015     ADD.setBackgroundError    (asCovariance = "1 0 0;0 1 0;0 0 1")
1016     ADD.setObservation        (asVector     = [0.5, 1.5, 2.5])
1017     ADD.setObservationError   (asCovariance = "1 0 0;0 1 0;0 0 1")
1018     ADD.setObservationOperator(asMatrix     = "1 0 0;0 1 0;0 0 1")
1019     
1020     ADD.setAlgorithm(choice="Blue")
1021     
1022     ADD.analyze()
1023     
1024     print "Nombre d'analyses  :", ADD.get("Analysis").stepnumber()
1025     print "Ebauche            :", [0, 1, 2]
1026     print "Observation        :", [0.5, 1.5, 2.5]
1027     print "Demi-somme         :", list((numpy.array([0, 1, 2])+numpy.array([0.5, 1.5, 2.5]))/2)
1028     print "  qui doit être identique à :"
1029     print "Analyse résultante :", ADD.get("Analysis")[0]
1030     print
1031     
1032     print "Algorithmes disponibles.......................:", ADD.get_available_algorithms()
1033     # print " Chemin des algorithmes.....................:", ADD.get_algorithms_main_path()
1034     print "Diagnostics types disponibles.................:", ADD.get_available_diagnostics()
1035     # print " Chemin des diagnostics.....................:", ADD.get_diagnostics_main_path()
1036     print "Variables disponibles.........................:", ADD.get_available_variables()
1037     print
1038     
1039     print "Paramètres requis par algorithme :"
1040     for algo in ADD.get_available_algorithms():
1041         tmpADD = AssimilationStudy("Un algorithme")
1042         tmpADD.setAlgorithm(choice=algo)
1043         print "  %25s : %s"%(algo,tmpADD.getAlgorithmParameters())
1044         del tmpADD
1045     print
1046
1047     ADD.setDiagnostic("RMS", "Ma RMS")
1048     
1049     liste = ADD.get().keys()
1050     liste.sort()
1051     print "Variables et diagnostics nommés disponibles...:", liste
1052
1053     print
1054     print "Exemple de mise en place d'un observeur :"
1055     def obs(var=None,info=None):
1056         print "  ---> Mise en oeuvre de l'observer"
1057         print "       var  =",var[-1]
1058         print "       info =",info
1059     ADD.setDataObserver( 'Analysis', HookFunction=obs, Scheduler = [2, 4], HookParameters = "Second observer")
1060     # Attention, il faut décaler le stockage de 1 pour suivre le pas interne
1061     # car le pas 0 correspond à l'analyse ci-dessus.
1062     for i in range(1,6):
1063         print
1064         print "Action sur la variable observée, étape :",i
1065         ADD.get('Analysis').store( [i, i, i] )
1066     print
1067
1068     print "Mise en debug et hors debug"
1069     print "Nombre d'analyses  :", ADD.get("Analysis").stepnumber()
1070     ADD.setDebug()
1071     ADD.analyze()
1072     ADD.unsetDebug()
1073     print "Nombre d'analyses  :", ADD.get("Analysis").stepnumber()
1074     ADD.analyze()
1075     print "Nombre d'analyses  :", ADD.get("Analysis").stepnumber()
1076     print