Salome HOME
03cf56bf7202fef2a074a7120c6596cc14104a33
[modules/adao.git] / src / daComposant / daAlgorithms / NonLinearLeastSquares.py
1 #-*-coding:iso-8859-1-*-
2 #
3 #  Copyright (C) 2008-2012 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
22 import logging
23 from daCore import BasicObjects, PlatformInfo
24 m = PlatformInfo.SystemUsage()
25
26 import numpy
27 import scipy.optimize
28
29 if logging.getLogger().level < 30:
30     iprint  = 1
31     message = scipy.optimize.tnc.MSG_ALL
32     disp    = 1
33 else:
34     iprint  = -1
35     message = scipy.optimize.tnc.MSG_NONE
36     disp    = 0
37
38 # ==============================================================================
39 class ElementaryAlgorithm(BasicObjects.Algorithm):
40     def __init__(self):
41         BasicObjects.Algorithm.__init__(self, "NONLINEARLEASTSQUARES")
42         self.defineRequiredParameter(
43             name     = "Minimizer",
44             default  = "LBFGSB",
45             typecast = str,
46             message  = "Minimiseur utilisé",
47             listval  = ["LBFGSB","TNC", "CG", "NCG", "BFGS"],
48             )
49         self.defineRequiredParameter(
50             name     = "MaximumNumberOfSteps",
51             default  = 15000,
52             typecast = int,
53             message  = "Nombre maximal de pas d'optimisation",
54             minval   = -1
55             )
56         self.defineRequiredParameter(
57             name     = "CostDecrementTolerance",
58             default  = 1.e-7,
59             typecast = float,
60             message  = "Diminution relative minimale du cout lors de l'arrêt",
61             )
62         self.defineRequiredParameter(
63             name     = "ProjectedGradientTolerance",
64             default  = -1,
65             typecast = float,
66             message  = "Maximum des composantes du gradient projeté lors de l'arrêt",
67             minval   = -1,
68             )
69         self.defineRequiredParameter(
70             name     = "GradientNormTolerance",
71             default  = 1.e-05,
72             typecast = float,
73             message  = "Maximum des composantes du gradient lors de l'arrêt",
74             )
75
76     def run(self, Xb=None, Y=None, H=None, M=None, R=None, B=None, Q=None, Parameters=None):
77         """
78         Calcul de l'estimateur moindres carrés pondérés non linéaires
79         (assimilation variationnelle sans ébauche)
80         """
81         logging.debug("%s Lancement"%self._name)
82         logging.debug("%s Taille mémoire utilisée de %.1f Mo"%(self._name, m.getUsedMemory("Mo")))
83         #
84         # Paramètres de pilotage
85         # ----------------------
86         self.setParameters(Parameters)
87         #
88         if self._parameters.has_key("Bounds") and (type(self._parameters["Bounds"]) is type([]) or type(self._parameters["Bounds"]) is type(())) and (len(self._parameters["Bounds"]) > 0):
89             Bounds = self._parameters["Bounds"]
90             logging.debug("%s Prise en compte des bornes effectuee"%(self._name,))
91         else:
92             Bounds = None
93         #
94         # Opérateur d'observation
95         # -----------------------
96         Hm = H["Direct"].appliedTo
97         Ha = H["Adjoint"].appliedInXTo
98         #
99         # Utilisation éventuelle d'un vecteur H(Xb) précalculé
100         # ----------------------------------------------------
101         if H["AppliedToX"] is not None and H["AppliedToX"].has_key("HXb"):
102             logging.debug("%s Utilisation de HXb"%self._name)
103             HXb = H["AppliedToX"]["HXb"]
104         else:
105             logging.debug("%s Calcul de Hm(Xb)"%self._name)
106             HXb = Hm( Xb )
107         HXb = numpy.asmatrix(HXb).flatten().T
108         #
109         # Calcul de l'innovation
110         # ----------------------
111         if Y.size != HXb.size:
112             raise ValueError("The size %i of observations Y and %i of observed calculation H(X) are different, they have to be identical."%(Y.size,HXb.size))
113         if max(Y.shape) != max(HXb.shape):
114             raise ValueError("The shapes %s of observations Y and %s of observed calculation H(X) are different, they have to be identical."%(Y.shape,HXb.shape))
115         d  = Y - HXb
116         logging.debug("%s Innovation d = %s"%(self._name, d))
117         #
118         # Précalcul des inversions de B et R
119         # ----------------------------------
120         # if B is not None:
121         #     BI = B.I
122         # elif self._parameters["B_scalar"] is not None:
123         #     BI = 1.0 / self._parameters["B_scalar"]
124         # else:
125         #     raise ValueError("Background error covariance matrix has to be properly defined!")
126         #
127         if R is not None:
128             RI = R.I
129         elif self._parameters["R_scalar"] is not None:
130             RI = 1.0 / self._parameters["R_scalar"]
131         else:
132             raise ValueError("Observation error covariance matrix has to be properly defined!")
133         #
134         # Définition de la fonction-coût
135         # ------------------------------
136         def CostFunction(x):
137             _X  = numpy.asmatrix(x).flatten().T
138             logging.debug("%s CostFunction X  = %s"%(self._name, numpy.asmatrix( _X ).flatten()))
139             _HX = Hm( _X )
140             _HX = numpy.asmatrix(_HX).flatten().T
141             Jb  = 0.
142             Jo  = 0.5 * (Y - _HX).T * RI * (Y - _HX)
143             J   = float( Jb ) + float( Jo )
144             logging.debug("%s CostFunction Jb = %s"%(self._name, Jb))
145             logging.debug("%s CostFunction Jo = %s"%(self._name, Jo))
146             logging.debug("%s CostFunction J  = %s"%(self._name, J))
147             self.StoredVariables["CurrentState"].store( _X.A1 )
148             self.StoredVariables["CostFunctionJb"].store( Jb )
149             self.StoredVariables["CostFunctionJo"].store( Jo )
150             self.StoredVariables["CostFunctionJ" ].store( J )
151             return float( J )
152         #
153         def GradientOfCostFunction(x):
154             _X      = numpy.asmatrix(x).flatten().T
155             logging.debug("%s GradientOfCostFunction X      = %s"%(self._name, numpy.asmatrix( _X ).flatten()))
156             _HX     = Hm( _X )
157             _HX     = numpy.asmatrix(_HX).flatten().T
158             GradJb  = 0.
159             GradJo  = - Ha( (_X, RI * (Y - _HX)) )
160             GradJ   = numpy.asmatrix( GradJb ).flatten().T + numpy.asmatrix( GradJo ).flatten().T
161             logging.debug("%s GradientOfCostFunction GradJb = %s"%(self._name, numpy.asmatrix( GradJb ).flatten()))
162             logging.debug("%s GradientOfCostFunction GradJo = %s"%(self._name, numpy.asmatrix( GradJo ).flatten()))
163             logging.debug("%s GradientOfCostFunction GradJ  = %s"%(self._name, numpy.asmatrix( GradJ  ).flatten()))
164             return GradJ.A1
165         #
166         # Point de démarrage de l'optimisation : Xini = Xb
167         # ------------------------------------
168         if type(Xb) is type(numpy.matrix([])):
169             Xini = Xb.A1.tolist()
170         else:
171             Xini = list(Xb)
172         logging.debug("%s Point de démarrage Xini = %s"%(self._name, Xini))
173         #
174         # Minimisation de la fonctionnelle
175         # --------------------------------
176         if self._parameters["Minimizer"] == "LBFGSB":
177             Minimum, J_optimal, Informations = scipy.optimize.fmin_l_bfgs_b(
178                 func        = CostFunction,
179                 x0          = Xini,
180                 fprime      = GradientOfCostFunction,
181                 args        = (),
182                 bounds      = Bounds,
183                 maxfun      = self._parameters["MaximumNumberOfSteps"]-1,
184                 factr       = self._parameters["CostDecrementTolerance"]*1.e14,
185                 pgtol       = self._parameters["ProjectedGradientTolerance"],
186                 iprint      = iprint,
187                 )
188             nfeval = Informations['funcalls']
189             rc     = Informations['warnflag']
190         elif self._parameters["Minimizer"] == "TNC":
191             Minimum, nfeval, rc = scipy.optimize.fmin_tnc(
192                 func        = CostFunction,
193                 x0          = Xini,
194                 fprime      = GradientOfCostFunction,
195                 args        = (),
196                 bounds      = Bounds,
197                 maxfun      = self._parameters["MaximumNumberOfSteps"],
198                 pgtol       = self._parameters["ProjectedGradientTolerance"],
199                 ftol        = self._parameters["CostDecrementTolerance"],
200                 messages    = message,
201                 )
202         elif self._parameters["Minimizer"] == "CG":
203             Minimum, fopt, nfeval, grad_calls, rc = scipy.optimize.fmin_cg(
204                 f           = CostFunction,
205                 x0          = Xini,
206                 fprime      = GradientOfCostFunction,
207                 args        = (),
208                 maxiter     = self._parameters["MaximumNumberOfSteps"],
209                 gtol        = self._parameters["GradientNormTolerance"],
210                 disp        = disp,
211                 full_output = True,
212                 )
213         elif self._parameters["Minimizer"] == "NCG":
214             Minimum, fopt, nfeval, grad_calls, hcalls, rc = scipy.optimize.fmin_ncg(
215                 f           = CostFunction,
216                 x0          = Xini,
217                 fprime      = GradientOfCostFunction,
218                 args        = (),
219                 maxiter     = self._parameters["MaximumNumberOfSteps"],
220                 avextol     = self._parameters["CostDecrementTolerance"],
221                 disp        = disp,
222                 full_output = True,
223                 )
224         elif self._parameters["Minimizer"] == "BFGS":
225             Minimum, fopt, gopt, Hopt, nfeval, grad_calls, rc = scipy.optimize.fmin_bfgs(
226                 f           = CostFunction,
227                 x0          = Xini,
228                 fprime      = GradientOfCostFunction,
229                 args        = (),
230                 maxiter     = self._parameters["MaximumNumberOfSteps"],
231                 gtol        = self._parameters["GradientNormTolerance"],
232                 disp        = disp,
233                 full_output = True,
234                 )
235         else:
236             raise ValueError("Error in Minimizer name: %s"%self._parameters["Minimizer"])
237         #
238         # Correction pour pallier a un bug de TNC sur le retour du Minimum
239         # ----------------------------------------------------------------
240         StepMin = numpy.argmin( self.StoredVariables["CostFunctionJ"].valueserie() )
241         MinJ    = self.StoredVariables["CostFunctionJ"].valueserie(step = StepMin)
242         Minimum = self.StoredVariables["CurrentState"].valueserie(step = StepMin)
243         #
244         logging.debug("%s %s Step of min cost  = %s"%(self._name, self._parameters["Minimizer"], StepMin))
245         logging.debug("%s %s Minimum cost      = %s"%(self._name, self._parameters["Minimizer"], MinJ))
246         logging.debug("%s %s Minimum state     = %s"%(self._name, self._parameters["Minimizer"], Minimum))
247         logging.debug("%s %s Nb of F           = %s"%(self._name, self._parameters["Minimizer"], nfeval))
248         logging.debug("%s %s RetCode           = %s"%(self._name, self._parameters["Minimizer"], rc))
249         #
250         # Obtention de l'analyse
251         # ----------------------
252         Xa = numpy.asmatrix(Minimum).T
253         logging.debug("%s Analyse Xa = %s"%(self._name, Xa))
254         #
255         self.StoredVariables["Analysis"].store( Xa.A1 )
256         self.StoredVariables["Innovation"].store( d.A1 )
257         #
258         logging.debug("%s Taille mémoire utilisée de %.1f Mo"%(self._name, m.getUsedMemory("MB")))
259         logging.debug("%s Terminé"%self._name)
260         #
261         return 0
262
263 # ==============================================================================
264 if __name__ == "__main__":
265     print '\n AUTODIAGNOSTIC \n'