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