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