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