Salome HOME
fda2bc0ed98527162da1983691d09b15b6b794c5
[modules/adao.git] / src / daComposant / daAlgorithms / LinearityTest.py
1 # -*- coding: utf-8 -*-
2 #
3 # Copyright (C) 2008-2022 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 import math, numpy
24 from daCore import BasicObjects, PlatformInfo
25 mpr = PlatformInfo.PlatformInfo().MachinePrecision()
26
27 # ==============================================================================
28 class ElementaryAlgorithm(BasicObjects.Algorithm):
29     def __init__(self):
30         BasicObjects.Algorithm.__init__(self, "LINEARITYTEST")
31         self.defineRequiredParameter(
32             name     = "ResiduFormula",
33             default  = "CenteredDL",
34             typecast = str,
35             message  = "Formule de résidu utilisée",
36             listval  = ["CenteredDL", "Taylor", "NominalTaylor", "NominalTaylorRMS"],
37             )
38         self.defineRequiredParameter(
39             name     = "EpsilonMinimumExponent",
40             default  = -8,
41             typecast = int,
42             message  = "Exposant minimal en puissance de 10 pour le multiplicateur d'incrément",
43             minval   = -20,
44             maxval   = 0,
45             )
46         self.defineRequiredParameter(
47             name     = "InitialDirection",
48             default  = [],
49             typecast = list,
50             message  = "Direction initiale de la dérivée directionnelle autour du point nominal",
51             )
52         self.defineRequiredParameter(
53             name     = "AmplitudeOfInitialDirection",
54             default  = 1.,
55             typecast = float,
56             message  = "Amplitude de la direction initiale de la dérivée directionnelle autour du point nominal",
57             )
58         self.defineRequiredParameter(
59             name     = "AmplitudeOfTangentPerturbation",
60             default  = 1.e-2,
61             typecast = float,
62             message  = "Amplitude de la perturbation pour le calcul de la forme tangente",
63             minval   = 1.e-10,
64             maxval   = 1.,
65             )
66         self.defineRequiredParameter(
67             name     = "SetSeed",
68             typecast = numpy.random.seed,
69             message  = "Graine fixée pour le générateur aléatoire",
70             )
71         self.defineRequiredParameter(
72             name     = "ResultTitle",
73             default  = "",
74             typecast = str,
75             message  = "Titre du tableau et de la figure",
76             )
77         self.defineRequiredParameter(
78             name     = "StoreSupplementaryCalculations",
79             default  = [],
80             typecast = tuple,
81             message  = "Liste de calculs supplémentaires à stocker et/ou effectuer",
82             listval  = [
83                 "CurrentState",
84                 "Residu",
85                 "SimulatedObservationAtCurrentState",
86                 ]
87             )
88         self.requireInputArguments(
89             mandatory= ("Xb", "HO"),
90             )
91         self.setAttributes(tags=(
92             "Checking",
93             ))
94
95     def run(self, Xb=None, Y=None, U=None, HO=None, EM=None, CM=None, R=None, B=None, Q=None, Parameters=None):
96         self._pre_run(Parameters, Xb, Y, U, HO, EM, CM, R, B, Q)
97         #
98         def RMS(V1, V2):
99             import math
100             return math.sqrt( ((numpy.ravel(V2) - numpy.ravel(V1))**2).sum() / float(numpy.ravel(V1).size) )
101         #
102         # Operateurs
103         # ----------
104         Hm = HO["Direct"].appliedTo
105         if self._parameters["ResiduFormula"] in ["Taylor", "NominalTaylor", "NominalTaylorRMS"]:
106             Ht = HO["Tangent"].appliedInXTo
107         #
108         # Construction des perturbations
109         # ------------------------------
110         Perturbations = [ 10**i for i in range(self._parameters["EpsilonMinimumExponent"],1) ]
111         Perturbations.reverse()
112         #
113         # Calcul du point courant
114         # -----------------------
115         Xn      = numpy.ravel(     Xb   ).reshape((-1,1))
116         FX      = numpy.ravel( Hm( Xn ) ).reshape((-1,1))
117         NormeX  = numpy.linalg.norm( Xn )
118         NormeFX = numpy.linalg.norm( FX )
119         if self._toStore("CurrentState"):
120             self.StoredVariables["CurrentState"].store( numpy.ravel(Xn) )
121         if self._toStore("SimulatedObservationAtCurrentState"):
122             self.StoredVariables["SimulatedObservationAtCurrentState"].store( numpy.ravel(FX) )
123         #
124         # Fabrication de la direction de l'increment dX
125         # ---------------------------------------------
126         if len(self._parameters["InitialDirection"]) == 0:
127             dX0 = []
128             for v in Xn:
129                 if abs(v) > 1.e-8:
130                     dX0.append( numpy.random.normal(0.,abs(v)) )
131                 else:
132                     dX0.append( numpy.random.normal(0.,Xn.mean()) )
133         else:
134             dX0 = numpy.ravel( self._parameters["InitialDirection"] )
135         #
136         dX0 = float(self._parameters["AmplitudeOfInitialDirection"]) * numpy.ravel( dX0 ).reshape((-1,1))
137         #
138         # Calcul du gradient au point courant X pour l'increment dX
139         # ---------------------------------------------------------
140         if self._parameters["ResiduFormula"] in ["Taylor", "NominalTaylor", "NominalTaylorRMS"]:
141             dX1      = float(self._parameters["AmplitudeOfTangentPerturbation"]) * dX0
142             GradFxdX = Ht( (Xn, dX1) )
143             GradFxdX = numpy.ravel( GradFxdX ).reshape((-1,1))
144             GradFxdX = float(1./self._parameters["AmplitudeOfTangentPerturbation"]) * GradFxdX
145         #
146         # Entete des resultats
147         # --------------------
148         __marge =  12*u" "
149         __precision = u"""
150             Remarque : les nombres inferieurs a %.0e (environ) representent un zero
151                        a la precision machine.\n"""%mpr
152         if self._parameters["ResiduFormula"] == "CenteredDL":
153             __entete = u"  i   Alpha     ||X||      ||F(X)||   |   R(Alpha)  log10( R )"
154             __msgdoc = u"""
155             On observe le residu provenant de la difference centree des valeurs de F
156             au point nominal et aux points perturbes, normalisee par la valeur au
157             point nominal :
158
159                          || F(X+Alpha*dX) + F(X-Alpha*dX) - 2*F(X) ||
160               R(Alpha) = --------------------------------------------
161                                          || F(X) ||
162
163             S'il reste constamment tres faible par rapport a 1, l'hypothese de linearite
164             de F est verifiee.
165
166             Si le residu varie, ou qu'il est de l'ordre de 1 ou plus, et qu'il n'est
167             faible qu'a partir d'un certain ordre d'increment, l'hypothese de linearite
168             de F n'est pas verifiee.
169
170             Si le residu decroit et que la decroissance se fait en Alpha**2 selon Alpha,
171             cela signifie que le gradient est calculable jusqu'a la precision d'arret
172             de la decroissance quadratique.
173
174             On prend dX0 = Normal(0,X) et dX = Alpha*dX0. F est le code de calcul.\n""" + __precision
175         if self._parameters["ResiduFormula"] == "Taylor":
176             __entete = u"  i   Alpha     ||X||      ||F(X)||   |   R(Alpha)  log10( R )"
177             __msgdoc = u"""
178             On observe le residu issu du developpement de Taylor de la fonction F,
179             normalisee par la valeur au point nominal :
180
181                          || F(X+Alpha*dX) - F(X) - Alpha * GradientF_X(dX) ||
182               R(Alpha) = ----------------------------------------------------
183                                          || F(X) ||
184
185             S'il reste constamment tres faible par rapport a 1, l'hypothese de linearite
186             de F est verifiee.
187
188             Si le residu varie, ou qu'il est de l'ordre de 1 ou plus, et qu'il n'est
189             faible qu'a partir d'un certain ordre d'increment, l'hypothese de linearite
190             de F n'est pas verifiee.
191
192             Si le residu decroit et que la decroissance se fait en Alpha**2 selon Alpha,
193             cela signifie que le gradient est bien calcule jusqu'a la precision d'arret
194             de la decroissance quadratique.
195
196             On prend dX0 = Normal(0,X) et dX = Alpha*dX0. F est le code de calcul.\n""" + __precision
197         if self._parameters["ResiduFormula"] == "NominalTaylor":
198             __entete = u"  i   Alpha     ||X||      ||F(X)||   |   R(Alpha)   |R-1| en %"
199             __msgdoc = u"""
200             On observe le residu obtenu a partir de deux approximations d'ordre 1 de F(X),
201             normalisees par la valeur au point nominal :
202
203               R(Alpha) = max(
204                 || F(X+Alpha*dX) - Alpha * F(dX) || / || F(X) ||,
205                 || F(X-Alpha*dX) + Alpha * F(dX) || / || F(X) ||,
206               )
207
208             S'il reste constamment egal a 1 a moins de 2 ou 3 pourcents pres (c'est-a-dire
209             que |R-1| reste egal a 2 ou 3 pourcents), c'est que l'hypothese de linearite
210             de F est verifiee.
211
212             S'il est egal a 1 sur une partie seulement du domaine de variation de
213             l'increment Alpha, c'est sur cette partie que l'hypothese de linearite de F
214             est verifiee.
215
216             On prend dX0 = Normal(0,X) et dX = Alpha*dX0. F est le code de calcul.\n""" + __precision
217         if self._parameters["ResiduFormula"] == "NominalTaylorRMS":
218             __entete = u"  i   Alpha     ||X||      ||F(X)||   |   R(Alpha)    |R| en %"
219             __msgdoc = u"""
220             On observe le residu obtenu a partir de deux approximations d'ordre 1 de F(X),
221             normalisees par la valeur au point nominal :
222
223               R(Alpha) = max(
224                 RMS( F(X), F(X+Alpha*dX) - Alpha * F(dX) ) / || F(X) ||,
225                 RMS( F(X), F(X-Alpha*dX) + Alpha * F(dX) ) / || F(X) ||,
226               )
227
228             S'il reste constamment egal a 0 a moins de 1 ou 2 pourcents pres, c'est
229             que l'hypothese de linearite de F est verifiee.
230
231             S'il est egal a 0 sur une partie seulement du domaine de variation de
232             l'increment Alpha, c'est sur cette partie que l'hypothese de linearite de F
233             est verifiee.
234
235             On prend dX0 = Normal(0,X) et dX = Alpha*dX0. F est le code de calcul.\n""" + __precision
236         #
237         if len(self._parameters["ResultTitle"]) > 0:
238             __rt = str(self._parameters["ResultTitle"])
239             msgs  = u"\n"
240             msgs += __marge + "====" + "="*len(__rt) + "====\n"
241             msgs += __marge + "    " + __rt + "\n"
242             msgs += __marge + "====" + "="*len(__rt) + "====\n"
243         else:
244             msgs  = u""
245         msgs += __msgdoc
246         #
247         __nbtirets = len(__entete) + 2
248         msgs += "\n" + __marge + "-"*__nbtirets
249         msgs += "\n" + __marge + __entete
250         msgs += "\n" + __marge + "-"*__nbtirets
251         #
252         # Boucle sur les perturbations
253         # ----------------------------
254         for i,amplitude in enumerate(Perturbations):
255             dX      = amplitude * dX0
256             #
257             if self._parameters["ResiduFormula"] == "CenteredDL":
258                 if self._toStore("CurrentState"):
259                     self.StoredVariables["CurrentState"].store( Xn + dX )
260                     self.StoredVariables["CurrentState"].store( Xn - dX )
261                 #
262                 FX_plus_dX  = numpy.ravel( Hm( Xn + dX ) ).reshape((-1,1))
263                 FX_moins_dX = numpy.ravel( Hm( Xn - dX ) ).reshape((-1,1))
264                 #
265                 if self._toStore("SimulatedObservationAtCurrentState"):
266                     self.StoredVariables["SimulatedObservationAtCurrentState"].store( FX_plus_dX )
267                     self.StoredVariables["SimulatedObservationAtCurrentState"].store( FX_moins_dX )
268                 #
269                 Residu = numpy.linalg.norm( FX_plus_dX + FX_moins_dX - 2 * FX ) / NormeFX
270                 #
271                 self.StoredVariables["Residu"].store( Residu )
272                 msg = "  %2i  %5.0e   %9.3e   %9.3e   |   %9.3e   %4.0f"%(i,amplitude,NormeX,NormeFX,Residu,math.log10(max(1.e-99,Residu)))
273                 msgs += "\n" + __marge + msg
274             #
275             if self._parameters["ResiduFormula"] == "Taylor":
276                 if self._toStore("CurrentState"):
277                     self.StoredVariables["CurrentState"].store( Xn + dX )
278                 #
279                 FX_plus_dX  = numpy.ravel( Hm( Xn + dX ) ).reshape((-1,1))
280                 #
281                 if self._toStore("SimulatedObservationAtCurrentState"):
282                     self.StoredVariables["SimulatedObservationAtCurrentState"].store( FX_plus_dX )
283                 #
284                 Residu = numpy.linalg.norm( FX_plus_dX - FX - amplitude * GradFxdX ) / NormeFX
285                 #
286                 self.StoredVariables["Residu"].store( Residu )
287                 msg = "  %2i  %5.0e   %9.3e   %9.3e   |   %9.3e   %4.0f"%(i,amplitude,NormeX,NormeFX,Residu,math.log10(max(1.e-99,Residu)))
288                 msgs += "\n" + __marge + msg
289             #
290             if self._parameters["ResiduFormula"] == "NominalTaylor":
291                 if self._toStore("CurrentState"):
292                     self.StoredVariables["CurrentState"].store( Xn + dX )
293                     self.StoredVariables["CurrentState"].store( Xn - dX )
294                     self.StoredVariables["CurrentState"].store( dX )
295                 #
296                 FX_plus_dX  = numpy.ravel( Hm( Xn + dX ) ).reshape((-1,1))
297                 FX_moins_dX = numpy.ravel( Hm( Xn - dX ) ).reshape((-1,1))
298                 FdX         = numpy.ravel( Hm( dX )      ).reshape((-1,1))
299                 #
300                 if self._toStore("SimulatedObservationAtCurrentState"):
301                     self.StoredVariables["SimulatedObservationAtCurrentState"].store( FX_plus_dX )
302                     self.StoredVariables["SimulatedObservationAtCurrentState"].store( FX_moins_dX )
303                     self.StoredVariables["SimulatedObservationAtCurrentState"].store( FdX )
304                 #
305                 Residu = max(
306                     numpy.linalg.norm( FX_plus_dX  - amplitude * FdX ) / NormeFX,
307                     numpy.linalg.norm( FX_moins_dX + amplitude * FdX ) / NormeFX,
308                     )
309                 #
310                 self.StoredVariables["Residu"].store( Residu )
311                 msg = "  %2i  %5.0e   %9.3e   %9.3e   |   %9.3e   %5i %s"%(i,amplitude,NormeX,NormeFX,Residu,100.*abs(Residu-1.),"%")
312                 msgs += "\n" + __marge + msg
313             #
314             if self._parameters["ResiduFormula"] == "NominalTaylorRMS":
315                 if self._toStore("CurrentState"):
316                     self.StoredVariables["CurrentState"].store( Xn + dX )
317                     self.StoredVariables["CurrentState"].store( Xn - dX )
318                     self.StoredVariables["CurrentState"].store( dX )
319                 #
320                 FX_plus_dX  = numpy.ravel( Hm( Xn + dX ) ).reshape((-1,1))
321                 FX_moins_dX = numpy.ravel( Hm( Xn - dX ) ).reshape((-1,1))
322                 FdX         = numpy.ravel( Hm( dX )      ).reshape((-1,1))
323                 #
324                 if self._toStore("SimulatedObservationAtCurrentState"):
325                     self.StoredVariables["SimulatedObservationAtCurrentState"].store( FX_plus_dX )
326                     self.StoredVariables["SimulatedObservationAtCurrentState"].store( FX_moins_dX )
327                     self.StoredVariables["SimulatedObservationAtCurrentState"].store( FdX )
328                 #
329                 Residu = max(
330                     RMS( FX, FX_plus_dX   - amplitude * FdX ) / NormeFX,
331                     RMS( FX, FX_moins_dX  + amplitude * FdX ) / NormeFX,
332                     )
333                 #
334                 self.StoredVariables["Residu"].store( Residu )
335                 msg = "  %2i  %5.0e   %9.3e   %9.3e   |   %9.3e   %5i %s"%(i,amplitude,NormeX,NormeFX,Residu,100.*Residu,"%")
336                 msgs += "\n" + __marge + msg
337         #
338         msgs += "\n" + __marge + "-"*__nbtirets
339         msgs += "\n"
340         #
341         # Sorties eventuelles
342         # -------------------
343         print("\nResults of linearity check by \"%s\" formula:"%self._parameters["ResiduFormula"])
344         print(msgs)
345         #
346         self._post_run(HO)
347         return 0
348
349 # ==============================================================================
350 if __name__ == "__main__":
351     print('\n AUTODIAGNOSTIC\n')