1 #-*-coding:iso-8859-1-*-
3 # Copyright (C) 2008-2015 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
38 logging.debug("Succeed initial import of scipy.optimize with Scipy %s", scipy.version.version)
40 logging.debug("Fail initial import of scipy.optimize")
42 from BasicObjects import Operator, Covariance
43 from PlatformInfo import uniq
45 # ==============================================================================
46 class AssimilationStudy:
48 Cette classe sert d'interface pour l'utilisation de l'assimilation de
49 données. Elle contient les méthodes ou accesseurs nécessaires à la
50 construction d'un calcul d'assimilation.
52 def __init__(self, name=""):
54 Prévoit de conserver l'ensemble des variables nécssaires à un algorithme
55 élémentaire. Ces variables sont ensuite disponibles pour implémenter un
56 algorithme élémentaire particulier.
58 - Background : vecteur Xb
59 - Observation : vecteur Y (potentiellement temporel) d'observations
60 - State : vecteur d'état dont une partie est le vecteur de contrôle.
61 Cette information n'est utile que si l'on veut faire des calculs sur
62 l'état complet, mais elle n'est pas indispensable pour l'assimilation.
63 - Control : vecteur X contenant toutes les variables de contrôle, i.e.
64 les paramètres ou l'état dont on veut estimer la valeur pour obtenir
66 - ObservationOperator...: opérateur d'observation H
68 Les observations présentent une erreur dont la matrice de covariance est
69 R. L'ébauche du vecteur de contrôle présente une erreur dont la matrice
72 self.__name = str(name)
83 self.__X = Persistence.OneVector()
84 self.__Parameters = {}
85 self.__StoredDiagnostics = {}
86 self.__StoredInputs = {}
88 # Variables temporaires
90 self.__algorithmFile = None
91 self.__algorithmName = None
92 self.__diagnosticFile = None
94 # Récupère le chemin du répertoire parent et l'ajoute au path
95 # (Cela complète l'action de la classe PathManagement dans PlatformInfo,
96 # qui est activée dans Persistence)
97 self.__parent = os.path.abspath(os.path.join(os.path.dirname(__file__),".."))
98 sys.path.insert(0, self.__parent)
99 sys.path = uniq( sys.path ) # Conserve en unique exemplaire chaque chemin
101 # ---------------------------------------------------------
102 def setBackground(self,
104 asPersistentVector = None,
109 Permet de définir l'estimation a priori :
110 - asVector : entrée des données, comme un vecteur compatible avec le
111 constructeur de numpy.matrix
112 - asPersistentVector : entrée des données, comme un vecteur de type
113 persistent contruit avec la classe ad-hoc "Persistence"
114 - Scheduler est le contrôle temporel des données
115 - toBeStored : booléen indiquant si la donnée d'entrée est sauvée pour
116 être rendue disponible au même titre que les variables de calcul
118 if asVector is not None:
119 if type( asVector ) is type( numpy.matrix([]) ):
120 self.__Xb = numpy.matrix( asVector.A1, numpy.float ).T
122 self.__Xb = numpy.matrix( asVector, numpy.float ).T
123 elif asPersistentVector is not None:
124 if type(asPersistentVector) in [type([]),type(()),type(numpy.array([])),type(numpy.matrix([]))]:
125 self.__Xb = Persistence.OneVector("Background", basetype=numpy.matrix)
126 for member in asPersistentVector:
127 self.__Xb.store( numpy.matrix( numpy.asmatrix(member).A1, numpy.float ).T )
129 self.__Xb = asPersistentVector
131 raise ValueError("Error: improperly defined background, it requires at minima either a vector, a list/tuple of vectors or a persistent object")
133 self.__StoredInputs["Background"] = self.__Xb
136 def setBackgroundError(self,
138 asEyeByScalar = None,
139 asEyeByVector = None,
144 Permet de définir la covariance des erreurs d'ébauche :
145 - asCovariance : entrée des données, comme une matrice compatible avec
146 le constructeur de numpy.matrix
147 - asEyeByScalar : entrée des données comme un seul scalaire de variance,
148 multiplicatif d'une matrice de corrélation identité, aucune matrice
149 n'étant donc explicitement à donner
150 - asEyeByVector : entrée des données comme un seul vecteur de variance,
151 à mettre sur la diagonale d'une matrice de corrélation, aucune matrice
152 n'étant donc explicitement à donner
153 - asCovObject : entrée des données comme un objet ayant des méthodes
154 particulieres de type matriciel
155 - toBeStored : booléen indiquant si la donnée d'entrée est sauvée pour
156 être rendue disponible au même titre que les variables de calcul
158 self.__B = Covariance(
159 name = "BackgroundError",
160 asCovariance = asCovariance,
161 asEyeByScalar = asEyeByScalar,
162 asEyeByVector = asEyeByVector,
163 asCovObject = asCovObject,
166 self.__StoredInputs["BackgroundError"] = self.__B
169 # -----------------------------------------------------------
170 def setObservation(self,
172 asPersistentVector = None,
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
186 if asVector is not None:
187 if type( asVector ) is type( numpy.matrix([]) ):
188 self.__Y = numpy.matrix( asVector.A1, numpy.float ).T
190 self.__Y = numpy.matrix( asVector, numpy.float ).T
191 elif asPersistentVector is not None:
192 if type(asPersistentVector) in [type([]),type(()),type(numpy.array([])),type(numpy.matrix([]))]:
193 self.__Y = Persistence.OneVector("Observation", basetype=numpy.matrix)
194 for member in asPersistentVector:
195 self.__Y.store( numpy.matrix( numpy.asmatrix(member).A1, numpy.float ).T )
197 self.__Y = asPersistentVector
199 raise ValueError("Error: improperly defined observations, it requires at minima either a vector, a list/tuple of vectors or a persistent object")
201 self.__StoredInputs["Observation"] = self.__Y
204 def setObservationError(self,
206 asEyeByScalar = None,
207 asEyeByVector = None,
212 Permet de définir la covariance des erreurs d'observations :
213 - asCovariance : entrée des données, comme une matrice compatible avec
214 le constructeur de numpy.matrix
215 - asEyeByScalar : entrée des données comme un seul scalaire de variance,
216 multiplicatif d'une matrice de corrélation identité, aucune matrice
217 n'étant donc explicitement à donner
218 - asEyeByVector : entrée des données comme un seul vecteur de variance,
219 à mettre sur la diagonale d'une matrice de corrélation, aucune matrice
220 n'étant donc explicitement à donner
221 - asCovObject : entrée des données comme un objet ayant des méthodes
222 particulieres de type matriciel
223 - toBeStored : booléen indiquant si la donnée d'entrée est sauvée pour
224 être rendue disponible au même titre que les variables de calcul
226 self.__R = Covariance(
227 name = "ObservationError",
228 asCovariance = asCovariance,
229 asEyeByScalar = asEyeByScalar,
230 asEyeByVector = asEyeByVector,
231 asCovObject = asCovObject,
234 self.__StoredInputs["ObservationError"] = self.__R
237 def setObservationOperator(self,
245 Permet de définir un opérateur d'observation H. L'ordre de priorité des
246 définitions et leur sens sont les suivants :
247 - si asFunction["Tangent"] et asFunction["Adjoint"] ne sont pas None
248 alors on définit l'opérateur à l'aide de fonctions. Si la fonction
249 "Direct" n'est pas définie, on prend la fonction "Tangent".
250 Si "useApproximatedDerivatives" est vrai, on utilise une approximation
251 des opérateurs tangents et adjoints. On utilise par défaut des
252 différences finies non centrées ou centrées (si "withCenteredDF" est
253 vrai) avec un incrément multiplicatif "withIncrement" de 1% autour
254 du point courant ou sur le point fixe "withdX".
255 - si les fonctions ne sont pas disponibles et si asMatrix n'est pas
256 None, alors on définit l'opérateur "Direct" et "Tangent" à l'aide de
257 la matrice, et l'opérateur "Adjoint" à l'aide de la transposée. La
258 matrice fournie doit être sous une forme compatible avec le
259 constructeur de numpy.matrix.
260 - si l'argument "appliedToX" n'est pas None, alors on définit, pour des
261 X divers, l'opérateur par sa valeur appliquée à cet X particulier,
262 sous la forme d'un dictionnaire appliedToX[NAME] avec NAME un nom.
263 L'opérateur doit néanmoins déjà avoir été défini comme d'habitude.
264 - toBeStored : booléen indiquant si la donnée d'entrée est sauvée pour
265 être rendue disponible au même titre que les variables de calcul
266 L'argument "asFunction" peut prendre la forme complète suivante, avec
267 les valeurs par défaut standards :
268 asFunction = {"Direct":None, "Tangent":None, "Adjoint":None,
269 "useApproximatedDerivatives":False,
270 "withCenteredDF" :False,
271 "withIncrement" :0.01,
273 "withAvoidingRedundancy" :True,
274 "withToleranceInRedundancy" :1.e-18,
275 "withLenghtOfRedundancy" :-1,
276 "withmpEnabled" :False,
277 "withmpWorkers" :None,
280 if (type(asFunction) is type({})) and \
281 asFunction.has_key("useApproximatedDerivatives") and bool(asFunction["useApproximatedDerivatives"]) and \
282 asFunction.has_key("Direct") and (asFunction["Direct"] is not None):
283 if not asFunction.has_key("withCenteredDF"): asFunction["withCenteredDF"] = False
284 if not asFunction.has_key("withIncrement"): asFunction["withIncrement"] = 0.01
285 if not asFunction.has_key("withdX"): asFunction["withdX"] = None
286 if not asFunction.has_key("withAvoidingRedundancy"): asFunction["withAvoidingRedundancy"] = True
287 if not asFunction.has_key("withToleranceInRedundancy"): asFunction["withToleranceInRedundancy"] = 1.e-18
288 if not asFunction.has_key("withLenghtOfRedundancy"): asFunction["withLenghtOfRedundancy"] = -1
289 if not asFunction.has_key("withmpEnabled"): asFunction["withmpEnabled"] = False
290 if not asFunction.has_key("withmpWorkers"): asFunction["withmpWorkers"] = None
291 from daNumerics.ApproximatedDerivatives import FDApproximation
292 FDA = FDApproximation(
293 Function = asFunction["Direct"],
294 centeredDF = asFunction["withCenteredDF"],
295 increment = asFunction["withIncrement"],
296 dX = asFunction["withdX"],
297 avoidingRedundancy = asFunction["withAvoidingRedundancy"],
298 toleranceInRedundancy = asFunction["withToleranceInRedundancy"],
299 lenghtOfRedundancy = asFunction["withLenghtOfRedundancy"],
300 mpEnabled = asFunction["withmpEnabled"],
301 mpWorkers = asFunction["withmpWorkers"],
303 self.__HO["Direct"] = Operator( fromMethod = FDA.DirectOperator, avoidingRedundancy = avoidRC )
304 self.__HO["Tangent"] = Operator( fromMethod = FDA.TangentOperator, avoidingRedundancy = avoidRC )
305 self.__HO["Adjoint"] = Operator( fromMethod = FDA.AdjointOperator, avoidingRedundancy = avoidRC )
306 elif (type(asFunction) is type({})) and \
307 asFunction.has_key("Tangent") and asFunction.has_key("Adjoint") and \
308 (asFunction["Tangent"] is not None) and (asFunction["Adjoint"] is not None):
309 if not asFunction.has_key("Direct") or (asFunction["Direct"] is None):
310 self.__HO["Direct"] = Operator( fromMethod = asFunction["Tangent"], avoidingRedundancy = avoidRC )
312 self.__HO["Direct"] = Operator( fromMethod = asFunction["Direct"], avoidingRedundancy = avoidRC )
313 self.__HO["Tangent"] = Operator( fromMethod = asFunction["Tangent"], avoidingRedundancy = avoidRC )
314 self.__HO["Adjoint"] = Operator( fromMethod = asFunction["Adjoint"], avoidingRedundancy = avoidRC )
315 elif asMatrix is not None:
316 matrice = numpy.matrix( asMatrix, numpy.float )
317 self.__HO["Direct"] = Operator( fromMatrix = matrice, avoidingRedundancy = avoidRC )
318 self.__HO["Tangent"] = Operator( fromMatrix = matrice, avoidingRedundancy = avoidRC )
319 self.__HO["Adjoint"] = Operator( fromMatrix = matrice.T, avoidingRedundancy = avoidRC )
322 raise ValueError("Improperly defined observation operator, it requires at minima either a matrix, a Direct for approximate derivatives or a Tangent/Adjoint pair.")
324 if appliedToX is not None:
325 self.__HO["AppliedToX"] = {}
326 if type(appliedToX) is not dict:
327 raise ValueError("Error: observation operator defined by \"appliedToX\" need a dictionary as argument.")
328 for key in appliedToX.keys():
329 if type( appliedToX[key] ) is type( numpy.matrix([]) ):
330 # Pour le cas où l'on a une vraie matrice
331 self.__HO["AppliedToX"][key] = numpy.matrix( appliedToX[key].A1, numpy.float ).T
332 elif type( appliedToX[key] ) is type( numpy.array([]) ) and len(appliedToX[key].shape) > 1:
333 # Pour le cas où l'on a un vecteur représenté en array avec 2 dimensions
334 self.__HO["AppliedToX"][key] = numpy.matrix( appliedToX[key].reshape(len(appliedToX[key]),), numpy.float ).T
336 self.__HO["AppliedToX"][key] = numpy.matrix( appliedToX[key], numpy.float ).T
338 self.__HO["AppliedToX"] = None
341 self.__StoredInputs["ObservationOperator"] = self.__HO
344 # -----------------------------------------------------------
345 def setEvolutionModel(self,
353 Permet de définir un opérateur d'évolution M. L'ordre de priorité des
354 définitions et leur sens sont les suivants :
355 - si asFunction["Tangent"] et asFunction["Adjoint"] ne sont pas None
356 alors on définit l'opérateur à l'aide de fonctions. Si la fonction
357 "Direct" n'est pas définie, on prend la fonction "Tangent".
358 Si "useApproximatedDerivatives" est vrai, on utilise une approximation
359 des opérateurs tangents et adjoints. On utilise par défaut des
360 différences finies non centrées ou centrées (si "withCenteredDF" est
361 vrai) avec un incrément multiplicatif "withIncrement" de 1% autour
362 du point courant ou sur le point fixe "withdX".
363 - si les fonctions ne sont pas disponibles et si asMatrix n'est pas
364 None, alors on définit l'opérateur "Direct" et "Tangent" à l'aide de
365 la matrice, et l'opérateur "Adjoint" à l'aide de la transposée. La
366 matrice fournie doit être sous une forme compatible avec le
367 constructeur de numpy.matrix.
368 - toBeStored : booléen indiquant si la donnée d'entrée est sauvée pour
369 être rendue disponible au même titre que les variables de calcul
370 L'argument "asFunction" peut prendre la forme complète suivante, avec
371 les valeurs par défaut standards :
372 asFunction = {"Direct":None, "Tangent":None, "Adjoint":None,
373 "useApproximatedDerivatives":False,
374 "withCenteredDF" :False,
375 "withIncrement" :0.01,
377 "withAvoidingRedundancy" :True,
378 "withToleranceInRedundancy" :1.e-18,
379 "withLenghtOfRedundancy" :-1,
380 "withmpEnabled" :False,
381 "withmpWorkers" :None,
384 if (type(asFunction) is type({})) and \
385 asFunction.has_key("useApproximatedDerivatives") and bool(asFunction["useApproximatedDerivatives"]) and \
386 asFunction.has_key("Direct") and (asFunction["Direct"] is not None):
387 if not asFunction.has_key("withCenteredDF"): asFunction["withCenteredDF"] = False
388 if not asFunction.has_key("withIncrement"): asFunction["withIncrement"] = 0.01
389 if not asFunction.has_key("withdX"): asFunction["withdX"] = None
390 if not asFunction.has_key("withAvoidingRedundancy"): asFunction["withAvoidingRedundancy"] = True
391 if not asFunction.has_key("withToleranceInRedundancy"): asFunction["withToleranceInRedundancy"] = 1.e-18
392 if not asFunction.has_key("withLenghtOfRedundancy"): asFunction["withLenghtOfRedundancy"] = -1
393 if not asFunction.has_key("withmpEnabled"): asFunction["withmpEnabled"] = False
394 if not asFunction.has_key("withmpWorkers"): asFunction["withmpWorkers"] = None
395 from daNumerics.ApproximatedDerivatives import FDApproximation
396 FDA = FDApproximation(
397 Function = asFunction["Direct"],
398 centeredDF = asFunction["withCenteredDF"],
399 increment = asFunction["withIncrement"],
400 dX = asFunction["withdX"],
401 avoidingRedundancy = asFunction["withAvoidingRedundancy"],
402 toleranceInRedundancy = asFunction["withToleranceInRedundancy"],
403 lenghtOfRedundancy = asFunction["withLenghtOfRedundancy"],
404 mpEnabled = asFunction["withmpEnabled"],
405 mpWorkers = asFunction["withmpWorkers"],
407 self.__EM["Direct"] = Operator( fromMethod = FDA.DirectOperator, avoidingRedundancy = avoidRC )
408 self.__EM["Tangent"] = Operator( fromMethod = FDA.TangentOperator, avoidingRedundancy = avoidRC )
409 self.__EM["Adjoint"] = Operator( fromMethod = FDA.AdjointOperator, avoidingRedundancy = avoidRC )
410 elif (type(asFunction) is type({})) and \
411 asFunction.has_key("Tangent") and asFunction.has_key("Adjoint") and \
412 (asFunction["Tangent"] is not None) and (asFunction["Adjoint"] is not None):
413 if not asFunction.has_key("Direct") or (asFunction["Direct"] is None):
414 self.__EM["Direct"] = Operator( fromMethod = asFunction["Tangent"], avoidingRedundancy = avoidRC )
416 self.__EM["Direct"] = Operator( fromMethod = asFunction["Direct"], avoidingRedundancy = avoidRC )
417 self.__EM["Tangent"] = Operator( fromMethod = asFunction["Tangent"], avoidingRedundancy = avoidRC )
418 self.__EM["Adjoint"] = Operator( fromMethod = asFunction["Adjoint"], avoidingRedundancy = avoidRC )
419 elif asMatrix is not None:
420 matrice = numpy.matrix( asMatrix, numpy.float )
421 self.__EM["Direct"] = Operator( fromMatrix = matrice, avoidingRedundancy = avoidRC )
422 self.__EM["Tangent"] = Operator( fromMatrix = matrice, avoidingRedundancy = avoidRC )
423 self.__EM["Adjoint"] = Operator( fromMatrix = matrice.T, avoidingRedundancy = avoidRC )
426 raise ValueError("Improperly defined evolution model, it requires at minima either a matrix, a Direct for approximate derivatives or a Tangent/Adjoint pair.")
429 self.__StoredInputs["EvolutionModel"] = self.__EM
432 def setEvolutionError(self,
434 asEyeByScalar = None,
435 asEyeByVector = None,
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
454 self.__Q = Covariance(
455 name = "EvolutionError",
456 asCovariance = asCovariance,
457 asEyeByScalar = asEyeByScalar,
458 asEyeByVector = asEyeByVector,
459 asCovObject = asCovObject,
462 self.__StoredInputs["EvolutionError"] = self.__Q
465 # -----------------------------------------------------------
466 def setControlModel(self,
467 asFunction = {"Direct":None, "Tangent":None, "Adjoint":None,
468 "useApproximatedDerivatives":False,
469 "withCenteredDF" :False,
470 "withIncrement" :0.01,
479 Permet de définir un opérateur de controle C. L'ordre de priorité des
480 définitions et leur sens sont les suivants :
481 - si asFunction["Tangent"] et asFunction["Adjoint"] ne sont pas None
482 alors on définit l'opérateur à l'aide de fonctions. Si la fonction
483 "Direct" n'est pas définie, on prend la fonction "Tangent".
484 Si "useApproximatedDerivatives" est vrai, on utilise une approximation
485 des opérateurs tangents et adjoints. On utilise par défaut des
486 différences finies non centrées ou centrées (si "withCenteredDF" est
487 vrai) avec un incrément multiplicatif "withIncrement" de 1% autour
488 du point courant ou sur le point fixe "withdX".
489 - si les fonctions ne sont pas disponibles et si asMatrix n'est pas
490 None, alors on définit l'opérateur "Direct" et "Tangent" à l'aide de
491 la matrice, et l'opérateur "Adjoint" à l'aide de la transposée. La
492 matrice fournie doit être sous une forme compatible avec le
493 constructeur de numpy.matrix.
494 - toBeStored : booléen indiquant si la donnée d'entrée est sauvée pour
495 être rendue disponible au même titre que les variables de calcul
497 if (type(asFunction) is type({})) and \
498 asFunction.has_key("useApproximatedDerivatives") and bool(asFunction["useApproximatedDerivatives"]) and \
499 asFunction.has_key("Direct") and (asFunction["Direct"] is not None):
500 if not asFunction.has_key("withCenteredDF"): asFunction["withCenteredDF"] = False
501 if not asFunction.has_key("withIncrement"): asFunction["withIncrement"] = 0.01
502 if not asFunction.has_key("withdX"): asFunction["withdX"] = None
503 from daNumerics.ApproximatedDerivatives import FDApproximation
504 FDA = FDApproximation(
505 Function = asFunction["Direct"],
506 centeredDF = asFunction["withCenteredDF"],
507 increment = asFunction["withIncrement"],
508 dX = asFunction["withdX"] )
509 self.__CM["Direct"] = Operator( fromMethod = FDA.DirectOperator, avoidingRedundancy = avoidRC )
510 self.__CM["Tangent"] = Operator( fromMethod = FDA.TangentOperator, avoidingRedundancy = avoidRC )
511 self.__CM["Adjoint"] = Operator( fromMethod = FDA.AdjointOperator, avoidingRedundancy = avoidRC )
512 elif (type(asFunction) is type({})) and \
513 asFunction.has_key("Tangent") and asFunction.has_key("Adjoint") and \
514 (asFunction["Tangent"] is not None) and (asFunction["Adjoint"] is not None):
515 if not asFunction.has_key("Direct") or (asFunction["Direct"] is None):
516 self.__CM["Direct"] = Operator( fromMethod = asFunction["Tangent"], avoidingRedundancy = avoidRC )
518 self.__CM["Direct"] = Operator( fromMethod = asFunction["Direct"], avoidingRedundancy = avoidRC )
519 self.__CM["Tangent"] = Operator( fromMethod = asFunction["Tangent"], avoidingRedundancy = avoidRC )
520 self.__CM["Adjoint"] = Operator( fromMethod = asFunction["Adjoint"], avoidingRedundancy = avoidRC )
521 elif asMatrix is not None:
522 matrice = numpy.matrix( asMatrix, numpy.float )
523 self.__CM["Direct"] = Operator( fromMatrix = matrice, avoidingRedundancy = avoidRC )
524 self.__CM["Tangent"] = Operator( fromMatrix = matrice, avoidingRedundancy = avoidRC )
525 self.__CM["Adjoint"] = Operator( fromMatrix = matrice.T, avoidingRedundancy = avoidRC )
528 raise ValueError("Improperly defined input control model, it requires at minima either a matrix, a Direct for approximate derivatives or a Tangent/Adjoint pair.")
531 self.__StoredInputs["ControlModel"] = self.__CM
534 def setControlInput(self,
536 asPersistentVector = None,
541 Permet de définir le controle en entree :
542 - asVector : entrée des données, comme un vecteur compatible avec le
543 constructeur de numpy.matrix
544 - asPersistentVector : entrée des données, comme un vecteur de type
545 persistent contruit avec la classe ad-hoc "Persistence"
546 - Scheduler est le contrôle temporel des données disponibles
547 - toBeStored : booléen indiquant si la donnée d'entrée est sauvée pour
548 être rendue disponible au même titre que les variables de calcul
550 if asVector is not None:
551 if isinstance(asVector,numpy.matrix):
552 self.__U = numpy.matrix( asVector.A1, numpy.float ).T
554 self.__U = numpy.matrix( asVector, numpy.float ).T
555 elif asPersistentVector is not None:
556 if type(asPersistentVector) in [type([]),type(()),type(numpy.array([])),type(numpy.matrix([]))]:
557 self.__U = Persistence.OneVector("ControlInput", basetype=numpy.matrix)
558 for member in asPersistentVector:
559 self.__U.store( numpy.matrix( numpy.asmatrix(member).A1, numpy.float ).T )
561 self.__U = asPersistentVector
563 raise ValueError("Error: improperly defined control input, it requires at minima either a vector, a list/tuple of vectors or a persistent object")
565 self.__StoredInputs["ControlInput"] = self.__U
568 # -----------------------------------------------------------
569 def setControls (self,
574 Permet de définir la valeur initiale du vecteur X contenant toutes les
575 variables de contrôle, i.e. les paramètres ou l'état dont on veut
576 estimer la valeur pour obtenir les observations. C'est utile pour un
577 algorithme itératif/incrémental.
578 - asVector : entrée des données, comme un vecteur compatible avec le
579 constructeur de numpy.matrix.
580 - toBeStored : booléen indiquant si la donnée d'entrée est sauvée pour
581 être rendue disponible au même titre que les variables de calcul
583 if asVector is not None:
584 self.__X.store( asVector )
586 self.__StoredInputs["Controls"] = self.__X
589 # -----------------------------------------------------------
590 def setAlgorithm(self, choice = None ):
592 Permet de sélectionner l'algorithme à utiliser pour mener à bien l'étude
593 d'assimilation. L'argument est un champ caractère se rapportant au nom
594 d'un fichier contenu dans "../daAlgorithms" et réalisant l'opération
595 d'assimilation sur les arguments fixes.
598 raise ValueError("Error: algorithm choice has to be given")
599 if self.__algorithmName is not None:
600 raise ValueError("Error: algorithm choice has already been done as \"%s\", it can't be changed."%self.__algorithmName)
601 daDirectory = "daAlgorithms"
603 # Recherche explicitement le fichier complet
604 # ------------------------------------------
606 for directory in sys.path:
607 if os.path.isfile(os.path.join(directory, daDirectory, str(choice)+'.py')):
608 module_path = os.path.abspath(os.path.join(directory, daDirectory))
609 if module_path is None:
610 raise ImportError("No algorithm module named \"%s\" was found in a \"%s\" subdirectory\n The search path is %s"%(choice, daDirectory, sys.path))
612 # Importe le fichier complet comme un module
613 # ------------------------------------------
615 sys_path_tmp = sys.path ; sys.path.insert(0,module_path)
616 self.__algorithmFile = __import__(str(choice), globals(), locals(), [])
617 self.__algorithmName = str(choice)
618 sys.path = sys_path_tmp ; del sys_path_tmp
619 except ImportError, e:
620 raise ImportError("The module named \"%s\" was found, but is incorrect at the import stage.\n The import error message is: %s"%(choice,e))
622 # Instancie un objet du type élémentaire du fichier
623 # -------------------------------------------------
624 self.__algorithm = self.__algorithmFile.ElementaryAlgorithm()
625 self.__StoredInputs["AlgorithmName"] = self.__algorithmName
628 def setAlgorithmParameters(self, asDico=None):
630 Permet de définir les paramètres de l'algorithme, sous la forme d'un
633 if asDico is not None:
634 self.__Parameters.update( dict( asDico ) )
636 self.__StoredInputs["AlgorithmParameters"] = self.__Parameters
639 def getAlgorithmParameters(self, noDetails=True):
641 Renvoie la liste des paramètres requis selon l'algorithme
643 return self.__algorithm.getRequiredParameters(noDetails)
645 # -----------------------------------------------------------
646 def setDiagnostic(self, choice = None, name = "", unit = "", basetype = None, parameters = {} ):
648 Permet de sélectionner un diagnostic a effectuer.
651 raise ValueError("Error: diagnostic choice has to be given")
652 daDirectory = "daDiagnostics"
654 # Recherche explicitement le fichier complet
655 # ------------------------------------------
657 for directory in sys.path:
658 if os.path.isfile(os.path.join(directory, daDirectory, str(choice)+'.py')):
659 module_path = os.path.abspath(os.path.join(directory, daDirectory))
660 if module_path is None:
661 raise ImportError("No diagnostic module named \"%s\" was found in a \"%s\" subdirectory\n The search path is %s"%(choice, daDirectory, sys.path))
663 # Importe le fichier complet comme un module
664 # ------------------------------------------
666 sys_path_tmp = sys.path ; sys.path.insert(0,module_path)
667 self.__diagnosticFile = __import__(str(choice), globals(), locals(), [])
668 sys.path = sys_path_tmp ; del sys_path_tmp
669 except ImportError, e:
670 raise ImportError("The module named \"%s\" was found, but is incorrect at the import stage.\n The import error message is: %s"%(choice,e))
672 # Instancie un objet du type élémentaire du fichier
673 # -------------------------------------------------
674 if self.__StoredInputs.has_key(name):
675 raise ValueError("A default input with the same name \"%s\" already exists."%str(name))
676 elif self.__StoredDiagnostics.has_key(name):
677 raise ValueError("A diagnostic with the same name \"%s\" already exists."%str(name))
679 self.__StoredDiagnostics[name] = self.__diagnosticFile.ElementaryDiagnostic(
683 parameters = parameters )
686 # -----------------------------------------------------------
687 def shape_validate(self):
689 Validation de la correspondance correcte des tailles des variables et
690 des matrices s'il y en a.
692 if self.__Xb is None: __Xb_shape = (0,)
693 elif hasattr(self.__Xb,"size"): __Xb_shape = (self.__Xb.size,)
694 elif hasattr(self.__Xb,"shape"):
695 if type(self.__Xb.shape) is tuple: __Xb_shape = self.__Xb.shape
696 else: __Xb_shape = self.__Xb.shape()
697 else: raise TypeError("The background (Xb) has no attribute of shape: problem !")
699 if self.__Y is None: __Y_shape = (0,)
700 elif hasattr(self.__Y,"size"): __Y_shape = (self.__Y.size,)
701 elif hasattr(self.__Y,"shape"):
702 if type(self.__Y.shape) is tuple: __Y_shape = self.__Y.shape
703 else: __Y_shape = self.__Y.shape()
704 else: raise TypeError("The observation (Y) has no attribute of shape: problem !")
706 if self.__U is None: __U_shape = (0,)
707 elif hasattr(self.__U,"size"): __U_shape = (self.__U.size,)
708 elif hasattr(self.__U,"shape"):
709 if type(self.__U.shape) is tuple: __U_shape = self.__U.shape
710 else: __U_shape = self.__U.shape()
711 else: raise TypeError("The control (U) has no attribute of shape: problem !")
713 if self.__B is None: __B_shape = (0,0)
714 elif hasattr(self.__B,"shape"):
715 if type(self.__B.shape) is tuple: __B_shape = self.__B.shape
716 else: __B_shape = self.__B.shape()
717 else: raise TypeError("The a priori errors covariance matrix (B) has no attribute of shape: problem !")
719 if self.__R is None: __R_shape = (0,0)
720 elif hasattr(self.__R,"shape"):
721 if type(self.__R.shape) is tuple: __R_shape = self.__R.shape
722 else: __R_shape = self.__R.shape()
723 else: raise TypeError("The observation errors covariance matrix (R) has no attribute of shape: problem !")
725 if self.__Q is None: __Q_shape = (0,0)
726 elif hasattr(self.__Q,"shape"):
727 if type(self.__Q.shape) is tuple: __Q_shape = self.__Q.shape
728 else: __Q_shape = self.__Q.shape()
729 else: raise TypeError("The evolution errors covariance matrix (Q) has no attribute of shape: problem !")
731 if len(self.__HO) == 0: __HO_shape = (0,0)
732 elif type(self.__HO) is type({}): __HO_shape = (0,0)
733 elif hasattr(self.__HO["Direct"],"shape"):
734 if type(self.__HO["Direct"].shape) is tuple: __HO_shape = self.__HO["Direct"].shape
735 else: __HO_shape = self.__HO["Direct"].shape()
736 else: raise TypeError("The observation operator (H) has no attribute of shape: problem !")
738 if len(self.__EM) == 0: __EM_shape = (0,0)
739 elif type(self.__EM) is type({}): __EM_shape = (0,0)
740 elif hasattr(self.__EM["Direct"],"shape"):
741 if type(self.__EM["Direct"].shape) is tuple: __EM_shape = self.__EM["Direct"].shape
742 else: __EM_shape = self.__EM["Direct"].shape()
743 else: raise TypeError("The evolution model (EM) has no attribute of shape: problem !")
745 if len(self.__CM) == 0: __CM_shape = (0,0)
746 elif type(self.__CM) is type({}): __CM_shape = (0,0)
747 elif hasattr(self.__CM["Direct"],"shape"):
748 if type(self.__CM["Direct"].shape) is tuple: __CM_shape = self.__CM["Direct"].shape
749 else: __CM_shape = self.__CM["Direct"].shape()
750 else: raise TypeError("The control model (CM) has no attribute of shape: problem !")
752 # Vérification des conditions
753 # ---------------------------
754 if not( len(__Xb_shape) == 1 or min(__Xb_shape) == 1 ):
755 raise ValueError("Shape characteristic of background (Xb) is incorrect: \"%s\"."%(__Xb_shape,))
756 if not( len(__Y_shape) == 1 or min(__Y_shape) == 1 ):
757 raise ValueError("Shape characteristic of observation (Y) is incorrect: \"%s\"."%(__Y_shape,))
759 if not( min(__B_shape) == max(__B_shape) ):
760 raise ValueError("Shape characteristic of a priori errors covariance matrix (B) is incorrect: \"%s\"."%(__B_shape,))
761 if not( min(__R_shape) == max(__R_shape) ):
762 raise ValueError("Shape characteristic of observation errors covariance matrix (R) is incorrect: \"%s\"."%(__R_shape,))
763 if not( min(__Q_shape) == max(__Q_shape) ):
764 raise ValueError("Shape characteristic of evolution errors covariance matrix (Q) is incorrect: \"%s\"."%(__Q_shape,))
765 if not( min(__EM_shape) == max(__EM_shape) ):
766 raise ValueError("Shape characteristic of evolution operator (EM) is incorrect: \"%s\"."%(__EM_shape,))
768 if len(self.__HO) > 0 and not(type(self.__HO) is type({})) and not( __HO_shape[1] == max(__Xb_shape) ):
769 raise ValueError("Shape characteristic of observation operator (H) \"%s\" and state (X) \"%s\" are incompatible."%(__HO_shape,__Xb_shape))
770 if len(self.__HO) > 0 and not(type(self.__HO) is type({})) and not( __HO_shape[0] == max(__Y_shape) ):
771 raise ValueError("Shape characteristic of observation operator (H) \"%s\" and observation (Y) \"%s\" are incompatible."%(__HO_shape,__Y_shape))
772 if len(self.__HO) > 0 and not(type(self.__HO) is type({})) and len(self.__B) > 0 and not( __HO_shape[1] == __B_shape[0] ):
773 raise ValueError("Shape characteristic of observation operator (H) \"%s\" and a priori errors covariance matrix (B) \"%s\" are incompatible."%(__HO_shape,__B_shape))
774 if len(self.__HO) > 0 and not(type(self.__HO) is type({})) and len(self.__R) > 0 and not( __HO_shape[0] == __R_shape[1] ):
775 raise ValueError("Shape characteristic of observation operator (H) \"%s\" and observation errors covariance matrix (R) \"%s\" are incompatible."%(__HO_shape,__R_shape))
777 if self.__B is not None and len(self.__B) > 0 and not( __B_shape[1] == max(__Xb_shape) ):
778 if self.__StoredInputs["AlgorithmName"] in ["EnsembleBlue",]:
779 asPersistentVector = self.__Xb.reshape((-1,min(__B_shape)))
780 self.__Xb = Persistence.OneVector("Background", basetype=numpy.matrix)
781 for member in asPersistentVector:
782 self.__Xb.store( numpy.matrix( numpy.ravel(member), numpy.float ).T )
783 __Xb_shape = min(__B_shape)
785 raise ValueError("Shape characteristic of a priori errors covariance matrix (B) \"%s\" and background (Xb) \"%s\" are incompatible."%(__B_shape,__Xb_shape))
787 if self.__R is not None and len(self.__R) > 0 and not( __R_shape[1] == max(__Y_shape) ):
788 raise ValueError("Shape characteristic of observation errors covariance matrix (R) \"%s\" and observation (Y) \"%s\" are incompatible."%(__R_shape,__Y_shape))
790 if self.__EM is not None and len(self.__EM) > 0 and not(type(self.__EM) is type({})) and not( __EM_shape[1] == max(__Xb_shape) ):
791 raise ValueError("Shape characteristic of evolution model (EM) \"%s\" and state (X) \"%s\" are incompatible."%(__EM_shape,__Xb_shape))
793 if self.__CM is not None and len(self.__CM) > 0 and not(type(self.__CM) is type({})) and not( __CM_shape[1] == max(__U_shape) ):
794 raise ValueError("Shape characteristic of control model (CM) \"%s\" and control (U) \"%s\" are incompatible."%(__CM_shape,__U_shape))
796 if self.__StoredInputs.has_key("AlgorithmParameters") \
797 and self.__StoredInputs["AlgorithmParameters"].has_key("Bounds") \
798 and (type(self.__StoredInputs["AlgorithmParameters"]["Bounds"]) is type([]) or type(self.__StoredInputs["AlgorithmParameters"]["Bounds"]) is type(())) \
799 and (len(self.__StoredInputs["AlgorithmParameters"]["Bounds"]) != max(__Xb_shape)):
800 raise ValueError("The number \"%s\" of bound pairs for the state (X) components is different of the size \"%s\" of the state itself." \
801 %(len(self.__StoredInputs["AlgorithmParameters"]["Bounds"]),max(__Xb_shape)))
805 # -----------------------------------------------------------
808 Permet de lancer le calcul d'assimilation.
810 Le nom de la méthode à activer est toujours "run". Les paramètres en
811 arguments de la méthode sont fixés. En sortie, on obtient les résultats
812 dans la variable de type dictionnaire "StoredVariables", qui contient en
813 particulier des objets de Persistence pour les analyses, OMA...
815 Operator.CM.clearCache()
816 self.shape_validate()
818 self.__algorithm.run(
828 Parameters = self.__Parameters,
832 # -----------------------------------------------------------
833 def get(self, key=None):
835 Renvoie les résultats disponibles après l'exécution de la méthode
836 d'assimilation, ou les diagnostics disponibles. Attention, quand un
837 diagnostic porte le même nom qu'une variable stockée, c'est la variable
838 stockée qui est renvoyée, et le diagnostic est inatteignable.
841 if self.__algorithm.has_key(key):
842 return self.__algorithm.get( key )
843 elif self.__StoredInputs.has_key(key):
844 return self.__StoredInputs[key]
845 elif self.__StoredDiagnostics.has_key(key):
846 return self.__StoredDiagnostics[key]
848 raise ValueError("The requested key \"%s\" does not exists as an input, a diagnostic or a stored variable."%key)
850 allvariables = self.__algorithm.get()
851 allvariables.update( self.__StoredDiagnostics )
852 allvariables.update( self.__StoredInputs )
855 def get_available_variables(self):
857 Renvoie les variables potentiellement utilisables pour l'étude,
858 initialement stockées comme données d'entrées ou dans les algorithmes,
859 identifiés par les chaînes de caractères. L'algorithme doit avoir été
860 préalablement choisi sinon la méthode renvoie "None".
862 if len( self.__algorithm.keys()) == 0 and len( self.__StoredInputs.keys() ) == 0:
866 if len( self.__algorithm.keys()) > 0:
867 variables.extend( self.__algorithm.get().keys() )
868 if len( self.__StoredInputs.keys() ) > 0:
869 variables.extend( self.__StoredInputs.keys() )
873 def get_available_algorithms(self):
875 Renvoie la liste des algorithmes potentiellement utilisables, identifiés
876 par les chaînes de caractères.
879 for directory in sys.path:
880 if os.path.isdir(os.path.join(directory,"daAlgorithms")):
881 for fname in os.listdir(os.path.join(directory,"daAlgorithms")):
882 root, ext = os.path.splitext(fname)
883 if ext == '.py' and root != '__init__':
888 def get_available_diagnostics(self):
890 Renvoie la liste des diagnostics potentiellement utilisables, identifiés
891 par les chaînes de caractères.
894 for directory in sys.path:
895 if os.path.isdir(os.path.join(directory,"daDiagnostics")):
896 for fname in os.listdir(os.path.join(directory,"daDiagnostics")):
897 root, ext = os.path.splitext(fname)
898 if ext == '.py' and root != '__init__':
903 # -----------------------------------------------------------
904 def get_algorithms_main_path(self):
906 Renvoie le chemin pour le répertoire principal contenant les algorithmes
907 dans un sous-répertoire "daAlgorithms"
911 def add_algorithms_path(self, asPath=None):
913 Ajoute au chemin de recherche des algorithmes un répertoire dans lequel
914 se trouve un sous-répertoire "daAlgorithms"
916 Remarque : si le chemin a déjà été ajouté pour les diagnostics, il n'est
917 pas indispensable de le rajouter ici.
919 if not os.path.isdir(asPath):
920 raise ValueError("The given "+asPath+" argument must exist as a directory")
921 if not os.path.isdir(os.path.join(asPath,"daAlgorithms")):
922 raise ValueError("The given \""+asPath+"\" argument must contain a subdirectory named \"daAlgorithms\"")
923 if not os.path.isfile(os.path.join(asPath,"daAlgorithms","__init__.py")):
924 raise ValueError("The given \""+asPath+"/daAlgorithms\" path must contain a file named \"__init__.py\"")
925 sys.path.insert(0, os.path.abspath(asPath))
926 sys.path = uniq( sys.path ) # Conserve en unique exemplaire chaque chemin
929 def get_diagnostics_main_path(self):
931 Renvoie le chemin pour le répertoire principal contenant les diagnostics
932 dans un sous-répertoire "daDiagnostics"
936 def add_diagnostics_path(self, asPath=None):
938 Ajoute au chemin de recherche des algorithmes un répertoire dans lequel
939 se trouve un sous-répertoire "daDiagnostics"
941 Remarque : si le chemin a déjà été ajouté pour les algorithmes, il n'est
942 pas indispensable de le rajouter ici.
944 if not os.path.isdir(asPath):
945 raise ValueError("The given "+asPath+" argument must exist as a directory")
946 if not os.path.isdir(os.path.join(asPath,"daDiagnostics")):
947 raise ValueError("The given \""+asPath+"\" argument must contain a subdirectory named \"daDiagnostics\"")
948 if not os.path.isfile(os.path.join(asPath,"daDiagnostics","__init__.py")):
949 raise ValueError("The given \""+asPath+"/daDiagnostics\" path must contain a file named \"__init__.py\"")
950 sys.path.insert(0, os.path.abspath(asPath))
951 sys.path = uniq( sys.path ) # Conserve en unique exemplaire chaque chemin
954 # -----------------------------------------------------------
955 def setDataObserver(self,
958 HookParameters = None,
962 Permet d'associer un observer à une ou des variables nommées gérées en
963 interne, activable selon des règles définies dans le Scheduler. A chaque
964 pas demandé dans le Scheduler, il effectue la fonction HookFunction avec
965 les arguments (variable persistante VariableName, paramètres HookParameters).
968 if type( self.__algorithm ) is dict:
969 raise ValueError("No observer can be build before choosing an algorithm.")
971 # Vérification du nom de variable et typage
972 # -----------------------------------------
973 if type( VariableName ) is str:
974 VariableNames = [VariableName,]
975 elif type( VariableName ) is list:
976 VariableNames = map( str, VariableName )
978 raise ValueError("The observer requires a name or a list of names of variables.")
980 # Association interne de l'observer à la variable
981 # -----------------------------------------------
982 for n in VariableNames:
983 if not self.__algorithm.has_key( n ):
984 raise ValueError("An observer requires to be set on a variable named %s which does not exist."%n)
986 self.__algorithm.StoredVariables[ n ].setDataObserver(
987 Scheduler = Scheduler,
988 HookFunction = HookFunction,
989 HookParameters = HookParameters,
992 def removeDataObserver(self,
997 Permet de retirer un observer à une ou des variable nommée.
1000 if type( self.__algorithm ) is dict:
1001 raise ValueError("No observer can be removed before choosing an algorithm.")
1003 # Vérification du nom de variable et typage
1004 # -----------------------------------------
1005 if type( VariableName ) is str:
1006 VariableNames = [VariableName,]
1007 elif type( VariableName ) is list:
1008 VariableNames = map( str, VariableName )
1010 raise ValueError("The observer requires a name or a list of names of variables.")
1012 # Association interne de l'observer à la variable
1013 # -----------------------------------------------
1014 for n in VariableNames:
1015 if not self.__algorithm.has_key( n ):
1016 raise ValueError("An observer requires to be removed on a variable named %s which does not exist."%n)
1018 self.__algorithm.StoredVariables[ n ].removeDataObserver(
1019 HookFunction = HookFunction,
1022 # -----------------------------------------------------------
1023 def setDebug(self, level=10):
1025 Utiliser par exemple "import logging ; level = logging.DEBUG" avant cet
1026 appel pour changer le niveau de verbosité, avec :
1027 NOTSET=0 < DEBUG=10 < INFO=20 < WARNING=30 < ERROR=40 < CRITICAL=50
1029 log = logging.getLogger()
1030 log.setLevel( level )
1032 def unsetDebug(self):
1034 Remet le logger au niveau par défaut
1036 log = logging.getLogger()
1037 log.setLevel( logging.WARNING )
1040 # return set(self.__dict__.keys() + dir(self.__class__))
1041 return ['get', '__doc__', '__init__', '__module__']
1043 def prepare_to_pickle(self):
1045 Retire les variables non pickelisables
1048 del self.__CM # non pickelisable
1049 del self.__EM # non pickelisable
1050 del self.__HO # non pickelisable
1051 del self.__Parameters
1058 del self.__algorithmFile # non pickelisable
1059 del self.__algorithmName
1060 del self.__diagnosticFile # non pickelisable
1061 self.__class__.__doc__ = ""
1063 # ==============================================================================
1064 if __name__ == "__main__":
1065 print '\n AUTODIAGNOSTIC \n'