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