Salome HOME
Adding a flag to avoid storing internal variables
[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         self.defineRequiredParameter(
76             name     = "StoreInternalVariables",
77             default  = False,
78             typecast = bool,
79             message  = "Stockage des variables internes ou intermédiaires du calcul",
80             )
81
82     def run(self, Xb=None, Y=None, H=None, M=None, R=None, B=None, Q=None, Parameters=None):
83         """
84         Calcul de l'estimateur moindres carrés pondérés non linéaires
85         (assimilation variationnelle sans ébauche)
86         """
87         logging.debug("%s Lancement"%self._name)
88         logging.debug("%s Taille mémoire utilisée de %.1f Mo"%(self._name, m.getUsedMemory("Mo")))
89         #
90         # Paramètres de pilotage
91         # ----------------------
92         self.setParameters(Parameters)
93         #
94         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):
95             Bounds = self._parameters["Bounds"]
96             logging.debug("%s Prise en compte des bornes effectuee"%(self._name,))
97         else:
98             Bounds = None
99         #
100         # Correction pour pallier a un bug de TNC sur le retour du Minimum
101         if self._parameters.has_key("Minimizer") is "TNC":
102             self.setParameterValue("StoreInternalVariables",True)
103         #
104         # Opérateur d'observation
105         # -----------------------
106         Hm = H["Direct"].appliedTo
107         Ha = H["Adjoint"].appliedInXTo
108         #
109         # Utilisation éventuelle d'un vecteur H(Xb) précalculé
110         # ----------------------------------------------------
111         if H["AppliedToX"] is not None and H["AppliedToX"].has_key("HXb"):
112             logging.debug("%s Utilisation de HXb"%self._name)
113             HXb = H["AppliedToX"]["HXb"]
114         else:
115             logging.debug("%s Calcul de Hm(Xb)"%self._name)
116             HXb = Hm( Xb )
117         HXb = numpy.asmatrix(HXb).flatten().T
118         #
119         # Calcul de l'innovation
120         # ----------------------
121         if Y.size != HXb.size:
122             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))
123         if max(Y.shape) != max(HXb.shape):
124             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))
125         d  = Y - HXb
126         logging.debug("%s Innovation d = %s"%(self._name, d))
127         #
128         # Précalcul des inversions de B et R
129         # ----------------------------------
130         # if B is not None:
131         #     BI = B.I
132         # elif self._parameters["B_scalar"] is not None:
133         #     BI = 1.0 / self._parameters["B_scalar"]
134         # else:
135         #     raise ValueError("Background error covariance matrix has to be properly defined!")
136         #
137         if R is not None:
138             RI = R.I
139         elif self._parameters["R_scalar"] is not None:
140             RI = 1.0 / self._parameters["R_scalar"]
141         else:
142             raise ValueError("Observation error covariance matrix has to be properly defined!")
143         #
144         # Définition de la fonction-coût
145         # ------------------------------
146         def CostFunction(x):
147             _X  = numpy.asmatrix(x).flatten().T
148             logging.debug("%s CostFunction X  = %s"%(self._name, numpy.asmatrix( _X ).flatten()))
149             _HX = Hm( _X )
150             _HX = numpy.asmatrix(_HX).flatten().T
151             Jb  = 0.
152             Jo  = 0.5 * (Y - _HX).T * RI * (Y - _HX)
153             J   = float( Jb ) + float( Jo )
154             logging.debug("%s CostFunction Jb = %s"%(self._name, Jb))
155             logging.debug("%s CostFunction Jo = %s"%(self._name, Jo))
156             logging.debug("%s CostFunction J  = %s"%(self._name, J))
157             if self._parameters["StoreInternalVariables"]:
158                 self.StoredVariables["CurrentState"].store( _X.A1 )
159             self.StoredVariables["CostFunctionJb"].store( Jb )
160             self.StoredVariables["CostFunctionJo"].store( Jo )
161             self.StoredVariables["CostFunctionJ" ].store( J )
162             return float( J )
163         #
164         def GradientOfCostFunction(x):
165             _X      = numpy.asmatrix(x).flatten().T
166             logging.debug("%s GradientOfCostFunction X      = %s"%(self._name, numpy.asmatrix( _X ).flatten()))
167             _HX     = Hm( _X )
168             _HX     = numpy.asmatrix(_HX).flatten().T
169             GradJb  = 0.
170             GradJo  = - Ha( (_X, RI * (Y - _HX)) )
171             GradJ   = numpy.asmatrix( GradJb ).flatten().T + numpy.asmatrix( GradJo ).flatten().T
172             logging.debug("%s GradientOfCostFunction GradJb = %s"%(self._name, numpy.asmatrix( GradJb ).flatten()))
173             logging.debug("%s GradientOfCostFunction GradJo = %s"%(self._name, numpy.asmatrix( GradJo ).flatten()))
174             logging.debug("%s GradientOfCostFunction GradJ  = %s"%(self._name, numpy.asmatrix( GradJ  ).flatten()))
175             return GradJ.A1
176         #
177         # Point de démarrage de l'optimisation : Xini = Xb
178         # ------------------------------------
179         if type(Xb) is type(numpy.matrix([])):
180             Xini = Xb.A1.tolist()
181         else:
182             Xini = list(Xb)
183         logging.debug("%s Point de démarrage Xini = %s"%(self._name, Xini))
184         #
185         # Minimisation de la fonctionnelle
186         # --------------------------------
187         if self._parameters["Minimizer"] == "LBFGSB":
188             Minimum, J_optimal, Informations = scipy.optimize.fmin_l_bfgs_b(
189                 func        = CostFunction,
190                 x0          = Xini,
191                 fprime      = GradientOfCostFunction,
192                 args        = (),
193                 bounds      = Bounds,
194                 maxfun      = self._parameters["MaximumNumberOfSteps"]-1,
195                 factr       = self._parameters["CostDecrementTolerance"]*1.e14,
196                 pgtol       = self._parameters["ProjectedGradientTolerance"],
197                 iprint      = iprint,
198                 )
199             nfeval = Informations['funcalls']
200             rc     = Informations['warnflag']
201         elif self._parameters["Minimizer"] == "TNC":
202             Minimum, nfeval, rc = scipy.optimize.fmin_tnc(
203                 func        = CostFunction,
204                 x0          = Xini,
205                 fprime      = GradientOfCostFunction,
206                 args        = (),
207                 bounds      = Bounds,
208                 maxfun      = self._parameters["MaximumNumberOfSteps"],
209                 pgtol       = self._parameters["ProjectedGradientTolerance"],
210                 ftol        = self._parameters["CostDecrementTolerance"],
211                 messages    = message,
212                 )
213         elif self._parameters["Minimizer"] == "CG":
214             Minimum, fopt, nfeval, grad_calls, rc = scipy.optimize.fmin_cg(
215                 f           = CostFunction,
216                 x0          = Xini,
217                 fprime      = GradientOfCostFunction,
218                 args        = (),
219                 maxiter     = self._parameters["MaximumNumberOfSteps"],
220                 gtol        = self._parameters["GradientNormTolerance"],
221                 disp        = disp,
222                 full_output = True,
223                 )
224         elif self._parameters["Minimizer"] == "NCG":
225             Minimum, fopt, nfeval, grad_calls, hcalls, rc = scipy.optimize.fmin_ncg(
226                 f           = CostFunction,
227                 x0          = Xini,
228                 fprime      = GradientOfCostFunction,
229                 args        = (),
230                 maxiter     = self._parameters["MaximumNumberOfSteps"],
231                 avextol     = self._parameters["CostDecrementTolerance"],
232                 disp        = disp,
233                 full_output = True,
234                 )
235         elif self._parameters["Minimizer"] == "BFGS":
236             Minimum, fopt, gopt, Hopt, nfeval, grad_calls, rc = scipy.optimize.fmin_bfgs(
237                 f           = CostFunction,
238                 x0          = Xini,
239                 fprime      = GradientOfCostFunction,
240                 args        = (),
241                 maxiter     = self._parameters["MaximumNumberOfSteps"],
242                 gtol        = self._parameters["GradientNormTolerance"],
243                 disp        = disp,
244                 full_output = True,
245                 )
246         else:
247             raise ValueError("Error in Minimizer name: %s"%self._parameters["Minimizer"])
248         #
249         StepMin = numpy.argmin( self.StoredVariables["CostFunctionJ"].valueserie() )
250         MinJ    = self.StoredVariables["CostFunctionJ"].valueserie(step = StepMin)
251         #
252         # Correction pour pallier a un bug de TNC sur le retour du Minimum
253         # ----------------------------------------------------------------
254         if self._parameters["StoreInternalVariables"]:
255             Minimum = self.StoredVariables["CurrentState"].valueserie(step = StepMin)
256         #
257         logging.debug("%s %s Step of min cost  = %s"%(self._name, self._parameters["Minimizer"], StepMin))
258         logging.debug("%s %s Minimum cost      = %s"%(self._name, self._parameters["Minimizer"], MinJ))
259         logging.debug("%s %s Minimum state     = %s"%(self._name, self._parameters["Minimizer"], Minimum))
260         logging.debug("%s %s Nb of F           = %s"%(self._name, self._parameters["Minimizer"], nfeval))
261         logging.debug("%s %s RetCode           = %s"%(self._name, self._parameters["Minimizer"], rc))
262         #
263         # Obtention de l'analyse
264         # ----------------------
265         Xa = numpy.asmatrix(Minimum).T
266         logging.debug("%s Analyse Xa = %s"%(self._name, Xa))
267         #
268         self.StoredVariables["Analysis"].store( Xa.A1 )
269         self.StoredVariables["Innovation"].store( d.A1 )
270         #
271         logging.debug("%s Taille mémoire utilisée de %.1f Mo"%(self._name, m.getUsedMemory("MB")))
272         logging.debug("%s Terminé"%self._name)
273         #
274         return 0
275
276 # ==============================================================================
277 if __name__ == "__main__":
278     print '\n AUTODIAGNOSTIC \n'