Salome HOME
d6e27596de18fd3a9e5f4f6eba4d1fa969e069ad
[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("The background (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("The observation (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("The control (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("The a priori errors covariance matrix (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("The observation errors covariance matrix (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("The evolution errors covariance matrix (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("The observation operator (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("The evolution model (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("The control model (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 background (Xb) is incorrect: \"%s\"."%(__Xb_shape,))
724         if not( len(__Y_shape) == 1 or min(__Y_shape) == 1 ):
725             raise ValueError("Shape characteristic of observation (Y) is incorrect: \"%s\"."%(__Y_shape,))
726         #
727         if not( min(__B_shape) == max(__B_shape) ):
728             raise ValueError("Shape characteristic of a priori errors covariance matrix (B) is incorrect: \"%s\"."%(__B_shape,))
729         if not( min(__R_shape) == max(__R_shape) ):
730             raise ValueError("Shape characteristic of observation errors covariance matrix (R) is incorrect: \"%s\"."%(__R_shape,))
731         if not( min(__Q_shape) == max(__Q_shape) ):
732             raise ValueError("Shape characteristic of evolution errors covariance matrix (Q) is incorrect: \"%s\"."%(__Q_shape,))
733         if not( min(__EM_shape) == max(__EM_shape) ):
734             raise ValueError("Shape characteristic of evolution operator (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 observation operator (H) \"%s\" and state (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 observation operator (H) \"%s\" and observation (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 observation operator (H) \"%s\" and a priori errors covariance matrix (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 observation operator (H) \"%s\" and observation errors covariance matrix (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 a priori errors covariance matrix (B) \"%s\" and background (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 observation errors covariance matrix (R) \"%s\" and observation (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 evolution model (EM) \"%s\" and state (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 control model (CM) \"%s\" and control (U) \"%s\" are incompatible."%(__CM_shape,__U_shape))
763         #
764         if self.__StoredInputs.has_key("AlgorithmParameters") \
765             and self.__StoredInputs["AlgorithmParameters"].has_key("Bounds") \
766             and (type(self.__StoredInputs["AlgorithmParameters"]["Bounds"]) is type([]) or type(self._parameters["Bounds"]) is type(())) \
767             and (len(self.__StoredInputs["AlgorithmParameters"]["Bounds"]) != max(__Xb_shape)):
768             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)))
769         #
770         return 1
771
772     # -----------------------------------------------------------
773     def analyze(self):
774         """
775         Permet de lancer le calcul d'assimilation.
776         
777         Le nom de la méthode à activer est toujours "run". Les paramètres en
778         arguments de la méthode sont fixés. En sortie, on obtient les résultats
779         dans la variable de type dictionnaire "StoredVariables", qui contient en
780         particulier des objets de Persistence pour les analyses, OMA...
781         """
782         self.shape_validate()
783         #
784         self.__algorithm.run(
785             Xb         = self.__Xb,
786             Y          = self.__Y,
787             U          = self.__U,
788             HO         = self.__HO,
789             EM         = self.__EM,
790             CM         = self.__CM,
791             R          = self.__R,
792             B          = self.__B,
793             Q          = self.__Q,
794             Parameters = self.__Parameters,
795             )
796         return 0
797
798     # -----------------------------------------------------------
799     def get(self, key=None):
800         """
801         Renvoie les résultats disponibles après l'exécution de la méthode
802         d'assimilation, ou les diagnostics disponibles. Attention, quand un
803         diagnostic porte le même nom qu'une variable stockée, c'est la variable
804         stockée qui est renvoyée, et le diagnostic est inatteignable.
805         """
806         if key is not None:
807             if self.__algorithm.has_key(key):
808                 return self.__algorithm.get( key )
809             elif self.__StoredInputs.has_key(key):
810                 return self.__StoredInputs[key]
811             elif self.__StoredDiagnostics.has_key(key):
812                 return self.__StoredDiagnostics[key]
813             else:
814                 raise ValueError("The requested key \"%s\" does not exists as an input, a diagnostic or a stored variable."%key)
815         else:
816             allvariables = self.__algorithm.get()
817             allvariables.update( self.__StoredDiagnostics )
818             allvariables.update( self.__StoredInputs )
819             return allvariables
820     
821     def get_available_variables(self):
822         """
823         Renvoie les variables potentiellement utilisables pour l'étude,
824         initialement stockées comme données d'entrées ou dans les algorithmes,
825         identifiés par les chaînes de caractères. L'algorithme doit avoir été
826         préalablement choisi sinon la méthode renvoie "None".
827         """
828         if len( self.__algorithm.keys()) == 0 and len( self.__StoredInputs.keys() ) == 0:
829             return None
830         else:
831             variables = []
832             if len( self.__algorithm.keys()) > 0:
833                 variables.extend( self.__algorithm.get().keys() )
834             if len( self.__StoredInputs.keys() ) > 0:
835                 variables.extend( self.__StoredInputs.keys() )
836             variables.sort()
837             return variables
838     
839     def get_available_algorithms(self):
840         """
841         Renvoie la liste des algorithmes potentiellement utilisables, identifiés
842         par les chaînes de caractères.
843         """
844         files = []
845         for directory in sys.path:
846             if os.path.isdir(os.path.join(directory,"daAlgorithms")):
847                 for fname in os.listdir(os.path.join(directory,"daAlgorithms")):
848                     root, ext = os.path.splitext(fname)
849                     if ext == '.py' and root != '__init__':
850                         files.append(root)
851         files.sort()
852         return files
853         
854     def get_available_diagnostics(self):
855         """
856         Renvoie la liste des diagnostics potentiellement utilisables, identifiés
857         par les chaînes de caractères.
858         """
859         files = []
860         for directory in sys.path:
861             if os.path.isdir(os.path.join(directory,"daDiagnostics")):
862                 for fname in os.listdir(os.path.join(directory,"daDiagnostics")):
863                     root, ext = os.path.splitext(fname)
864                     if ext == '.py' and root != '__init__':
865                         files.append(root)
866         files.sort()
867         return files
868
869     # -----------------------------------------------------------
870     def get_algorithms_main_path(self):
871         """
872         Renvoie le chemin pour le répertoire principal contenant les algorithmes
873         dans un sous-répertoire "daAlgorithms"
874         """
875         return self.__parent
876
877     def add_algorithms_path(self, asPath=None):
878         """
879         Ajoute au chemin de recherche des algorithmes un répertoire dans lequel
880         se trouve un sous-répertoire "daAlgorithms"
881         
882         Remarque : si le chemin a déjà été ajouté pour les diagnostics, il n'est
883         pas indispensable de le rajouter ici.
884         """
885         if not os.path.isdir(asPath):
886             raise ValueError("The given "+asPath+" argument must exist as a directory")
887         if not os.path.isdir(os.path.join(asPath,"daAlgorithms")):
888             raise ValueError("The given \""+asPath+"\" argument must contain a subdirectory named \"daAlgorithms\"")
889         if not os.path.isfile(os.path.join(asPath,"daAlgorithms","__init__.py")):
890             raise ValueError("The given \""+asPath+"/daAlgorithms\" path must contain a file named \"__init__.py\"")
891         sys.path.insert(0, os.path.abspath(asPath))
892         sys.path = uniq( sys.path ) # Conserve en unique exemplaire chaque chemin
893         return 1
894
895     def get_diagnostics_main_path(self):
896         """
897         Renvoie le chemin pour le répertoire principal contenant les diagnostics
898         dans un sous-répertoire "daDiagnostics"
899         """
900         return self.__parent
901
902     def add_diagnostics_path(self, asPath=None):
903         """
904         Ajoute au chemin de recherche des algorithmes un répertoire dans lequel
905         se trouve un sous-répertoire "daDiagnostics"
906         
907         Remarque : si le chemin a déjà été ajouté pour les algorithmes, il n'est
908         pas indispensable de le rajouter ici.
909         """
910         if not os.path.isdir(asPath):
911             raise ValueError("The given "+asPath+" argument must exist as a directory")
912         if not os.path.isdir(os.path.join(asPath,"daDiagnostics")):
913             raise ValueError("The given \""+asPath+"\" argument must contain a subdirectory named \"daDiagnostics\"")
914         if not os.path.isfile(os.path.join(asPath,"daDiagnostics","__init__.py")):
915             raise ValueError("The given \""+asPath+"/daDiagnostics\" path must contain a file named \"__init__.py\"")
916         sys.path.insert(0, os.path.abspath(asPath))
917         sys.path = uniq( sys.path ) # Conserve en unique exemplaire chaque chemin
918         return 1
919
920     # -----------------------------------------------------------
921     def setDataObserver(self,
922             VariableName   = None,
923             HookFunction   = None,
924             HookParameters = None,
925             Scheduler      = None,
926             ):
927         """
928         Permet d'associer un observer à une ou des variables nommées gérées en
929         interne, activable selon des règles définies dans le Scheduler. A chaque
930         pas demandé dans le Scheduler, il effectue la fonction HookFunction avec
931         les arguments (variable persistante VariableName, paramètres HookParameters).
932         """
933         # 
934         if type( self.__algorithm ) is dict:
935             raise ValueError("No observer can be build before choosing an algorithm.")
936         #
937         # Vérification du nom de variable et typage
938         # -----------------------------------------
939         if type( VariableName ) is str:
940             VariableNames = [VariableName,]
941         elif type( VariableName ) is list:
942             VariableNames = map( str, VariableName )
943         else:
944             raise ValueError("The observer requires a name or a list of names of variables.")
945         #
946         # Association interne de l'observer à la variable
947         # -----------------------------------------------
948         for n in VariableNames:
949             if not self.__algorithm.has_key( n ):
950                 raise ValueError("An observer requires to be set on a variable named %s which does not exist."%n)
951         else:
952             self.__algorithm.StoredVariables[ n ].setDataObserver(
953                 Scheduler      = Scheduler,
954                 HookFunction   = HookFunction,
955                 HookParameters = HookParameters,
956                 )
957
958     def removeDataObserver(self,
959             VariableName   = None,
960             HookFunction   = None,
961             ):
962         """
963         Permet de retirer un observer à une ou des variable nommée.
964         """
965         # 
966         if type( self.__algorithm ) is dict:
967             raise ValueError("No observer can be removed before choosing an algorithm.")
968         #
969         # Vérification du nom de variable et typage
970         # -----------------------------------------
971         if type( VariableName ) is str:
972             VariableNames = [VariableName,]
973         elif type( VariableName ) is list:
974             VariableNames = map( str, VariableName )
975         else:
976             raise ValueError("The observer requires a name or a list of names of variables.")
977         #
978         # Association interne de l'observer à la variable
979         # -----------------------------------------------
980         for n in VariableNames:
981             if not self.__algorithm.has_key( n ):
982                 raise ValueError("An observer requires to be removed on a variable named %s which does not exist."%n)
983         else:
984             self.__algorithm.StoredVariables[ n ].removeDataObserver(
985                 HookFunction   = HookFunction,
986                 )
987
988     # -----------------------------------------------------------
989     def setDebug(self, level=10):
990         """
991         Utiliser par exemple "import logging ; level = logging.DEBUG" avant cet
992         appel pour changer le niveau de verbosité, avec :
993         NOTSET=0 < DEBUG=10 < INFO=20 < WARNING=30 < ERROR=40 < CRITICAL=50
994         """
995         import logging
996         log = logging.getLogger()
997         log.setLevel( level )
998
999     def unsetDebug(self):
1000         """
1001         Remet le logger au niveau par défaut
1002         """
1003         import logging
1004         log = logging.getLogger()
1005         log.setLevel( logging.WARNING )
1006
1007     def prepare_to_pickle(self):
1008         self.__algorithmFile = None
1009         self.__diagnosticFile = None
1010         self.__HO  = {}
1011         self.__EM  = {}
1012         self.__CM  = {}
1013
1014 # ==============================================================================
1015 if __name__ == "__main__":
1016     print '\n AUTODIAGNOSTIC \n'