1 #-*-coding:iso-8859-1-*-
3 # Copyright (C) 2008-2013 EDF R&D
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.
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.
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
19 # See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
21 # Author: Jean-Philippe Argaud, jean-philippe.argaud@edf.fr, EDF R&D
24 Classe principale pour la préparation, la réalisation et la restitution de
25 calculs d'assimilation de données.
27 Ce module est destiné à être appelé par AssimilationStudy pour constituer
28 les objets élémentaires de l'étude.
30 __author__ = "Jean-Philippe ARGAUD"
34 import Logging ; Logging.Logging() # A importer en premier
37 from BasicObjects import Operator, Covariance
38 from PlatformInfo import uniq
40 # ==============================================================================
41 class AssimilationStudy:
43 Cette classe sert d'interface pour l'utilisation de l'assimilation de
44 données. Elle contient les méthodes ou accesseurs nécessaires à la
45 construction d'un calcul d'assimilation.
47 def __init__(self, name=""):
49 Prévoit de conserver l'ensemble des variables nécssaires à un algorithme
50 élémentaire. Ces variables sont ensuite disponibles pour implémenter un
51 algorithme élémentaire particulier.
53 - Background : vecteur Xb
54 - Observation : vecteur Y (potentiellement temporel) d'observations
55 - State : vecteur d'état dont une partie est le vecteur de contrôle.
56 Cette information n'est utile que si l'on veut faire des calculs sur
57 l'état complet, mais elle n'est pas indispensable pour l'assimilation.
58 - Control : vecteur X contenant toutes les variables de contrôle, i.e.
59 les paramètres ou l'état dont on veut estimer la valeur pour obtenir
61 - ObservationOperator...: opérateur d'observation H
63 Les observations présentent une erreur dont la matrice de covariance est
64 R. L'ébauche du vecteur de contrôle présente une erreur dont la matrice
67 self.__name = str(name)
78 self.__X = Persistence.OneVector()
79 self.__Parameters = {}
80 self.__StoredDiagnostics = {}
81 self.__StoredInputs = {}
83 # Variables temporaires
85 self.__algorithmFile = None
86 self.__algorithmName = None
87 self.__diagnosticFile = None
89 # Récupère le chemin du répertoire parent et l'ajoute au path
90 # (Cela complète l'action de la classe PathManagement dans PlatformInfo,
91 # qui est activée dans Persistence)
92 self.__parent = os.path.abspath(os.path.join(os.path.dirname(__file__),".."))
93 sys.path.insert(0, self.__parent)
94 sys.path = uniq( sys.path ) # Conserve en unique exemplaire chaque chemin
96 # ---------------------------------------------------------
97 def setBackground(self,
99 asPersistentVector = None,
104 Permet de définir l'estimation a priori :
105 - asVector : entrée des données, comme un vecteur compatible avec le
106 constructeur de numpy.matrix
107 - asPersistentVector : entrée des données, comme un vecteur de type
108 persistent contruit avec la classe ad-hoc "Persistence"
109 - Scheduler est le contrôle temporel des données
110 - toBeStored : booléen indiquant si la donnée d'entrée est sauvée pour
111 être rendue disponible au même titre que les variables de calcul
113 if asVector is not None:
114 if type( asVector ) is type( numpy.matrix([]) ):
115 self.__Xb = numpy.matrix( asVector.A1, numpy.float ).T
117 self.__Xb = numpy.matrix( asVector, numpy.float ).T
118 elif asPersistentVector is not None:
119 if type(asPersistentVector) in [type([]),type(()),type(numpy.array([])),type(numpy.matrix([]))]:
120 self.__Xb = Persistence.OneVector("Background", basetype=numpy.matrix)
121 for member in asPersistentVector:
122 self.__Xb.store( numpy.matrix( numpy.asmatrix(member).A1, numpy.float ).T )
124 self.__Xb = asPersistentVector
126 raise ValueError("Error: improperly defined background, it requires at minima either a vector, a list/tuple of vectors or a persistent object")
128 self.__StoredInputs["Background"] = self.__Xb
131 def setBackgroundError(self,
133 asEyeByScalar = None,
134 asEyeByVector = None,
138 Permet de définir la covariance des erreurs d'ébauche :
139 - asCovariance : entrée des données, comme une matrice compatible avec
140 le constructeur de numpy.matrix
141 - asEyeByScalar : entrée des données comme un seul scalaire de variance,
142 multiplicatif d'une matrice de corrélation identité, aucune matrice
143 n'étant donc explicitement à donner
144 - asEyeByVector : entrée des données comme un seul vecteur de variance,
145 à mettre sur la diagonale d'une matrice de corrélation, aucune matrice
146 n'étant donc explicitement à donner
147 - toBeStored : booléen indiquant si la donnée d'entrée est sauvée pour
148 être rendue disponible au même titre que les variables de calcul
150 self.__B = Covariance(
151 name = "BackgroundError",
152 asCovariance = asCovariance,
153 asEyeByScalar = asEyeByScalar,
154 asEyeByVector = asEyeByVector,
157 self.__StoredInputs["BackgroundError"] = self.__B
160 # -----------------------------------------------------------
161 def setObservation(self,
163 asPersistentVector = None,
168 Permet de définir les observations :
169 - asVector : entrée des données, comme un vecteur compatible avec le
170 constructeur de numpy.matrix
171 - asPersistentVector : entrée des données, comme un vecteur de type
172 persistent contruit avec la classe ad-hoc "Persistence"
173 - Scheduler est le contrôle temporel des données disponibles
174 - toBeStored : booléen indiquant si la donnée d'entrée est sauvée pour
175 être rendue disponible au même titre que les variables de calcul
177 if asVector is not None:
178 if type( asVector ) is type( numpy.matrix([]) ):
179 self.__Y = numpy.matrix( asVector.A1, numpy.float ).T
181 self.__Y = numpy.matrix( asVector, numpy.float ).T
182 elif asPersistentVector is not None:
183 if type(asPersistentVector) in [type([]),type(()),type(numpy.array([])),type(numpy.matrix([]))]:
184 self.__Y = Persistence.OneVector("Observation", basetype=numpy.matrix)
185 for member in asPersistentVector:
186 self.__Y.store( numpy.matrix( numpy.asmatrix(member).A1, numpy.float ).T )
188 self.__Y = asPersistentVector
190 raise ValueError("Error: improperly defined observations, it requires at minima either a vector, a list/tuple of vectors or a persistent object")
192 self.__StoredInputs["Observation"] = self.__Y
195 def setObservationError(self,
197 asEyeByScalar = None,
198 asEyeByVector = None,
202 Permet de définir la covariance des erreurs d'observations :
203 - asCovariance : entrée des données, comme une matrice compatible avec
204 le constructeur de numpy.matrix
205 - asEyeByScalar : entrée des données comme un seul scalaire de variance,
206 multiplicatif d'une matrice de corrélation identité, aucune matrice
207 n'étant donc explicitement à donner
208 - asEyeByVector : entrée des données comme un seul vecteur de variance,
209 à mettre sur la diagonale d'une matrice de corrélation, aucune matrice
210 n'étant donc explicitement à donner
211 - toBeStored : booléen indiquant si la donnée d'entrée est sauvée pour
212 être rendue disponible au même titre que les variables de calcul
214 self.__R = Covariance(
215 name = "ObservationError",
216 asCovariance = asCovariance,
217 asEyeByScalar = asEyeByScalar,
218 asEyeByVector = asEyeByVector,
221 self.__StoredInputs["ObservationError"] = self.__R
224 def setObservationOperator(self,
231 Permet de définir un opérateur d'observation H. L'ordre de priorité des
232 définitions et leur sens sont les suivants :
233 - si asFunction["Tangent"] et asFunction["Adjoint"] ne sont pas None
234 alors on définit l'opérateur à l'aide de fonctions. Si la fonction
235 "Direct" n'est pas définie, on prend la fonction "Tangent".
236 Si "useApproximatedDerivatives" est vrai, on utilise une approximation
237 des opérateurs tangents et adjoints. On utilise par défaut des
238 différences finies non centrées ou centrées (si "withCenteredDF" est
239 vrai) avec un incrément multiplicatif "withIncrement" de 1% autour
240 du point courant ou sur le point fixe "withdX".
241 - si les fonctions ne sont pas disponibles et si asMatrix n'est pas
242 None, alors on définit l'opérateur "Direct" et "Tangent" à l'aide de
243 la matrice, et l'opérateur "Adjoint" à l'aide de la transposée. La
244 matrice fournie doit être sous une forme compatible avec le
245 constructeur de numpy.matrix.
246 - si l'argument "appliedToX" n'est pas None, alors on définit, pour des
247 X divers, l'opérateur par sa valeur appliquée à cet X particulier,
248 sous la forme d'un dictionnaire appliedToX[NAME] avec NAME un nom.
249 L'opérateur doit néanmoins déjà avoir été défini comme d'habitude.
250 - toBeStored : booléen indiquant si la donnée d'entrée est sauvée pour
251 être rendue disponible au même titre que les variables de calcul
252 L'argument "asFunction" peut prendre la forme complète suivante, avec
253 les valeurs par défaut standards :
254 asFunction = {"Direct":None, "Tangent":None, "Adjoint":None,
255 "useApproximatedDerivatives":False,
256 "withCenteredDF" :False,
257 "withIncrement" :0.01,
259 "withAvoidingRedundancy" :True,
260 "withToleranceInRedundancy" :1.e-18,
261 "withLenghtOfRedundancy" :-1,
264 if (type(asFunction) is type({})) and \
265 asFunction.has_key("useApproximatedDerivatives") and bool(asFunction["useApproximatedDerivatives"]) and \
266 asFunction.has_key("Direct") and (asFunction["Direct"] is not None):
267 if not asFunction.has_key("withCenteredDF"): asFunction["withCenteredDF"] = False
268 if not asFunction.has_key("withIncrement"): asFunction["withIncrement"] = 0.01
269 if not asFunction.has_key("withdX"): asFunction["withdX"] = None
270 if not asFunction.has_key("withAvoidingRedundancy"): asFunction["withAvoidingRedundancy"] = True
271 if not asFunction.has_key("withToleranceInRedundancy"): asFunction["withToleranceInRedundancy"] = 1.e-18
272 if not asFunction.has_key("withLenghtOfRedundancy"): asFunction["withLenghtOfRedundancy"] = -1
273 from daNumerics.ApproximatedDerivatives import FDApproximation
274 FDA = FDApproximation(
275 Function = asFunction["Direct"],
276 centeredDF = asFunction["withCenteredDF"],
277 increment = asFunction["withIncrement"],
278 dX = asFunction["withdX"],
279 avoidingRedundancy = asFunction["withAvoidingRedundancy"],
280 toleranceInRedundancy = asFunction["withToleranceInRedundancy"],
281 lenghtOfRedundancy = asFunction["withLenghtOfRedundancy"],
283 self.__HO["Direct"] = Operator( fromMethod = FDA.DirectOperator )
284 self.__HO["Tangent"] = Operator( fromMethod = FDA.TangentOperator )
285 self.__HO["Adjoint"] = Operator( fromMethod = FDA.AdjointOperator )
286 elif (type(asFunction) is type({})) and \
287 asFunction.has_key("Tangent") and asFunction.has_key("Adjoint") and \
288 (asFunction["Tangent"] is not None) and (asFunction["Adjoint"] is not None):
289 if not asFunction.has_key("Direct") or (asFunction["Direct"] is None):
290 self.__HO["Direct"] = Operator( fromMethod = asFunction["Tangent"] )
292 self.__HO["Direct"] = Operator( fromMethod = asFunction["Direct"] )
293 self.__HO["Tangent"] = Operator( fromMethod = asFunction["Tangent"] )
294 self.__HO["Adjoint"] = Operator( fromMethod = asFunction["Adjoint"] )
295 elif asMatrix is not None:
296 matrice = numpy.matrix( asMatrix, numpy.float )
297 self.__HO["Direct"] = Operator( fromMatrix = matrice )
298 self.__HO["Tangent"] = Operator( fromMatrix = matrice )
299 self.__HO["Adjoint"] = Operator( fromMatrix = matrice.T )
302 raise ValueError("Improperly defined observation operator, it requires at minima either a matrix, a Direct for approximate derivatives or a Tangent/Adjoint pair.")
304 if appliedToX is not None:
305 self.__HO["AppliedToX"] = {}
306 if type(appliedToX) is not dict:
307 raise ValueError("Error: observation operator defined by \"appliedToX\" need a dictionary as argument.")
308 for key in appliedToX.keys():
309 if type( appliedToX[key] ) is type( numpy.matrix([]) ):
310 # Pour le cas où l'on a une vraie matrice
311 self.__HO["AppliedToX"][key] = numpy.matrix( appliedToX[key].A1, numpy.float ).T
312 elif type( appliedToX[key] ) is type( numpy.array([]) ) and len(appliedToX[key].shape) > 1:
313 # Pour le cas où l'on a un vecteur représenté en array avec 2 dimensions
314 self.__HO["AppliedToX"][key] = numpy.matrix( appliedToX[key].reshape(len(appliedToX[key]),), numpy.float ).T
316 self.__HO["AppliedToX"][key] = numpy.matrix( appliedToX[key], numpy.float ).T
318 self.__HO["AppliedToX"] = None
321 self.__StoredInputs["ObservationOperator"] = self.__HO
324 # -----------------------------------------------------------
325 def setEvolutionModel(self,
332 Permet de définir un opérateur d'évolution M. L'ordre de priorité des
333 définitions et leur sens sont les suivants :
334 - si asFunction["Tangent"] et asFunction["Adjoint"] ne sont pas None
335 alors on définit l'opérateur à l'aide de fonctions. Si la fonction
336 "Direct" n'est pas définie, on prend la fonction "Tangent".
337 Si "useApproximatedDerivatives" est vrai, on utilise une approximation
338 des opérateurs tangents et adjoints. On utilise par défaut des
339 différences finies non centrées ou centrées (si "withCenteredDF" est
340 vrai) avec un incrément multiplicatif "withIncrement" de 1% autour
341 du point courant ou sur le point fixe "withdX".
342 - si les fonctions ne sont pas disponibles et si asMatrix n'est pas
343 None, alors on définit l'opérateur "Direct" et "Tangent" à l'aide de
344 la matrice, et l'opérateur "Adjoint" à l'aide de la transposée. La
345 matrice fournie doit être sous une forme compatible avec le
346 constructeur de numpy.matrix.
347 - toBeStored : booléen indiquant si la donnée d'entrée est sauvée pour
348 être rendue disponible au même titre que les variables de calcul
349 L'argument "asFunction" peut prendre la forme complète suivante, avec
350 les valeurs par défaut standards :
351 asFunction = {"Direct":None, "Tangent":None, "Adjoint":None,
352 "useApproximatedDerivatives":False,
353 "withCenteredDF" :False,
354 "withIncrement" :0.01,
356 "withAvoidingRedundancy" :True,
357 "withToleranceInRedundancy" :1.e-18,
358 "withLenghtOfRedundancy" :-1,
361 if (type(asFunction) is type({})) and \
362 asFunction.has_key("useApproximatedDerivatives") and bool(asFunction["useApproximatedDerivatives"]) and \
363 asFunction.has_key("Direct") and (asFunction["Direct"] is not None):
364 if not asFunction.has_key("withCenteredDF"): asFunction["withCenteredDF"] = False
365 if not asFunction.has_key("withIncrement"): asFunction["withIncrement"] = 0.01
366 if not asFunction.has_key("withdX"): asFunction["withdX"] = None
367 if not asFunction.has_key("withAvoidingRedundancy"): asFunction["withAvoidingRedundancy"] = True
368 if not asFunction.has_key("withToleranceInRedundancy"): asFunction["withToleranceInRedundancy"] = 1.e-18
369 if not asFunction.has_key("withLenghtOfRedundancy"): asFunction["withLenghtOfRedundancy"] = -1
370 from daNumerics.ApproximatedDerivatives import FDApproximation
371 FDA = FDApproximation(
372 Function = asFunction["Direct"],
373 centeredDF = asFunction["withCenteredDF"],
374 increment = asFunction["withIncrement"],
375 dX = asFunction["withdX"],
376 avoidingRedundancy = asFunction["withAvoidingRedundancy"],
377 toleranceInRedundancy = asFunction["withToleranceInRedundancy"],
378 lenghtOfRedundancy = asFunction["withLenghtOfRedundancy"],
380 self.__EM["Direct"] = Operator( fromMethod = FDA.DirectOperator )
381 self.__EM["Tangent"] = Operator( fromMethod = FDA.TangentOperator )
382 self.__EM["Adjoint"] = Operator( fromMethod = FDA.AdjointOperator )
383 elif (type(asFunction) is type({})) and \
384 asFunction.has_key("Tangent") and asFunction.has_key("Adjoint") and \
385 (asFunction["Tangent"] is not None) and (asFunction["Adjoint"] is not None):
386 if not asFunction.has_key("Direct") or (asFunction["Direct"] is None):
387 self.__EM["Direct"] = Operator( fromMethod = asFunction["Tangent"] )
389 self.__EM["Direct"] = Operator( fromMethod = asFunction["Direct"] )
390 self.__EM["Tangent"] = Operator( fromMethod = asFunction["Tangent"] )
391 self.__EM["Adjoint"] = Operator( fromMethod = asFunction["Adjoint"] )
392 elif asMatrix is not None:
393 matrice = numpy.matrix( asMatrix, numpy.float )
394 self.__EM["Direct"] = Operator( fromMatrix = matrice )
395 self.__EM["Tangent"] = Operator( fromMatrix = matrice )
396 self.__EM["Adjoint"] = Operator( fromMatrix = matrice.T )
399 raise ValueError("Improperly defined evolution model, it requires at minima either a matrix, a Direct for approximate derivatives or a Tangent/Adjoint pair.")
402 self.__StoredInputs["EvolutionModel"] = self.__EM
405 def setEvolutionError(self,
407 asEyeByScalar = None,
408 asEyeByVector = None,
412 Permet de définir la covariance des erreurs de modèle :
413 - asCovariance : entrée des données, comme une matrice compatible avec
414 le constructeur de numpy.matrix
415 - asEyeByScalar : entrée des données comme un seul scalaire de variance,
416 multiplicatif d'une matrice de corrélation identité, aucune matrice
417 n'étant donc explicitement à donner
418 - asEyeByVector : entrée des données comme un seul vecteur de variance,
419 à mettre sur la diagonale d'une matrice de corrélation, aucune matrice
420 n'étant donc explicitement à donner
421 - toBeStored : booléen indiquant si la donnée d'entrée est sauvée pour
422 être rendue disponible au même titre que les variables de calcul
424 self.__Q = Covariance(
425 name = "ObservationError",
426 asCovariance = asCovariance,
427 asEyeByScalar = asEyeByScalar,
428 asEyeByVector = asEyeByVector,
431 self.__StoredInputs["EvolutionError"] = self.__Q
434 # -----------------------------------------------------------
435 def setControlModel(self,
436 asFunction = {"Direct":None, "Tangent":None, "Adjoint":None,
437 "useApproximatedDerivatives":False,
438 "withCenteredDF" :False,
439 "withIncrement" :0.01,
447 Permet de définir un opérateur de controle C. L'ordre de priorité des
448 définitions et leur sens sont les suivants :
449 - si asFunction["Tangent"] et asFunction["Adjoint"] ne sont pas None
450 alors on définit l'opérateur à l'aide de fonctions. Si la fonction
451 "Direct" n'est pas définie, on prend la fonction "Tangent".
452 Si "useApproximatedDerivatives" est vrai, on utilise une approximation
453 des opérateurs tangents et adjoints. On utilise par défaut des
454 différences finies non centrées ou centrées (si "withCenteredDF" est
455 vrai) avec un incrément multiplicatif "withIncrement" de 1% autour
456 du point courant ou sur le point fixe "withdX".
457 - si les fonctions ne sont pas disponibles et si asMatrix n'est pas
458 None, alors on définit l'opérateur "Direct" et "Tangent" à l'aide de
459 la matrice, et l'opérateur "Adjoint" à l'aide de la transposée. La
460 matrice fournie doit être sous une forme compatible avec le
461 constructeur de numpy.matrix.
462 - toBeStored : booléen indiquant si la donnée d'entrée est sauvée pour
463 être rendue disponible au même titre que les variables de calcul
465 if (type(asFunction) is type({})) and \
466 asFunction.has_key("useApproximatedDerivatives") and bool(asFunction["useApproximatedDerivatives"]) and \
467 asFunction.has_key("Direct") and (asFunction["Direct"] is not None):
468 if not asFunction.has_key("withCenteredDF"): asFunction["withCenteredDF"] = False
469 if not asFunction.has_key("withIncrement"): asFunction["withIncrement"] = 0.01
470 if not asFunction.has_key("withdX"): asFunction["withdX"] = None
471 from daNumerics.ApproximatedDerivatives import FDApproximation
472 FDA = FDApproximation(
473 Function = asFunction["Direct"],
474 centeredDF = asFunction["withCenteredDF"],
475 increment = asFunction["withIncrement"],
476 dX = asFunction["withdX"] )
477 self.__CM["Direct"] = Operator( fromMethod = FDA.DirectOperator )
478 self.__CM["Tangent"] = Operator( fromMethod = FDA.TangentOperator )
479 self.__CM["Adjoint"] = Operator( fromMethod = FDA.AdjointOperator )
480 elif (type(asFunction) is type({})) and \
481 asFunction.has_key("Tangent") and asFunction.has_key("Adjoint") and \
482 (asFunction["Tangent"] is not None) and (asFunction["Adjoint"] is not None):
483 if not asFunction.has_key("Direct") or (asFunction["Direct"] is None):
484 self.__CM["Direct"] = Operator( fromMethod = asFunction["Tangent"] )
486 self.__CM["Direct"] = Operator( fromMethod = asFunction["Direct"] )
487 self.__CM["Tangent"] = Operator( fromMethod = asFunction["Tangent"] )
488 self.__CM["Adjoint"] = Operator( fromMethod = asFunction["Adjoint"] )
489 elif asMatrix is not None:
490 matrice = numpy.matrix( asMatrix, numpy.float )
491 self.__CM["Direct"] = Operator( fromMatrix = matrice )
492 self.__CM["Tangent"] = Operator( fromMatrix = matrice )
493 self.__CM["Adjoint"] = Operator( fromMatrix = matrice.T )
496 raise ValueError("Improperly defined input control model, it requires at minima either a matrix, a Direct for approximate derivatives or a Tangent/Adjoint pair.")
499 self.__StoredInputs["ControlModel"] = self.__CM
502 def setControlInput(self,
504 asPersistentVector = None,
509 Permet de définir le controle en entree :
510 - asVector : entrée des données, comme un vecteur compatible avec le
511 constructeur de numpy.matrix
512 - asPersistentVector : entrée des données, comme un vecteur de type
513 persistent contruit avec la classe ad-hoc "Persistence"
514 - Scheduler est le contrôle temporel des données disponibles
515 - toBeStored : booléen indiquant si la donnée d'entrée est sauvée pour
516 être rendue disponible au même titre que les variables de calcul
518 if asVector is not None:
519 if isinstance(asVector,numpy.matrix):
520 self.__U = numpy.matrix( asVector.A1, numpy.float ).T
522 self.__U = numpy.matrix( asVector, numpy.float ).T
523 elif asPersistentVector is not None:
524 if type(asPersistentVector) in [type([]),type(()),type(numpy.array([])),type(numpy.matrix([]))]:
525 self.__U = Persistence.OneVector("ControlInput", basetype=numpy.matrix)
526 for member in asPersistentVector:
527 self.__U.store( numpy.matrix( numpy.asmatrix(member).A1, numpy.float ).T )
529 self.__U = asPersistentVector
531 raise ValueError("Error: improperly defined control input, it requires at minima either a vector, a list/tuple of vectors or a persistent object")
533 self.__StoredInputs["ControlInput"] = self.__U
536 # -----------------------------------------------------------
537 def setControls (self,
542 Permet de définir la valeur initiale du vecteur X contenant toutes les
543 variables de contrôle, i.e. les paramètres ou l'état dont on veut
544 estimer la valeur pour obtenir les observations. C'est utile pour un
545 algorithme itératif/incrémental.
546 - asVector : entrée des données, comme un vecteur compatible avec le
547 constructeur de numpy.matrix.
548 - toBeStored : booléen indiquant si la donnée d'entrée est sauvée pour
549 être rendue disponible au même titre que les variables de calcul
551 if asVector is not None:
552 self.__X.store( asVector )
554 self.__StoredInputs["Controls"] = self.__X
557 # -----------------------------------------------------------
558 def setAlgorithm(self, choice = None ):
560 Permet de sélectionner l'algorithme à utiliser pour mener à bien l'étude
561 d'assimilation. L'argument est un champ caractère se rapportant au nom
562 d'un fichier contenu dans "../daAlgorithms" et réalisant l'opération
563 d'assimilation sur les arguments fixes.
566 raise ValueError("Error: algorithm choice has to be given")
567 if self.__algorithmName is not None:
568 raise ValueError("Error: algorithm choice has already been done as \"%s\", it can't be changed."%self.__algorithmName)
569 daDirectory = "daAlgorithms"
571 # Recherche explicitement le fichier complet
572 # ------------------------------------------
574 for directory in sys.path:
575 if os.path.isfile(os.path.join(directory, daDirectory, str(choice)+'.py')):
576 module_path = os.path.abspath(os.path.join(directory, daDirectory))
577 if module_path is None:
578 raise ImportError("No algorithm module named \"%s\" was found in a \"%s\" subdirectory\n The search path is %s"%(choice, daDirectory, sys.path))
580 # Importe le fichier complet comme un module
581 # ------------------------------------------
583 sys_path_tmp = sys.path ; sys.path.insert(0,module_path)
584 self.__algorithmFile = __import__(str(choice), globals(), locals(), [])
585 self.__algorithmName = str(choice)
586 sys.path = sys_path_tmp ; del sys_path_tmp
587 except ImportError, e:
588 raise ImportError("The module named \"%s\" was found, but is incorrect at the import stage.\n The import error message is: %s"%(choice,e))
590 # Instancie un objet du type élémentaire du fichier
591 # -------------------------------------------------
592 self.__algorithm = self.__algorithmFile.ElementaryAlgorithm()
593 self.__StoredInputs["AlgorithmName"] = self.__algorithmName
596 def setAlgorithmParameters(self, asDico=None):
598 Permet de définir les paramètres de l'algorithme, sous la forme d'un
601 if asDico is not None:
602 self.__Parameters.update( dict( asDico ) )
604 self.__StoredInputs["AlgorithmParameters"] = self.__Parameters
607 def getAlgorithmParameters(self, noDetails=True):
609 Renvoie la liste des paramètres requis selon l'algorithme
611 return self.__algorithm.getRequiredParameters(noDetails)
613 # -----------------------------------------------------------
614 def setDiagnostic(self, choice = None, name = "", unit = "", basetype = None, parameters = {} ):
616 Permet de sélectionner un diagnostic a effectuer.
619 raise ValueError("Error: diagnostic choice has to be given")
620 daDirectory = "daDiagnostics"
622 # Recherche explicitement le fichier complet
623 # ------------------------------------------
625 for directory in sys.path:
626 if os.path.isfile(os.path.join(directory, daDirectory, str(choice)+'.py')):
627 module_path = os.path.abspath(os.path.join(directory, daDirectory))
628 if module_path is None:
629 raise ImportError("No diagnostic module named \"%s\" was found in a \"%s\" subdirectory\n The search path is %s"%(choice, daDirectory, sys.path))
631 # Importe le fichier complet comme un module
632 # ------------------------------------------
634 sys_path_tmp = sys.path ; sys.path.insert(0,module_path)
635 self.__diagnosticFile = __import__(str(choice), globals(), locals(), [])
636 sys.path = sys_path_tmp ; del sys_path_tmp
637 except ImportError, e:
638 raise ImportError("The module named \"%s\" was found, but is incorrect at the import stage.\n The import error message is: %s"%(choice,e))
640 # Instancie un objet du type élémentaire du fichier
641 # -------------------------------------------------
642 if self.__StoredInputs.has_key(name):
643 raise ValueError("A default input with the same name \"%s\" already exists."%str(name))
644 elif self.__StoredDiagnostics.has_key(name):
645 raise ValueError("A diagnostic with the same name \"%s\" already exists."%str(name))
647 self.__StoredDiagnostics[name] = self.__diagnosticFile.ElementaryDiagnostic(
651 parameters = parameters )
654 # -----------------------------------------------------------
655 def shape_validate(self):
657 Validation de la correspondance correcte des tailles des variables et
658 des matrices s'il y en a.
660 if self.__Xb is None: __Xb_shape = (0,)
661 elif hasattr(self.__Xb,"size"): __Xb_shape = (self.__Xb.size,)
662 elif hasattr(self.__Xb,"shape"):
663 if type(self.__Xb.shape) is tuple: __Xb_shape = self.__Xb.shape
664 else: __Xb_shape = self.__Xb.shape()
665 else: raise TypeError("Xb has no attribute of shape: problem !")
667 if self.__Y is None: __Y_shape = (0,)
668 elif hasattr(self.__Y,"size"): __Y_shape = (self.__Y.size,)
669 elif hasattr(self.__Y,"shape"):
670 if type(self.__Y.shape) is tuple: __Y_shape = self.__Y.shape
671 else: __Y_shape = self.__Y.shape()
672 else: raise TypeError("Y has no attribute of shape: problem !")
674 if self.__U is None: __U_shape = (0,)
675 elif hasattr(self.__U,"size"): __U_shape = (self.__U.size,)
676 elif hasattr(self.__U,"shape"):
677 if type(self.__U.shape) is tuple: __U_shape = self.__U.shape
678 else: __U_shape = self.__U.shape()
679 else: raise TypeError("U has no attribute of shape: problem !")
681 if self.__B is None: __B_shape = (0,0)
682 elif hasattr(self.__B,"shape"):
683 if type(self.__B.shape) is tuple: __B_shape = self.__B.shape
684 else: __B_shape = self.__B.shape()
685 else: raise TypeError("B has no attribute of shape: problem !")
687 if self.__R is None: __R_shape = (0,0)
688 elif hasattr(self.__R,"shape"):
689 if type(self.__R.shape) is tuple: __R_shape = self.__R.shape
690 else: __R_shape = self.__R.shape()
691 else: raise TypeError("R has no attribute of shape: problem !")
693 if self.__Q is None: __Q_shape = (0,0)
694 elif hasattr(self.__Q,"shape"):
695 if type(self.__Q.shape) is tuple: __Q_shape = self.__Q.shape
696 else: __Q_shape = self.__Q.shape()
697 else: raise TypeError("Q has no attribute of shape: problem !")
699 if len(self.__HO) == 0: __HO_shape = (0,0)
700 elif type(self.__HO) is type({}): __HO_shape = (0,0)
701 elif hasattr(self.__HO["Direct"],"shape"):
702 if type(self.__HO["Direct"].shape) is tuple: __HO_shape = self.__HO["Direct"].shape
703 else: __HO_shape = self.__HO["Direct"].shape()
704 else: raise TypeError("H has no attribute of shape: problem !")
706 if len(self.__EM) == 0: __EM_shape = (0,0)
707 elif type(self.__EM) is type({}): __EM_shape = (0,0)
708 elif hasattr(self.__EM["Direct"],"shape"):
709 if type(self.__EM["Direct"].shape) is tuple: __EM_shape = self.__EM["Direct"].shape
710 else: __EM_shape = self.__EM["Direct"].shape()
711 else: raise TypeError("EM has no attribute of shape: problem !")
713 if len(self.__CM) == 0: __CM_shape = (0,0)
714 elif type(self.__CM) is type({}): __CM_shape = (0,0)
715 elif hasattr(self.__CM["Direct"],"shape"):
716 if type(self.__CM["Direct"].shape) is tuple: __CM_shape = self.__CM["Direct"].shape
717 else: __CM_shape = self.__CM["Direct"].shape()
718 else: raise TypeError("CM has no attribute of shape: problem !")
720 # Vérification des conditions
721 # ---------------------------
722 if not( len(__Xb_shape) == 1 or min(__Xb_shape) == 1 ):
723 raise ValueError("Shape characteristic of Xb is incorrect: \"%s\""%(__Xb_shape,))
724 if not( len(__Y_shape) == 1 or min(__Y_shape) == 1 ):
725 raise ValueError("Shape characteristic of Y is incorrect: \"%s\""%(__Y_shape,))
727 if not( min(__B_shape) == max(__B_shape) ):
728 raise ValueError("Shape characteristic of B is incorrect: \"%s\""%(__B_shape,))
729 if not( min(__R_shape) == max(__R_shape) ):
730 raise ValueError("Shape characteristic of R is incorrect: \"%s\""%(__R_shape,))
731 if not( min(__Q_shape) == max(__Q_shape) ):
732 raise ValueError("Shape characteristic of Q is incorrect: \"%s\""%(__Q_shape,))
733 if not( min(__EM_shape) == max(__EM_shape) ):
734 raise ValueError("Shape characteristic of EM is incorrect: \"%s\""%(__EM_shape,))
736 if len(self.__HO) > 0 and not(type(self.__HO) is type({})) and not( __HO_shape[1] == max(__Xb_shape) ):
737 raise ValueError("Shape characteristic of H \"%s\" and X \"%s\" are incompatible"%(__HO_shape,__Xb_shape))
738 if len(self.__HO) > 0 and not(type(self.__HO) is type({})) and not( __HO_shape[0] == max(__Y_shape) ):
739 raise ValueError("Shape characteristic of H \"%s\" and Y \"%s\" are incompatible"%(__HO_shape,__Y_shape))
740 if len(self.__HO) > 0 and not(type(self.__HO) is type({})) and len(self.__B) > 0 and not( __HO_shape[1] == __B_shape[0] ):
741 raise ValueError("Shape characteristic of H \"%s\" and B \"%s\" are incompatible"%(__HO_shape,__B_shape))
742 if len(self.__HO) > 0 and not(type(self.__HO) is type({})) and len(self.__R) > 0 and not( __HO_shape[0] == __R_shape[1] ):
743 raise ValueError("Shape characteristic of H \"%s\" and R \"%s\" are incompatible"%(__HO_shape,__R_shape))
745 if self.__B is not None and len(self.__B) > 0 and not( __B_shape[1] == max(__Xb_shape) ):
746 if self.__StoredInputs["AlgorithmName"] in ["EnsembleBlue",]:
747 asPersistentVector = self.__Xb.reshape((-1,min(__B_shape)))
748 self.__Xb = Persistence.OneVector("Background", basetype=numpy.matrix)
749 for member in asPersistentVector:
750 self.__Xb.store( numpy.matrix( numpy.ravel(member), numpy.float ).T )
751 __Xb_shape = min(__B_shape)
753 raise ValueError("Shape characteristic of B \"%s\" and Xb \"%s\" are incompatible"%(__B_shape,__Xb_shape))
755 if self.__R is not None and len(self.__R) > 0 and not( __R_shape[1] == max(__Y_shape) ):
756 raise ValueError("Shape characteristic of R \"%s\" and Y \"%s\" are incompatible"%(__R_shape,__Y_shape))
758 if self.__EM is not None and len(self.__EM) > 0 and not(type(self.__EM) is type({})) and not( __EM_shape[1] == max(__Xb_shape) ):
759 raise ValueError("Shape characteristic of EM \"%s\" and X \"%s\" are incompatible"%(__EM_shape,__Xb_shape))
761 if self.__CM is not None and len(self.__CM) > 0 and not(type(self.__CM) is type({})) and not( __CM_shape[1] == max(__U_shape) ):
762 raise ValueError("Shape characteristic of CM \"%s\" and U \"%s\" are incompatible"%(__CM_shape,__U_shape))
766 # -----------------------------------------------------------
769 Permet de lancer le calcul d'assimilation.
771 Le nom de la méthode à activer est toujours "run". Les paramètres en
772 arguments de la méthode sont fixés. En sortie, on obtient les résultats
773 dans la variable de type dictionnaire "StoredVariables", qui contient en
774 particulier des objets de Persistence pour les analyses, OMA...
776 self.shape_validate()
778 self.__algorithm.run(
788 Parameters = self.__Parameters,
792 # -----------------------------------------------------------
793 def get(self, key=None):
795 Renvoie les résultats disponibles après l'exécution de la méthode
796 d'assimilation, ou les diagnostics disponibles. Attention, quand un
797 diagnostic porte le même nom qu'une variable stockée, c'est la variable
798 stockée qui est renvoyée, et le diagnostic est inatteignable.
801 if self.__algorithm.has_key(key):
802 return self.__algorithm.get( key )
803 elif self.__StoredInputs.has_key(key):
804 return self.__StoredInputs[key]
805 elif self.__StoredDiagnostics.has_key(key):
806 return self.__StoredDiagnostics[key]
808 raise ValueError("The requested key \"%s\" does not exists as an input, a diagnostic or a stored variable."%key)
810 allvariables = self.__algorithm.get()
811 allvariables.update( self.__StoredDiagnostics )
812 allvariables.update( self.__StoredInputs )
815 def get_available_variables(self):
817 Renvoie les variables potentiellement utilisables pour l'étude,
818 initialement stockées comme données d'entrées ou dans les algorithmes,
819 identifiés par les chaînes de caractères. L'algorithme doit avoir été
820 préalablement choisi sinon la méthode renvoie "None".
822 if len( self.__algorithm.keys()) == 0 and len( self.__StoredInputs.keys() ) == 0:
826 if len( self.__algorithm.keys()) > 0:
827 variables.extend( self.__algorithm.get().keys() )
828 if len( self.__StoredInputs.keys() ) > 0:
829 variables.extend( self.__StoredInputs.keys() )
833 def get_available_algorithms(self):
835 Renvoie la liste des algorithmes potentiellement utilisables, identifiés
836 par les chaînes de caractères.
839 for directory in sys.path:
840 if os.path.isdir(os.path.join(directory,"daAlgorithms")):
841 for fname in os.listdir(os.path.join(directory,"daAlgorithms")):
842 root, ext = os.path.splitext(fname)
843 if ext == '.py' and root != '__init__':
848 def get_available_diagnostics(self):
850 Renvoie la liste des diagnostics potentiellement utilisables, identifiés
851 par les chaînes de caractères.
854 for directory in sys.path:
855 if os.path.isdir(os.path.join(directory,"daDiagnostics")):
856 for fname in os.listdir(os.path.join(directory,"daDiagnostics")):
857 root, ext = os.path.splitext(fname)
858 if ext == '.py' and root != '__init__':
863 # -----------------------------------------------------------
864 def get_algorithms_main_path(self):
866 Renvoie le chemin pour le répertoire principal contenant les algorithmes
867 dans un sous-répertoire "daAlgorithms"
871 def add_algorithms_path(self, asPath=None):
873 Ajoute au chemin de recherche des algorithmes un répertoire dans lequel
874 se trouve un sous-répertoire "daAlgorithms"
876 Remarque : si le chemin a déjà été ajouté pour les diagnostics, il n'est
877 pas indispensable de le rajouter ici.
879 if not os.path.isdir(asPath):
880 raise ValueError("The given "+asPath+" argument must exist as a directory")
881 if not os.path.isdir(os.path.join(asPath,"daAlgorithms")):
882 raise ValueError("The given \""+asPath+"\" argument must contain a subdirectory named \"daAlgorithms\"")
883 if not os.path.isfile(os.path.join(asPath,"daAlgorithms","__init__.py")):
884 raise ValueError("The given \""+asPath+"/daAlgorithms\" path must contain a file named \"__init__.py\"")
885 sys.path.insert(0, os.path.abspath(asPath))
886 sys.path = uniq( sys.path ) # Conserve en unique exemplaire chaque chemin
889 def get_diagnostics_main_path(self):
891 Renvoie le chemin pour le répertoire principal contenant les diagnostics
892 dans un sous-répertoire "daDiagnostics"
896 def add_diagnostics_path(self, asPath=None):
898 Ajoute au chemin de recherche des algorithmes un répertoire dans lequel
899 se trouve un sous-répertoire "daDiagnostics"
901 Remarque : si le chemin a déjà été ajouté pour les algorithmes, il n'est
902 pas indispensable de le rajouter ici.
904 if not os.path.isdir(asPath):
905 raise ValueError("The given "+asPath+" argument must exist as a directory")
906 if not os.path.isdir(os.path.join(asPath,"daDiagnostics")):
907 raise ValueError("The given \""+asPath+"\" argument must contain a subdirectory named \"daDiagnostics\"")
908 if not os.path.isfile(os.path.join(asPath,"daDiagnostics","__init__.py")):
909 raise ValueError("The given \""+asPath+"/daDiagnostics\" path must contain a file named \"__init__.py\"")
910 sys.path.insert(0, os.path.abspath(asPath))
911 sys.path = uniq( sys.path ) # Conserve en unique exemplaire chaque chemin
914 # -----------------------------------------------------------
915 def setDataObserver(self,
918 HookParameters = None,
922 Permet d'associer un observer à une ou des variables nommées gérées en
923 interne, activable selon des règles définies dans le Scheduler. A chaque
924 pas demandé dans le Scheduler, il effectue la fonction HookFunction avec
925 les arguments (variable persistante VariableName, paramètres HookParameters).
928 if type( self.__algorithm ) is dict:
929 raise ValueError("No observer can be build before choosing an algorithm.")
931 # Vérification du nom de variable et typage
932 # -----------------------------------------
933 if type( VariableName ) is str:
934 VariableNames = [VariableName,]
935 elif type( VariableName ) is list:
936 VariableNames = map( str, VariableName )
938 raise ValueError("The observer requires a name or a list of names of variables.")
940 # Association interne de l'observer à la variable
941 # -----------------------------------------------
942 for n in VariableNames:
943 if not self.__algorithm.has_key( n ):
944 raise ValueError("An observer requires to be set on a variable named %s which does not exist."%n)
946 self.__algorithm.StoredVariables[ n ].setDataObserver(
947 Scheduler = Scheduler,
948 HookFunction = HookFunction,
949 HookParameters = HookParameters,
952 def removeDataObserver(self,
957 Permet de retirer un observer à une ou des variable nommée.
960 if type( self.__algorithm ) is dict:
961 raise ValueError("No observer can be removed before choosing an algorithm.")
963 # Vérification du nom de variable et typage
964 # -----------------------------------------
965 if type( VariableName ) is str:
966 VariableNames = [VariableName,]
967 elif type( VariableName ) is list:
968 VariableNames = map( str, VariableName )
970 raise ValueError("The observer requires a name or a list of names of variables.")
972 # Association interne de l'observer à la variable
973 # -----------------------------------------------
974 for n in VariableNames:
975 if not self.__algorithm.has_key( n ):
976 raise ValueError("An observer requires to be removed on a variable named %s which does not exist."%n)
978 self.__algorithm.StoredVariables[ n ].removeDataObserver(
979 HookFunction = HookFunction,
982 # -----------------------------------------------------------
983 def setDebug(self, level=10):
985 Utiliser par exemple "import logging ; level = logging.DEBUG" avant cet
986 appel pour changer le niveau de verbosité, avec :
987 NOTSET=0 < DEBUG=10 < INFO=20 < WARNING=30 < ERROR=40 < CRITICAL=50
990 log = logging.getLogger()
991 log.setLevel( level )
993 def unsetDebug(self):
995 Remet le logger au niveau par défaut
998 log = logging.getLogger()
999 log.setLevel( logging.WARNING )
1001 def prepare_to_pickle(self):
1002 self.__algorithmFile = None
1003 self.__diagnosticFile = None
1008 # ==============================================================================
1009 if __name__ == "__main__":
1010 print '\n AUTODIAGNOSTIC \n'