1 #-*-coding:iso-8859-1-*-
3 # Copyright (C) 2008-2014 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 ExtendedLogging ; ExtendedLogging.ExtendedLogging() # A importer en premier
40 from BasicObjects import Operator, Covariance
41 from PlatformInfo import uniq
43 # ==============================================================================
44 class AssimilationStudy:
46 Cette classe sert d'interface pour l'utilisation de l'assimilation de
47 données. Elle contient les méthodes ou accesseurs nécessaires à la
48 construction d'un calcul d'assimilation.
50 def __init__(self, name=""):
52 Prévoit de conserver l'ensemble des variables nécssaires à un algorithme
53 élémentaire. Ces variables sont ensuite disponibles pour implémenter un
54 algorithme élémentaire particulier.
56 - Background : vecteur Xb
57 - Observation : vecteur Y (potentiellement temporel) d'observations
58 - State : vecteur d'état dont une partie est le vecteur de contrôle.
59 Cette information n'est utile que si l'on veut faire des calculs sur
60 l'état complet, mais elle n'est pas indispensable pour l'assimilation.
61 - Control : vecteur X contenant toutes les variables de contrôle, i.e.
62 les paramètres ou l'état dont on veut estimer la valeur pour obtenir
64 - ObservationOperator...: opérateur d'observation H
66 Les observations présentent une erreur dont la matrice de covariance est
67 R. L'ébauche du vecteur de contrôle présente une erreur dont la matrice
70 self.__name = str(name)
81 self.__X = Persistence.OneVector()
82 self.__Parameters = {}
83 self.__StoredDiagnostics = {}
84 self.__StoredInputs = {}
86 # Variables temporaires
88 self.__algorithmFile = None
89 self.__algorithmName = None
90 self.__diagnosticFile = None
92 # Récupère le chemin du répertoire parent et l'ajoute au path
93 # (Cela complète l'action de la classe PathManagement dans PlatformInfo,
94 # qui est activée dans Persistence)
95 self.__parent = os.path.abspath(os.path.join(os.path.dirname(__file__),".."))
96 sys.path.insert(0, self.__parent)
97 sys.path = uniq( sys.path ) # Conserve en unique exemplaire chaque chemin
99 # ---------------------------------------------------------
100 def setBackground(self,
102 asPersistentVector = None,
107 Permet de définir l'estimation a priori :
108 - asVector : entrée des données, comme un vecteur compatible avec le
109 constructeur de numpy.matrix
110 - asPersistentVector : entrée des données, comme un vecteur de type
111 persistent contruit avec la classe ad-hoc "Persistence"
112 - Scheduler est le contrôle temporel des données
113 - toBeStored : booléen indiquant si la donnée d'entrée est sauvée pour
114 être rendue disponible au même titre que les variables de calcul
116 if asVector is not None:
117 if type( asVector ) is type( numpy.matrix([]) ):
118 self.__Xb = numpy.matrix( asVector.A1, numpy.float ).T
120 self.__Xb = 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 )
127 self.__Xb = asPersistentVector
129 raise ValueError("Error: improperly defined background, it requires at minima either a vector, a list/tuple of vectors or a persistent object")
131 self.__StoredInputs["Background"] = self.__Xb
134 def setBackgroundError(self,
136 asEyeByScalar = None,
137 asEyeByVector = None,
141 Permet de définir la covariance des erreurs d'ébauche :
142 - asCovariance : entrée des données, comme une matrice compatible avec
143 le constructeur de numpy.matrix
144 - asEyeByScalar : entrée des données comme un seul scalaire de variance,
145 multiplicatif d'une matrice de corrélation identité, aucune matrice
146 n'étant donc explicitement à donner
147 - asEyeByVector : entrée des données comme un seul vecteur de variance,
148 à mettre sur la diagonale d'une matrice de corrélation, aucune matrice
149 n'étant donc explicitement à donner
150 - toBeStored : booléen indiquant si la donnée d'entrée est sauvée pour
151 être rendue disponible au même titre que les variables de calcul
153 self.__B = Covariance(
154 name = "BackgroundError",
155 asCovariance = asCovariance,
156 asEyeByScalar = asEyeByScalar,
157 asEyeByVector = asEyeByVector,
160 self.__StoredInputs["BackgroundError"] = self.__B
163 # -----------------------------------------------------------
164 def setObservation(self,
166 asPersistentVector = None,
171 Permet de définir les observations :
172 - asVector : entrée des données, comme un vecteur compatible avec le
173 constructeur de numpy.matrix
174 - asPersistentVector : entrée des données, comme un vecteur de type
175 persistent contruit avec la classe ad-hoc "Persistence"
176 - Scheduler est le contrôle temporel des données disponibles
177 - toBeStored : booléen indiquant si la donnée d'entrée est sauvée pour
178 être rendue disponible au même titre que les variables de calcul
180 if asVector is not None:
181 if type( asVector ) is type( numpy.matrix([]) ):
182 self.__Y = numpy.matrix( asVector.A1, numpy.float ).T
184 self.__Y = numpy.matrix( asVector, numpy.float ).T
185 elif asPersistentVector is not None:
186 if type(asPersistentVector) in [type([]),type(()),type(numpy.array([])),type(numpy.matrix([]))]:
187 self.__Y = Persistence.OneVector("Observation", basetype=numpy.matrix)
188 for member in asPersistentVector:
189 self.__Y.store( numpy.matrix( numpy.asmatrix(member).A1, numpy.float ).T )
191 self.__Y = asPersistentVector
193 raise ValueError("Error: improperly defined observations, it requires at minima either a vector, a list/tuple of vectors or a persistent object")
195 self.__StoredInputs["Observation"] = self.__Y
198 def setObservationError(self,
200 asEyeByScalar = None,
201 asEyeByVector = None,
205 Permet de définir la covariance des erreurs d'observations :
206 - asCovariance : entrée des données, comme une matrice compatible avec
207 le constructeur de numpy.matrix
208 - asEyeByScalar : entrée des données comme un seul scalaire de variance,
209 multiplicatif d'une matrice de corrélation identité, aucune matrice
210 n'étant donc explicitement à donner
211 - asEyeByVector : entrée des données comme un seul vecteur de variance,
212 à mettre sur la diagonale d'une matrice de corrélation, aucune matrice
213 n'étant donc explicitement à donner
214 - toBeStored : booléen indiquant si la donnée d'entrée est sauvée pour
215 être rendue disponible au même titre que les variables de calcul
217 self.__R = Covariance(
218 name = "ObservationError",
219 asCovariance = asCovariance,
220 asEyeByScalar = asEyeByScalar,
221 asEyeByVector = asEyeByVector,
224 self.__StoredInputs["ObservationError"] = self.__R
227 def setObservationOperator(self,
234 Permet de définir un opérateur d'observation H. L'ordre de priorité des
235 définitions et leur sens sont les suivants :
236 - si asFunction["Tangent"] et asFunction["Adjoint"] ne sont pas None
237 alors on définit l'opérateur à l'aide de fonctions. Si la fonction
238 "Direct" n'est pas définie, on prend la fonction "Tangent".
239 Si "useApproximatedDerivatives" est vrai, on utilise une approximation
240 des opérateurs tangents et adjoints. On utilise par défaut des
241 différences finies non centrées ou centrées (si "withCenteredDF" est
242 vrai) avec un incrément multiplicatif "withIncrement" de 1% autour
243 du point courant ou sur le point fixe "withdX".
244 - si les fonctions ne sont pas disponibles et si asMatrix n'est pas
245 None, alors on définit l'opérateur "Direct" et "Tangent" à l'aide de
246 la matrice, et l'opérateur "Adjoint" à l'aide de la transposée. La
247 matrice fournie doit être sous une forme compatible avec le
248 constructeur de numpy.matrix.
249 - si l'argument "appliedToX" n'est pas None, alors on définit, pour des
250 X divers, l'opérateur par sa valeur appliquée à cet X particulier,
251 sous la forme d'un dictionnaire appliedToX[NAME] avec NAME un nom.
252 L'opérateur doit néanmoins déjà avoir été défini comme d'habitude.
253 - toBeStored : booléen indiquant si la donnée d'entrée est sauvée pour
254 être rendue disponible au même titre que les variables de calcul
255 L'argument "asFunction" peut prendre la forme complète suivante, avec
256 les valeurs par défaut standards :
257 asFunction = {"Direct":None, "Tangent":None, "Adjoint":None,
258 "useApproximatedDerivatives":False,
259 "withCenteredDF" :False,
260 "withIncrement" :0.01,
262 "withAvoidingRedundancy" :True,
263 "withToleranceInRedundancy" :1.e-18,
264 "withLenghtOfRedundancy" :-1,
265 "withmpEnabled" :False,
266 "withmpWorkers" :None,
269 if (type(asFunction) is type({})) and \
270 asFunction.has_key("useApproximatedDerivatives") and bool(asFunction["useApproximatedDerivatives"]) and \
271 asFunction.has_key("Direct") and (asFunction["Direct"] is not None):
272 if not asFunction.has_key("withCenteredDF"): asFunction["withCenteredDF"] = False
273 if not asFunction.has_key("withIncrement"): asFunction["withIncrement"] = 0.01
274 if not asFunction.has_key("withdX"): asFunction["withdX"] = None
275 if not asFunction.has_key("withAvoidingRedundancy"): asFunction["withAvoidingRedundancy"] = True
276 if not asFunction.has_key("withToleranceInRedundancy"): asFunction["withToleranceInRedundancy"] = 1.e-18
277 if not asFunction.has_key("withLenghtOfRedundancy"): asFunction["withLenghtOfRedundancy"] = -1
278 if not asFunction.has_key("withmpEnabled"): asFunction["withmpEnabled"] = False
279 if not asFunction.has_key("withmpWorkers"): asFunction["withmpWorkers"] = None
280 from daNumerics.ApproximatedDerivatives import FDApproximation
281 FDA = FDApproximation(
282 Function = asFunction["Direct"],
283 centeredDF = asFunction["withCenteredDF"],
284 increment = asFunction["withIncrement"],
285 dX = asFunction["withdX"],
286 avoidingRedundancy = asFunction["withAvoidingRedundancy"],
287 toleranceInRedundancy = asFunction["withToleranceInRedundancy"],
288 lenghtOfRedundancy = asFunction["withLenghtOfRedundancy"],
289 mpEnabled = asFunction["withmpEnabled"],
290 mpWorkers = asFunction["withmpWorkers"],
292 self.__HO["Direct"] = Operator( fromMethod = FDA.DirectOperator )
293 self.__HO["Tangent"] = Operator( fromMethod = FDA.TangentOperator )
294 self.__HO["Adjoint"] = Operator( fromMethod = FDA.AdjointOperator )
295 elif (type(asFunction) is type({})) and \
296 asFunction.has_key("Tangent") and asFunction.has_key("Adjoint") and \
297 (asFunction["Tangent"] is not None) and (asFunction["Adjoint"] is not None):
298 if not asFunction.has_key("Direct") or (asFunction["Direct"] is None):
299 self.__HO["Direct"] = Operator( fromMethod = asFunction["Tangent"] )
301 self.__HO["Direct"] = Operator( fromMethod = asFunction["Direct"] )
302 self.__HO["Tangent"] = Operator( fromMethod = asFunction["Tangent"] )
303 self.__HO["Adjoint"] = Operator( fromMethod = asFunction["Adjoint"] )
304 elif asMatrix is not None:
305 matrice = numpy.matrix( asMatrix, numpy.float )
306 self.__HO["Direct"] = Operator( fromMatrix = matrice )
307 self.__HO["Tangent"] = Operator( fromMatrix = matrice )
308 self.__HO["Adjoint"] = Operator( fromMatrix = matrice.T )
311 raise ValueError("Improperly defined observation operator, it requires at minima either a matrix, a Direct for approximate derivatives or a Tangent/Adjoint pair.")
313 if appliedToX is not None:
314 self.__HO["AppliedToX"] = {}
315 if type(appliedToX) is not dict:
316 raise ValueError("Error: observation operator defined by \"appliedToX\" need a dictionary as argument.")
317 for key in appliedToX.keys():
318 if type( appliedToX[key] ) is type( numpy.matrix([]) ):
319 # Pour le cas où l'on a une vraie matrice
320 self.__HO["AppliedToX"][key] = numpy.matrix( appliedToX[key].A1, numpy.float ).T
321 elif type( appliedToX[key] ) is type( numpy.array([]) ) and len(appliedToX[key].shape) > 1:
322 # Pour le cas où l'on a un vecteur représenté en array avec 2 dimensions
323 self.__HO["AppliedToX"][key] = numpy.matrix( appliedToX[key].reshape(len(appliedToX[key]),), numpy.float ).T
325 self.__HO["AppliedToX"][key] = numpy.matrix( appliedToX[key], numpy.float ).T
327 self.__HO["AppliedToX"] = None
330 self.__StoredInputs["ObservationOperator"] = self.__HO
333 # -----------------------------------------------------------
334 def setEvolutionModel(self,
341 Permet de définir un opérateur d'évolution M. L'ordre de priorité des
342 définitions et leur sens sont les suivants :
343 - si asFunction["Tangent"] et asFunction["Adjoint"] ne sont pas None
344 alors on définit l'opérateur à l'aide de fonctions. Si la fonction
345 "Direct" n'est pas définie, on prend la fonction "Tangent".
346 Si "useApproximatedDerivatives" est vrai, on utilise une approximation
347 des opérateurs tangents et adjoints. On utilise par défaut des
348 différences finies non centrées ou centrées (si "withCenteredDF" est
349 vrai) avec un incrément multiplicatif "withIncrement" de 1% autour
350 du point courant ou sur le point fixe "withdX".
351 - si les fonctions ne sont pas disponibles et si asMatrix n'est pas
352 None, alors on définit l'opérateur "Direct" et "Tangent" à l'aide de
353 la matrice, et l'opérateur "Adjoint" à l'aide de la transposée. La
354 matrice fournie doit être sous une forme compatible avec le
355 constructeur de numpy.matrix.
356 - toBeStored : booléen indiquant si la donnée d'entrée est sauvée pour
357 être rendue disponible au même titre que les variables de calcul
358 L'argument "asFunction" peut prendre la forme complète suivante, avec
359 les valeurs par défaut standards :
360 asFunction = {"Direct":None, "Tangent":None, "Adjoint":None,
361 "useApproximatedDerivatives":False,
362 "withCenteredDF" :False,
363 "withIncrement" :0.01,
365 "withAvoidingRedundancy" :True,
366 "withToleranceInRedundancy" :1.e-18,
367 "withLenghtOfRedundancy" :-1,
368 "withmpEnabled" :False,
369 "withmpWorkers" :None,
372 if (type(asFunction) is type({})) and \
373 asFunction.has_key("useApproximatedDerivatives") and bool(asFunction["useApproximatedDerivatives"]) and \
374 asFunction.has_key("Direct") and (asFunction["Direct"] is not None):
375 if not asFunction.has_key("withCenteredDF"): asFunction["withCenteredDF"] = False
376 if not asFunction.has_key("withIncrement"): asFunction["withIncrement"] = 0.01
377 if not asFunction.has_key("withdX"): asFunction["withdX"] = None
378 if not asFunction.has_key("withAvoidingRedundancy"): asFunction["withAvoidingRedundancy"] = True
379 if not asFunction.has_key("withToleranceInRedundancy"): asFunction["withToleranceInRedundancy"] = 1.e-18
380 if not asFunction.has_key("withLenghtOfRedundancy"): asFunction["withLenghtOfRedundancy"] = -1
381 if not asFunction.has_key("withmpEnabled"): asFunction["withmpEnabled"] = False
382 if not asFunction.has_key("withmpWorkers"): asFunction["withmpWorkers"] = None
383 from daNumerics.ApproximatedDerivatives import FDApproximation
384 FDA = FDApproximation(
385 Function = asFunction["Direct"],
386 centeredDF = asFunction["withCenteredDF"],
387 increment = asFunction["withIncrement"],
388 dX = asFunction["withdX"],
389 avoidingRedundancy = asFunction["withAvoidingRedundancy"],
390 toleranceInRedundancy = asFunction["withToleranceInRedundancy"],
391 lenghtOfRedundancy = asFunction["withLenghtOfRedundancy"],
392 mpEnabled = asFunction["withmpEnabled"],
393 mpWorkers = asFunction["withmpWorkers"],
395 self.__EM["Direct"] = Operator( fromMethod = FDA.DirectOperator )
396 self.__EM["Tangent"] = Operator( fromMethod = FDA.TangentOperator )
397 self.__EM["Adjoint"] = Operator( fromMethod = FDA.AdjointOperator )
398 elif (type(asFunction) is type({})) and \
399 asFunction.has_key("Tangent") and asFunction.has_key("Adjoint") and \
400 (asFunction["Tangent"] is not None) and (asFunction["Adjoint"] is not None):
401 if not asFunction.has_key("Direct") or (asFunction["Direct"] is None):
402 self.__EM["Direct"] = Operator( fromMethod = asFunction["Tangent"] )
404 self.__EM["Direct"] = Operator( fromMethod = asFunction["Direct"] )
405 self.__EM["Tangent"] = Operator( fromMethod = asFunction["Tangent"] )
406 self.__EM["Adjoint"] = Operator( fromMethod = asFunction["Adjoint"] )
407 elif asMatrix is not None:
408 matrice = numpy.matrix( asMatrix, numpy.float )
409 self.__EM["Direct"] = Operator( fromMatrix = matrice )
410 self.__EM["Tangent"] = Operator( fromMatrix = matrice )
411 self.__EM["Adjoint"] = Operator( fromMatrix = matrice.T )
414 raise ValueError("Improperly defined evolution model, it requires at minima either a matrix, a Direct for approximate derivatives or a Tangent/Adjoint pair.")
417 self.__StoredInputs["EvolutionModel"] = self.__EM
420 def setEvolutionError(self,
422 asEyeByScalar = None,
423 asEyeByVector = None,
427 Permet de définir la covariance des erreurs de modèle :
428 - asCovariance : entrée des données, comme une matrice compatible avec
429 le constructeur de numpy.matrix
430 - asEyeByScalar : entrée des données comme un seul scalaire de variance,
431 multiplicatif d'une matrice de corrélation identité, aucune matrice
432 n'étant donc explicitement à donner
433 - asEyeByVector : entrée des données comme un seul vecteur de variance,
434 à mettre sur la diagonale d'une matrice de corrélation, aucune matrice
435 n'étant donc explicitement à donner
436 - toBeStored : booléen indiquant si la donnée d'entrée est sauvée pour
437 être rendue disponible au même titre que les variables de calcul
439 self.__Q = Covariance(
440 name = "ObservationError",
441 asCovariance = asCovariance,
442 asEyeByScalar = asEyeByScalar,
443 asEyeByVector = asEyeByVector,
446 self.__StoredInputs["EvolutionError"] = self.__Q
449 # -----------------------------------------------------------
450 def setControlModel(self,
451 asFunction = {"Direct":None, "Tangent":None, "Adjoint":None,
452 "useApproximatedDerivatives":False,
453 "withCenteredDF" :False,
454 "withIncrement" :0.01,
462 Permet de définir un opérateur de controle C. L'ordre de priorité des
463 définitions et leur sens sont les suivants :
464 - si asFunction["Tangent"] et asFunction["Adjoint"] ne sont pas None
465 alors on définit l'opérateur à l'aide de fonctions. Si la fonction
466 "Direct" n'est pas définie, on prend la fonction "Tangent".
467 Si "useApproximatedDerivatives" est vrai, on utilise une approximation
468 des opérateurs tangents et adjoints. On utilise par défaut des
469 différences finies non centrées ou centrées (si "withCenteredDF" est
470 vrai) avec un incrément multiplicatif "withIncrement" de 1% autour
471 du point courant ou sur le point fixe "withdX".
472 - si les fonctions ne sont pas disponibles et si asMatrix n'est pas
473 None, alors on définit l'opérateur "Direct" et "Tangent" à l'aide de
474 la matrice, et l'opérateur "Adjoint" à l'aide de la transposée. La
475 matrice fournie doit être sous une forme compatible avec le
476 constructeur de numpy.matrix.
477 - toBeStored : booléen indiquant si la donnée d'entrée est sauvée pour
478 être rendue disponible au même titre que les variables de calcul
480 if (type(asFunction) is type({})) and \
481 asFunction.has_key("useApproximatedDerivatives") and bool(asFunction["useApproximatedDerivatives"]) and \
482 asFunction.has_key("Direct") and (asFunction["Direct"] is not None):
483 if not asFunction.has_key("withCenteredDF"): asFunction["withCenteredDF"] = False
484 if not asFunction.has_key("withIncrement"): asFunction["withIncrement"] = 0.01
485 if not asFunction.has_key("withdX"): asFunction["withdX"] = None
486 from daNumerics.ApproximatedDerivatives import FDApproximation
487 FDA = FDApproximation(
488 Function = asFunction["Direct"],
489 centeredDF = asFunction["withCenteredDF"],
490 increment = asFunction["withIncrement"],
491 dX = asFunction["withdX"] )
492 self.__CM["Direct"] = Operator( fromMethod = FDA.DirectOperator )
493 self.__CM["Tangent"] = Operator( fromMethod = FDA.TangentOperator )
494 self.__CM["Adjoint"] = Operator( fromMethod = FDA.AdjointOperator )
495 elif (type(asFunction) is type({})) and \
496 asFunction.has_key("Tangent") and asFunction.has_key("Adjoint") and \
497 (asFunction["Tangent"] is not None) and (asFunction["Adjoint"] is not None):
498 if not asFunction.has_key("Direct") or (asFunction["Direct"] is None):
499 self.__CM["Direct"] = Operator( fromMethod = asFunction["Tangent"] )
501 self.__CM["Direct"] = Operator( fromMethod = asFunction["Direct"] )
502 self.__CM["Tangent"] = Operator( fromMethod = asFunction["Tangent"] )
503 self.__CM["Adjoint"] = Operator( fromMethod = asFunction["Adjoint"] )
504 elif asMatrix is not None:
505 matrice = numpy.matrix( asMatrix, numpy.float )
506 self.__CM["Direct"] = Operator( fromMatrix = matrice )
507 self.__CM["Tangent"] = Operator( fromMatrix = matrice )
508 self.__CM["Adjoint"] = Operator( fromMatrix = matrice.T )
511 raise ValueError("Improperly defined input control model, it requires at minima either a matrix, a Direct for approximate derivatives or a Tangent/Adjoint pair.")
514 self.__StoredInputs["ControlModel"] = self.__CM
517 def setControlInput(self,
519 asPersistentVector = None,
524 Permet de définir le controle en entree :
525 - asVector : entrée des données, comme un vecteur compatible avec le
526 constructeur de numpy.matrix
527 - asPersistentVector : entrée des données, comme un vecteur de type
528 persistent contruit avec la classe ad-hoc "Persistence"
529 - Scheduler est le contrôle temporel des données disponibles
530 - toBeStored : booléen indiquant si la donnée d'entrée est sauvée pour
531 être rendue disponible au même titre que les variables de calcul
533 if asVector is not None:
534 if isinstance(asVector,numpy.matrix):
535 self.__U = numpy.matrix( asVector.A1, numpy.float ).T
537 self.__U = numpy.matrix( asVector, numpy.float ).T
538 elif asPersistentVector is not None:
539 if type(asPersistentVector) in [type([]),type(()),type(numpy.array([])),type(numpy.matrix([]))]:
540 self.__U = Persistence.OneVector("ControlInput", basetype=numpy.matrix)
541 for member in asPersistentVector:
542 self.__U.store( numpy.matrix( numpy.asmatrix(member).A1, numpy.float ).T )
544 self.__U = asPersistentVector
546 raise ValueError("Error: improperly defined control input, it requires at minima either a vector, a list/tuple of vectors or a persistent object")
548 self.__StoredInputs["ControlInput"] = self.__U
551 # -----------------------------------------------------------
552 def setControls (self,
557 Permet de définir la valeur initiale du vecteur X contenant toutes les
558 variables de contrôle, i.e. les paramètres ou l'état dont on veut
559 estimer la valeur pour obtenir les observations. C'est utile pour un
560 algorithme itératif/incrémental.
561 - asVector : entrée des données, comme un vecteur compatible avec le
562 constructeur de numpy.matrix.
563 - toBeStored : booléen indiquant si la donnée d'entrée est sauvée pour
564 être rendue disponible au même titre que les variables de calcul
566 if asVector is not None:
567 self.__X.store( asVector )
569 self.__StoredInputs["Controls"] = self.__X
572 # -----------------------------------------------------------
573 def setAlgorithm(self, choice = None ):
575 Permet de sélectionner l'algorithme à utiliser pour mener à bien l'étude
576 d'assimilation. L'argument est un champ caractère se rapportant au nom
577 d'un fichier contenu dans "../daAlgorithms" et réalisant l'opération
578 d'assimilation sur les arguments fixes.
581 raise ValueError("Error: algorithm choice has to be given")
582 if self.__algorithmName is not None:
583 raise ValueError("Error: algorithm choice has already been done as \"%s\", it can't be changed."%self.__algorithmName)
584 daDirectory = "daAlgorithms"
586 # Recherche explicitement le fichier complet
587 # ------------------------------------------
589 for directory in sys.path:
590 if os.path.isfile(os.path.join(directory, daDirectory, str(choice)+'.py')):
591 module_path = os.path.abspath(os.path.join(directory, daDirectory))
592 if module_path is None:
593 raise ImportError("No algorithm module named \"%s\" was found in a \"%s\" subdirectory\n The search path is %s"%(choice, daDirectory, sys.path))
595 # Importe le fichier complet comme un module
596 # ------------------------------------------
598 sys_path_tmp = sys.path ; sys.path.insert(0,module_path)
599 self.__algorithmFile = __import__(str(choice), globals(), locals(), [])
600 self.__algorithmName = str(choice)
601 sys.path = sys_path_tmp ; del sys_path_tmp
602 except ImportError, e:
603 raise ImportError("The module named \"%s\" was found, but is incorrect at the import stage.\n The import error message is: %s"%(choice,e))
605 # Instancie un objet du type élémentaire du fichier
606 # -------------------------------------------------
607 self.__algorithm = self.__algorithmFile.ElementaryAlgorithm()
608 self.__StoredInputs["AlgorithmName"] = self.__algorithmName
611 def setAlgorithmParameters(self, asDico=None):
613 Permet de définir les paramètres de l'algorithme, sous la forme d'un
616 if asDico is not None:
617 self.__Parameters.update( dict( asDico ) )
619 self.__StoredInputs["AlgorithmParameters"] = self.__Parameters
622 def getAlgorithmParameters(self, noDetails=True):
624 Renvoie la liste des paramètres requis selon l'algorithme
626 return self.__algorithm.getRequiredParameters(noDetails)
628 # -----------------------------------------------------------
629 def setDiagnostic(self, choice = None, name = "", unit = "", basetype = None, parameters = {} ):
631 Permet de sélectionner un diagnostic a effectuer.
634 raise ValueError("Error: diagnostic choice has to be given")
635 daDirectory = "daDiagnostics"
637 # Recherche explicitement le fichier complet
638 # ------------------------------------------
640 for directory in sys.path:
641 if os.path.isfile(os.path.join(directory, daDirectory, str(choice)+'.py')):
642 module_path = os.path.abspath(os.path.join(directory, daDirectory))
643 if module_path is None:
644 raise ImportError("No diagnostic module named \"%s\" was found in a \"%s\" subdirectory\n The search path is %s"%(choice, daDirectory, sys.path))
646 # Importe le fichier complet comme un module
647 # ------------------------------------------
649 sys_path_tmp = sys.path ; sys.path.insert(0,module_path)
650 self.__diagnosticFile = __import__(str(choice), globals(), locals(), [])
651 sys.path = sys_path_tmp ; del sys_path_tmp
652 except ImportError, e:
653 raise ImportError("The module named \"%s\" was found, but is incorrect at the import stage.\n The import error message is: %s"%(choice,e))
655 # Instancie un objet du type élémentaire du fichier
656 # -------------------------------------------------
657 if self.__StoredInputs.has_key(name):
658 raise ValueError("A default input with the same name \"%s\" already exists."%str(name))
659 elif self.__StoredDiagnostics.has_key(name):
660 raise ValueError("A diagnostic with the same name \"%s\" already exists."%str(name))
662 self.__StoredDiagnostics[name] = self.__diagnosticFile.ElementaryDiagnostic(
666 parameters = parameters )
669 # -----------------------------------------------------------
670 def shape_validate(self):
672 Validation de la correspondance correcte des tailles des variables et
673 des matrices s'il y en a.
675 if self.__Xb is None: __Xb_shape = (0,)
676 elif hasattr(self.__Xb,"size"): __Xb_shape = (self.__Xb.size,)
677 elif hasattr(self.__Xb,"shape"):
678 if type(self.__Xb.shape) is tuple: __Xb_shape = self.__Xb.shape
679 else: __Xb_shape = self.__Xb.shape()
680 else: raise TypeError("The background (Xb) has no attribute of shape: problem !")
682 if self.__Y is None: __Y_shape = (0,)
683 elif hasattr(self.__Y,"size"): __Y_shape = (self.__Y.size,)
684 elif hasattr(self.__Y,"shape"):
685 if type(self.__Y.shape) is tuple: __Y_shape = self.__Y.shape
686 else: __Y_shape = self.__Y.shape()
687 else: raise TypeError("The observation (Y) has no attribute of shape: problem !")
689 if self.__U is None: __U_shape = (0,)
690 elif hasattr(self.__U,"size"): __U_shape = (self.__U.size,)
691 elif hasattr(self.__U,"shape"):
692 if type(self.__U.shape) is tuple: __U_shape = self.__U.shape
693 else: __U_shape = self.__U.shape()
694 else: raise TypeError("The control (U) has no attribute of shape: problem !")
696 if self.__B is None: __B_shape = (0,0)
697 elif hasattr(self.__B,"shape"):
698 if type(self.__B.shape) is tuple: __B_shape = self.__B.shape
699 else: __B_shape = self.__B.shape()
700 else: raise TypeError("The a priori errors covariance matrix (B) has no attribute of shape: problem !")
702 if self.__R is None: __R_shape = (0,0)
703 elif hasattr(self.__R,"shape"):
704 if type(self.__R.shape) is tuple: __R_shape = self.__R.shape
705 else: __R_shape = self.__R.shape()
706 else: raise TypeError("The observation errors covariance matrix (R) has no attribute of shape: problem !")
708 if self.__Q is None: __Q_shape = (0,0)
709 elif hasattr(self.__Q,"shape"):
710 if type(self.__Q.shape) is tuple: __Q_shape = self.__Q.shape
711 else: __Q_shape = self.__Q.shape()
712 else: raise TypeError("The evolution errors covariance matrix (Q) has no attribute of shape: problem !")
714 if len(self.__HO) == 0: __HO_shape = (0,0)
715 elif type(self.__HO) is type({}): __HO_shape = (0,0)
716 elif hasattr(self.__HO["Direct"],"shape"):
717 if type(self.__HO["Direct"].shape) is tuple: __HO_shape = self.__HO["Direct"].shape
718 else: __HO_shape = self.__HO["Direct"].shape()
719 else: raise TypeError("The observation operator (H) has no attribute of shape: problem !")
721 if len(self.__EM) == 0: __EM_shape = (0,0)
722 elif type(self.__EM) is type({}): __EM_shape = (0,0)
723 elif hasattr(self.__EM["Direct"],"shape"):
724 if type(self.__EM["Direct"].shape) is tuple: __EM_shape = self.__EM["Direct"].shape
725 else: __EM_shape = self.__EM["Direct"].shape()
726 else: raise TypeError("The evolution model (EM) has no attribute of shape: problem !")
728 if len(self.__CM) == 0: __CM_shape = (0,0)
729 elif type(self.__CM) is type({}): __CM_shape = (0,0)
730 elif hasattr(self.__CM["Direct"],"shape"):
731 if type(self.__CM["Direct"].shape) is tuple: __CM_shape = self.__CM["Direct"].shape
732 else: __CM_shape = self.__CM["Direct"].shape()
733 else: raise TypeError("The control model (CM) has no attribute of shape: problem !")
735 # Vérification des conditions
736 # ---------------------------
737 if not( len(__Xb_shape) == 1 or min(__Xb_shape) == 1 ):
738 raise ValueError("Shape characteristic of background (Xb) is incorrect: \"%s\"."%(__Xb_shape,))
739 if not( len(__Y_shape) == 1 or min(__Y_shape) == 1 ):
740 raise ValueError("Shape characteristic of observation (Y) is incorrect: \"%s\"."%(__Y_shape,))
742 if not( min(__B_shape) == max(__B_shape) ):
743 raise ValueError("Shape characteristic of a priori errors covariance matrix (B) is incorrect: \"%s\"."%(__B_shape,))
744 if not( min(__R_shape) == max(__R_shape) ):
745 raise ValueError("Shape characteristic of observation errors covariance matrix (R) is incorrect: \"%s\"."%(__R_shape,))
746 if not( min(__Q_shape) == max(__Q_shape) ):
747 raise ValueError("Shape characteristic of evolution errors covariance matrix (Q) is incorrect: \"%s\"."%(__Q_shape,))
748 if not( min(__EM_shape) == max(__EM_shape) ):
749 raise ValueError("Shape characteristic of evolution operator (EM) is incorrect: \"%s\"."%(__EM_shape,))
751 if len(self.__HO) > 0 and not(type(self.__HO) is type({})) and not( __HO_shape[1] == max(__Xb_shape) ):
752 raise ValueError("Shape characteristic of observation operator (H) \"%s\" and state (X) \"%s\" are incompatible."%(__HO_shape,__Xb_shape))
753 if len(self.__HO) > 0 and not(type(self.__HO) is type({})) and not( __HO_shape[0] == max(__Y_shape) ):
754 raise ValueError("Shape characteristic of observation operator (H) \"%s\" and observation (Y) \"%s\" are incompatible."%(__HO_shape,__Y_shape))
755 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] ):
756 raise ValueError("Shape characteristic of observation operator (H) \"%s\" and a priori errors covariance matrix (B) \"%s\" are incompatible."%(__HO_shape,__B_shape))
757 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] ):
758 raise ValueError("Shape characteristic of observation operator (H) \"%s\" and observation errors covariance matrix (R) \"%s\" are incompatible."%(__HO_shape,__R_shape))
760 if self.__B is not None and len(self.__B) > 0 and not( __B_shape[1] == max(__Xb_shape) ):
761 if self.__StoredInputs["AlgorithmName"] in ["EnsembleBlue",]:
762 asPersistentVector = self.__Xb.reshape((-1,min(__B_shape)))
763 self.__Xb = Persistence.OneVector("Background", basetype=numpy.matrix)
764 for member in asPersistentVector:
765 self.__Xb.store( numpy.matrix( numpy.ravel(member), numpy.float ).T )
766 __Xb_shape = min(__B_shape)
768 raise ValueError("Shape characteristic of a priori errors covariance matrix (B) \"%s\" and background (Xb) \"%s\" are incompatible."%(__B_shape,__Xb_shape))
770 if self.__R is not None and len(self.__R) > 0 and not( __R_shape[1] == max(__Y_shape) ):
771 raise ValueError("Shape characteristic of observation errors covariance matrix (R) \"%s\" and observation (Y) \"%s\" are incompatible."%(__R_shape,__Y_shape))
773 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) ):
774 raise ValueError("Shape characteristic of evolution model (EM) \"%s\" and state (X) \"%s\" are incompatible."%(__EM_shape,__Xb_shape))
776 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) ):
777 raise ValueError("Shape characteristic of control model (CM) \"%s\" and control (U) \"%s\" are incompatible."%(__CM_shape,__U_shape))
779 if self.__StoredInputs.has_key("AlgorithmParameters") \
780 and self.__StoredInputs["AlgorithmParameters"].has_key("Bounds") \
781 and (type(self.__StoredInputs["AlgorithmParameters"]["Bounds"]) is type([]) or type(self._parameters["Bounds"]) is type(())) \
782 and (len(self.__StoredInputs["AlgorithmParameters"]["Bounds"]) != max(__Xb_shape)):
783 raise ValueError("The number \"%s\" of bound pairs for the state (X) components is different of the size \"%s\" of the state itself."%(len(self.__StoredInputs["AlgorithmParameters"]["Bounds"]),max(__Xb_shape)))
787 # -----------------------------------------------------------
790 Permet de lancer le calcul d'assimilation.
792 Le nom de la méthode à activer est toujours "run". Les paramètres en
793 arguments de la méthode sont fixés. En sortie, on obtient les résultats
794 dans la variable de type dictionnaire "StoredVariables", qui contient en
795 particulier des objets de Persistence pour les analyses, OMA...
797 Operator.CM.clearCache()
798 self.shape_validate()
800 self.__algorithm.run(
810 Parameters = self.__Parameters,
814 # -----------------------------------------------------------
815 def get(self, key=None):
817 Renvoie les résultats disponibles après l'exécution de la méthode
818 d'assimilation, ou les diagnostics disponibles. Attention, quand un
819 diagnostic porte le même nom qu'une variable stockée, c'est la variable
820 stockée qui est renvoyée, et le diagnostic est inatteignable.
823 if self.__algorithm.has_key(key):
824 return self.__algorithm.get( key )
825 elif self.__StoredInputs.has_key(key):
826 return self.__StoredInputs[key]
827 elif self.__StoredDiagnostics.has_key(key):
828 return self.__StoredDiagnostics[key]
830 raise ValueError("The requested key \"%s\" does not exists as an input, a diagnostic or a stored variable."%key)
832 allvariables = self.__algorithm.get()
833 allvariables.update( self.__StoredDiagnostics )
834 allvariables.update( self.__StoredInputs )
837 def get_available_variables(self):
839 Renvoie les variables potentiellement utilisables pour l'étude,
840 initialement stockées comme données d'entrées ou dans les algorithmes,
841 identifiés par les chaînes de caractères. L'algorithme doit avoir été
842 préalablement choisi sinon la méthode renvoie "None".
844 if len( self.__algorithm.keys()) == 0 and len( self.__StoredInputs.keys() ) == 0:
848 if len( self.__algorithm.keys()) > 0:
849 variables.extend( self.__algorithm.get().keys() )
850 if len( self.__StoredInputs.keys() ) > 0:
851 variables.extend( self.__StoredInputs.keys() )
855 def get_available_algorithms(self):
857 Renvoie la liste des algorithmes potentiellement utilisables, identifiés
858 par les chaînes de caractères.
861 for directory in sys.path:
862 if os.path.isdir(os.path.join(directory,"daAlgorithms")):
863 for fname in os.listdir(os.path.join(directory,"daAlgorithms")):
864 root, ext = os.path.splitext(fname)
865 if ext == '.py' and root != '__init__':
870 def get_available_diagnostics(self):
872 Renvoie la liste des diagnostics potentiellement utilisables, identifiés
873 par les chaînes de caractères.
876 for directory in sys.path:
877 if os.path.isdir(os.path.join(directory,"daDiagnostics")):
878 for fname in os.listdir(os.path.join(directory,"daDiagnostics")):
879 root, ext = os.path.splitext(fname)
880 if ext == '.py' and root != '__init__':
885 # -----------------------------------------------------------
886 def get_algorithms_main_path(self):
888 Renvoie le chemin pour le répertoire principal contenant les algorithmes
889 dans un sous-répertoire "daAlgorithms"
893 def add_algorithms_path(self, asPath=None):
895 Ajoute au chemin de recherche des algorithmes un répertoire dans lequel
896 se trouve un sous-répertoire "daAlgorithms"
898 Remarque : si le chemin a déjà été ajouté pour les diagnostics, il n'est
899 pas indispensable de le rajouter ici.
901 if not os.path.isdir(asPath):
902 raise ValueError("The given "+asPath+" argument must exist as a directory")
903 if not os.path.isdir(os.path.join(asPath,"daAlgorithms")):
904 raise ValueError("The given \""+asPath+"\" argument must contain a subdirectory named \"daAlgorithms\"")
905 if not os.path.isfile(os.path.join(asPath,"daAlgorithms","__init__.py")):
906 raise ValueError("The given \""+asPath+"/daAlgorithms\" path must contain a file named \"__init__.py\"")
907 sys.path.insert(0, os.path.abspath(asPath))
908 sys.path = uniq( sys.path ) # Conserve en unique exemplaire chaque chemin
911 def get_diagnostics_main_path(self):
913 Renvoie le chemin pour le répertoire principal contenant les diagnostics
914 dans un sous-répertoire "daDiagnostics"
918 def add_diagnostics_path(self, asPath=None):
920 Ajoute au chemin de recherche des algorithmes un répertoire dans lequel
921 se trouve un sous-répertoire "daDiagnostics"
923 Remarque : si le chemin a déjà été ajouté pour les algorithmes, il n'est
924 pas indispensable de le rajouter ici.
926 if not os.path.isdir(asPath):
927 raise ValueError("The given "+asPath+" argument must exist as a directory")
928 if not os.path.isdir(os.path.join(asPath,"daDiagnostics")):
929 raise ValueError("The given \""+asPath+"\" argument must contain a subdirectory named \"daDiagnostics\"")
930 if not os.path.isfile(os.path.join(asPath,"daDiagnostics","__init__.py")):
931 raise ValueError("The given \""+asPath+"/daDiagnostics\" path must contain a file named \"__init__.py\"")
932 sys.path.insert(0, os.path.abspath(asPath))
933 sys.path = uniq( sys.path ) # Conserve en unique exemplaire chaque chemin
936 # -----------------------------------------------------------
937 def setDataObserver(self,
940 HookParameters = None,
944 Permet d'associer un observer à une ou des variables nommées gérées en
945 interne, activable selon des règles définies dans le Scheduler. A chaque
946 pas demandé dans le Scheduler, il effectue la fonction HookFunction avec
947 les arguments (variable persistante VariableName, paramètres HookParameters).
950 if type( self.__algorithm ) is dict:
951 raise ValueError("No observer can be build before choosing an algorithm.")
953 # Vérification du nom de variable et typage
954 # -----------------------------------------
955 if type( VariableName ) is str:
956 VariableNames = [VariableName,]
957 elif type( VariableName ) is list:
958 VariableNames = map( str, VariableName )
960 raise ValueError("The observer requires a name or a list of names of variables.")
962 # Association interne de l'observer à la variable
963 # -----------------------------------------------
964 for n in VariableNames:
965 if not self.__algorithm.has_key( n ):
966 raise ValueError("An observer requires to be set on a variable named %s which does not exist."%n)
968 self.__algorithm.StoredVariables[ n ].setDataObserver(
969 Scheduler = Scheduler,
970 HookFunction = HookFunction,
971 HookParameters = HookParameters,
974 def removeDataObserver(self,
979 Permet de retirer un observer à une ou des variable nommée.
982 if type( self.__algorithm ) is dict:
983 raise ValueError("No observer can be removed before choosing an algorithm.")
985 # Vérification du nom de variable et typage
986 # -----------------------------------------
987 if type( VariableName ) is str:
988 VariableNames = [VariableName,]
989 elif type( VariableName ) is list:
990 VariableNames = map( str, VariableName )
992 raise ValueError("The observer requires a name or a list of names of variables.")
994 # Association interne de l'observer à la variable
995 # -----------------------------------------------
996 for n in VariableNames:
997 if not self.__algorithm.has_key( n ):
998 raise ValueError("An observer requires to be removed on a variable named %s which does not exist."%n)
1000 self.__algorithm.StoredVariables[ n ].removeDataObserver(
1001 HookFunction = HookFunction,
1004 # -----------------------------------------------------------
1005 def setDebug(self, level=10):
1007 Utiliser par exemple "import logging ; level = logging.DEBUG" avant cet
1008 appel pour changer le niveau de verbosité, avec :
1009 NOTSET=0 < DEBUG=10 < INFO=20 < WARNING=30 < ERROR=40 < CRITICAL=50
1012 log = logging.getLogger()
1013 log.setLevel( level )
1015 def unsetDebug(self):
1017 Remet le logger au niveau par défaut
1020 log = logging.getLogger()
1021 log.setLevel( logging.WARNING )
1023 def prepare_to_pickle(self):
1024 self.__algorithmFile = None
1025 self.__diagnosticFile = None
1030 # ==============================================================================
1031 if __name__ == "__main__":
1032 print '\n AUTODIAGNOSTIC \n'