Salome HOME
bb26ee2251b7cf24d28e4a8d299c8d806fb12205
[modules/adao.git] / src / daComposant / daCore / BasicObjects.py
1 #-*-coding:iso-8859-1-*-
2 #
3 # Copyright (C) 2008-2013 EDF R&D
4 #
5 # This library is free software; you can redistribute it and/or
6 # modify it under the terms of the GNU Lesser General Public
7 # License as published by the Free Software Foundation; either
8 # version 2.1 of the License.
9 #
10 # This library is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13 # Lesser General Public License for more details.
14 #
15 # You should have received a copy of the GNU Lesser General Public
16 # License along with this library; if not, write to the Free Software
17 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
18 #
19 # See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
20 #
21 # Author: Jean-Philippe Argaud, jean-philippe.argaud@edf.fr, EDF R&D
22
23 __doc__ = """
24     Définit les outils généraux élémentaires.
25     
26     Ce module est destiné à etre appelée par AssimilationStudy pour constituer
27     les objets élémentaires de l'algorithme.
28 """
29 __author__ = "Jean-Philippe ARGAUD"
30
31 import logging
32 import numpy
33 import Persistence
34
35 # ==============================================================================
36 class Operator:
37     """
38     Classe générale d'interface de type opérateur
39     """
40     def __init__(self, fromMethod=None, fromMatrix=None):
41         """
42         On construit un objet de ce type en fournissant à l'aide de l'un des
43         deux mots-clé, soit une fonction python, soit matrice.
44         Arguments :
45         - fromMethod : argument de type fonction Python
46         - fromMatrix : argument adapté au constructeur numpy.matrix
47         """
48         if   fromMethod is not None:
49             self.__Method = fromMethod
50             self.__Matrix = None
51             self.__Type   = "Method"
52         elif fromMatrix is not None:
53             self.__Method = None
54             self.__Matrix = numpy.matrix( fromMatrix, numpy.float )
55             self.__Type   = "Matrix"
56         else:
57             self.__Method = None
58             self.__Matrix = None
59             self.__Type   = None
60
61     def isType(self):
62         return self.__Type
63
64     def appliedTo(self, xValue):
65         """
66         Permet de restituer le résultat de l'application de l'opérateur à un
67         argument xValue. Cette méthode se contente d'appliquer, son argument
68         devant a priori être du bon type.
69         Arguments :
70         - xValue : argument adapté pour appliquer l'opérateur
71         """
72         if self.__Matrix is not None:
73             return self.__Matrix * xValue
74         else:
75             return self.__Method( xValue )
76
77     def appliedInXTo(self, (xNominal, xValue) ):
78         """
79         Permet de restituer le résultat de l'application de l'opérateur à un
80         argument xValue, sachant que l'opérateur est valable en xNominal.
81         Cette méthode se contente d'appliquer, son argument devant a priori
82         être du bon type. Si l'opérateur est linéaire car c'est une matrice,
83         alors il est valable en tout point nominal et il n'est pas nécessaire
84         d'utiliser xNominal.
85         Arguments : une liste contenant
86         - xNominal : argument permettant de donner le point où l'opérateur
87           est construit pour etre ensuite appliqué
88         - xValue : argument adapté pour appliquer l'opérateur
89         """
90         if self.__Matrix is not None:
91             return self.__Matrix * xValue
92         else:
93             return self.__Method( (xNominal, xValue) )
94
95     def asMatrix(self, ValueForMethodForm = "UnknownVoidValue"):
96         """
97         Permet de renvoyer l'opérateur sous la forme d'une matrice
98         """
99         if self.__Matrix is not None:
100             return self.__Matrix
101         elif ValueForMethodForm is not "UnknownVoidValue": # Ne pas utiliser "None"
102             return self.__Method( (ValueForMethodForm, None) )
103         else:
104             raise ValueError("Matrix form of the operator defined as a function/method requires to give an operating point.")
105
106     def shape(self):
107         """
108         Renvoie la taille sous forme numpy si l'opérateur est disponible sous
109         la forme d'une matrice
110         """
111         if self.__Matrix is not None:
112             return self.__Matrix.shape
113         else:
114             raise ValueError("Matrix form of the operator is not available, nor the shape")
115
116 # ==============================================================================
117 class Algorithm:
118     """
119     Classe générale d'interface de type algorithme
120     
121     Elle donne un cadre pour l'écriture d'une classe élémentaire d'algorithme
122     d'assimilation, en fournissant un container (dictionnaire) de variables
123     persistantes initialisées, et des méthodes d'accès à ces variables stockées.
124     
125     Une classe élémentaire d'algorithme doit implémenter la méthode "run".
126     """
127     def __init__(self, name):
128         """
129         L'initialisation présente permet de fabriquer des variables de stockage
130         disponibles de manière générique dans les algorithmes élémentaires. Ces
131         variables de stockage sont ensuite conservées dans un dictionnaire
132         interne à l'objet, mais auquel on accède par la méthode "get".
133         
134         Les variables prévues sont :
135             - CostFunctionJ  : fonction-cout globale, somme des deux parties suivantes
136             - CostFunctionJb : partie ébauche ou background de la fonction-cout
137             - CostFunctionJo : partie observations de la fonction-cout
138             - GradientOfCostFunctionJ  : gradient de la fonction-cout globale
139             - GradientOfCostFunctionJb : gradient de la partie ébauche de la fonction-cout
140             - GradientOfCostFunctionJo : gradient de la partie observations de la fonction-cout
141             - CurrentState : état courant lors d'itérations
142             - Analysis : l'analyse
143             - Innovation : l'innovation : d = Y - H Xb
144             - SigmaObs2 : indicateur de correction optimale des erreurs d'observation
145             - SigmaBck2 : indicateur de correction optimale des erreurs d'ébauche
146             - MahalanobisConsistency : indicateur de consistance des covariances
147             - OMA : Observation moins Analysis : Y - Xa
148             - OMB : Observation moins Background : Y - Xb
149             - AMB : Analysis moins Background : Xa - Xb
150             - APosterioriCovariance : matrice A
151         On peut rajouter des variables à stocker dans l'initialisation de
152         l'algorithme élémentaire qui va hériter de cette classe
153         """
154         logging.debug("%s Initialisation"%str(name))
155         self._name = str( name )
156         self._parameters = {}
157         self.__required_parameters = {}
158         self.StoredVariables = {}
159         #
160         self.StoredVariables["CostFunctionJ"]            = Persistence.OneScalar(name = "CostFunctionJ")
161         self.StoredVariables["CostFunctionJb"]           = Persistence.OneScalar(name = "CostFunctionJb")
162         self.StoredVariables["CostFunctionJo"]           = Persistence.OneScalar(name = "CostFunctionJo")
163         self.StoredVariables["GradientOfCostFunctionJ"]  = Persistence.OneVector(name = "GradientOfCostFunctionJ")
164         self.StoredVariables["GradientOfCostFunctionJb"] = Persistence.OneVector(name = "GradientOfCostFunctionJb")
165         self.StoredVariables["GradientOfCostFunctionJo"] = Persistence.OneVector(name = "GradientOfCostFunctionJo")
166         self.StoredVariables["CurrentState"]             = Persistence.OneVector(name = "CurrentState")
167         self.StoredVariables["Analysis"]                 = Persistence.OneVector(name = "Analysis")
168         self.StoredVariables["Innovation"]               = Persistence.OneVector(name = "Innovation")
169         self.StoredVariables["SigmaObs2"]                = Persistence.OneScalar(name = "SigmaObs2")
170         self.StoredVariables["SigmaBck2"]                = Persistence.OneScalar(name = "SigmaBck2")
171         self.StoredVariables["MahalanobisConsistency"]   = Persistence.OneScalar(name = "MahalanobisConsistency")
172         self.StoredVariables["OMA"]                      = Persistence.OneVector(name = "OMA")
173         self.StoredVariables["OMB"]                      = Persistence.OneVector(name = "OMB")
174         self.StoredVariables["BMA"]                      = Persistence.OneVector(name = "BMA")
175         self.StoredVariables["APosterioriCovariance"]    = Persistence.OneMatrix(name = "APosterioriCovariance")
176
177     def get(self, key=None):
178         """
179         Renvoie l'une des variables stockées identifiée par la clé, ou le
180         dictionnaire de l'ensemble des variables disponibles en l'absence de
181         clé. Ce sont directement les variables sous forme objet qui sont
182         renvoyées, donc les méthodes d'accès à l'objet individuel sont celles
183         des classes de persistance.
184         """
185         if key is not None:
186             return self.StoredVariables[key]
187         else:
188             return self.StoredVariables
189
190     def has_key(self, key=None):
191         """
192         Vérifie si l'une des variables stockées est identifiée par la clé.
193         """
194         return self.StoredVariables.has_key(key)
195
196     def keys(self):
197         """
198         Renvoie la liste des clés de variables stockées.
199         """
200         return self.StoredVariables.keys()
201
202     def run(self, Xb=None, Y=None, H=None, M=None, R=None, B=None, Q=None, Parameters=None):
203         """
204         Doit implémenter l'opération élémentaire de calcul d'assimilation sous
205         sa forme mathématique la plus naturelle possible.
206         """
207         raise NotImplementedError("Mathematical assimilation calculation has not been implemented!")
208
209     def defineRequiredParameter(self, name = None, default = None, typecast = None, message = None, minval = None, maxval = None, listval = None):
210         """
211         Permet de définir dans l'algorithme des paramètres requis et leurs
212         caractéristiques par défaut.
213         """
214         if name is None:
215             raise ValueError("A name is mandatory to define a required parameter.")
216         #
217         self.__required_parameters[name] = {
218             "default"  : default,
219             "typecast" : typecast,
220             "minval"   : minval,
221             "maxval"   : maxval,
222             "listval"  : listval,
223             "message"  : message,
224             }
225         logging.debug("%s %s (valeur par défaut = %s)"%(self._name, message, self.setParameterValue(name)))
226
227     def getRequiredParameters(self, noDetails=True):
228         """
229         Renvoie la liste des noms de paramètres requis ou directement le
230         dictionnaire des paramètres requis.
231         """
232         if noDetails:
233             ks = self.__required_parameters.keys()
234             ks.sort()
235             return ks
236         else:
237             return self.__required_parameters
238
239     def setParameterValue(self, name=None, value=None):
240         """
241         Renvoie la valeur d'un paramètre requis de manière contrôlée
242         """
243         default  = self.__required_parameters[name]["default"]
244         typecast = self.__required_parameters[name]["typecast"]
245         minval   = self.__required_parameters[name]["minval"]
246         maxval   = self.__required_parameters[name]["maxval"]
247         listval  = self.__required_parameters[name]["listval"]
248         #
249         if value is None and default is None:
250             __val = None
251         elif value is None and default is not None:
252             if typecast is None: __val = default
253             else:                __val = typecast( default )
254         else:
255             if typecast is None: __val = value
256             else:                __val = typecast( value )
257         #
258         if minval is not None and __val < minval:
259             raise ValueError("The parameter named \"%s\" of value \"%s\" can not be less than %s."%(name, __val, minval))
260         if maxval is not None and __val > maxval:
261             raise ValueError("The parameter named \"%s\" of value \"%s\" can not be greater than %s."%(name, __val, maxval))
262         if listval is not None:
263             if typecast is list or typecast is tuple or type(__val) is list or type(__val) is tuple:
264                 for v in __val:
265                     if v not in listval:
266                         raise ValueError("The value \"%s\" of the parameter named \"%s\" is not allowed, it has to be in the list %s."%(v, name, listval))
267             elif __val not in listval:
268                 raise ValueError("The value \"%s\" of the parameter named \"%s\" is not allowed, it has to be in the list %s."%( __val, name,listval))
269         return __val
270
271     def setParameters(self, fromDico={}):
272         """
273         Permet de stocker les paramètres reçus dans le dictionnaire interne.
274         """
275         self._parameters.update( fromDico )
276         for k in self.__required_parameters.keys():
277             if k in fromDico.keys():
278                 self._parameters[k] = self.setParameterValue(k,fromDico[k])
279             else:
280                 self._parameters[k] = self.setParameterValue(k)
281             logging.debug("%s %s : %s"%(self._name, self.__required_parameters[k]["message"], self._parameters[k]))
282
283 # ==============================================================================
284 class Diagnostic:
285     """
286     Classe générale d'interface de type diagnostic
287         
288     Ce template s'utilise de la manière suivante : il sert de classe "patron" en
289     même temps que l'une des classes de persistance, comme "OneScalar" par
290     exemple.
291     
292     Une classe élémentaire de diagnostic doit implémenter ses deux méthodes, la
293     méthode "_formula" pour écrire explicitement et proprement la formule pour
294     l'écriture mathématique du calcul du diagnostic (méthode interne non
295     publique), et "calculate" pour activer la précédente tout en ayant vérifié
296     et préparé les données, et pour stocker les résultats à chaque pas (méthode
297     externe d'activation).
298     """
299     def __init__(self, name = "", parameters = {}):
300         self.name       = str(name)
301         self.parameters = dict( parameters )
302
303     def _formula(self, *args):
304         """
305         Doit implémenter l'opération élémentaire de diagnostic sous sa forme
306         mathématique la plus naturelle possible.
307         """
308         raise NotImplementedError("Diagnostic mathematical formula has not been implemented!")
309
310     def calculate(self, *args):
311         """
312         Active la formule de calcul avec les arguments correctement rangés
313         """
314         raise NotImplementedError("Diagnostic activation method has not been implemented!")
315
316 # ==============================================================================
317 if __name__ == "__main__":
318     print '\n AUTODIAGNOSTIC \n'