Salome HOME
Documentation update with features and review corrections
[modules/adao.git] / src / daComposant / daAlgorithms / AdjointTest.py
1 # -*- coding: utf-8 -*-
2 #
3 # Copyright (C) 2008-2024 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 numpy
24 from daCore import BasicObjects, NumericObjects
25 from daCore.PlatformInfo import PlatformInfo, vfloat
26 mpr = PlatformInfo().MachinePrecision()
27 mfp = PlatformInfo().MaximumPrecision()
28
29 # ==============================================================================
30 class ElementaryAlgorithm(BasicObjects.Algorithm):
31     def __init__(self):
32         BasicObjects.Algorithm.__init__(self, "ADJOINTTEST")
33         self.defineRequiredParameter(
34             name     = "ResiduFormula",
35             default  = "ScalarProduct",
36             typecast = str,
37             message  = "Formule de résidu utilisée",
38             listval  = ["ScalarProduct"],
39         )
40         self.defineRequiredParameter(
41             name     = "AmplitudeOfInitialDirection",
42             default  = 1.,
43             typecast = float,
44             message  = "Amplitude de la direction initiale de la dérivée directionnelle autour du point nominal",
45         )
46         self.defineRequiredParameter(
47             name     = "EpsilonMinimumExponent",
48             default  = -8,
49             typecast = int,
50             message  = "Exposant minimal en puissance de 10 pour le multiplicateur d'incrément",
51             minval   = -20,
52             maxval   = 0,
53         )
54         self.defineRequiredParameter(
55             name     = "InitialDirection",
56             default  = [],
57             typecast = list,
58             message  = "Direction initiale de la dérivée directionnelle autour du point nominal",
59         )
60         self.defineRequiredParameter(
61             name     = "NumberOfPrintedDigits",
62             default  = 5,
63             typecast = int,
64             message  = "Nombre de chiffres affichés pour les impressions de réels",
65             minval   = 0,
66         )
67         self.defineRequiredParameter(
68             name     = "ResultTitle",
69             default  = "",
70             typecast = str,
71             message  = "Titre du tableau et de la figure",
72         )
73         self.defineRequiredParameter(
74             name     = "SetSeed",
75             typecast = numpy.random.seed,
76             message  = "Graine fixée pour le générateur aléatoire",
77         )
78         self.defineRequiredParameter(
79             name     = "StoreSupplementaryCalculations",
80             default  = [],
81             typecast = tuple,
82             message  = "Liste de calculs supplémentaires à stocker et/ou effectuer",
83             listval  = [
84                 "CurrentState",
85                 "Residu",
86                 "SimulatedObservationAtCurrentState",
87             ]
88         )
89         self.requireInputArguments(
90             mandatory= ("Xb", "HO"),
91             optional = ("Y", ),
92         )
93         self.setAttributes(
94             tags=(
95                 "Checking",
96             ),
97             features=(
98                 "DerivativeNeeded",
99                 "ParallelDerivativesOnly",
100             ),
101         )
102
103     def run(self, Xb=None, Y=None, U=None, HO=None, EM=None, CM=None, R=None, B=None, Q=None, Parameters=None):
104         self._pre_run(Parameters, Xb, Y, U, HO, EM, CM, R, B, Q)
105         #
106         Hm = HO["Direct"].appliedTo
107         Ht = HO["Tangent"].appliedInXTo
108         Ha = HO["Adjoint"].appliedInXTo
109         #
110         X0      = numpy.ravel( Xb ).reshape((-1, 1))
111         #
112         # ----------
113         __p = self._parameters["NumberOfPrintedDigits"]
114         #
115         __marge = 5 * u" "
116         __flech = 3 * "=" + "> "
117         msgs  = ("\n")  # 1
118         if len(self._parameters["ResultTitle"]) > 0:
119             __rt = str(self._parameters["ResultTitle"])
120             msgs += (__marge + "====" + "=" * len(__rt) + "====\n")
121             msgs += (__marge + "    " + __rt + "\n")
122             msgs += (__marge + "====" + "=" * len(__rt) + "====\n")
123         else:
124             msgs += (__marge + "%s\n"%self._name)
125             msgs += (__marge + "%s\n"%("=" * len(self._name),))
126         #
127         msgs += ("\n")
128         msgs += (__marge + "This test allows to analyze the quality of an adjoint operator associated\n")
129         msgs += (__marge + "to some given direct operator F, applied to one single vector argument x.\n")
130         msgs += (__marge + "If the adjoint operator is approximated and not given, the test measures\n")
131         msgs += (__marge + "the quality of the automatic approximation, around an input checking point X.\n")
132         msgs += ("\n")
133         msgs += (__flech + "Information before launching:\n")
134         msgs += (__marge + "-----------------------------\n")
135         msgs += ("\n")
136         msgs += (__marge + "Characteristics of input vector X, internally converted:\n")
137         msgs += (__marge + "  Type...............: %s\n")%type( X0 )
138         msgs += (__marge + "  Length of vector...: %i\n")%max(numpy.ravel( X0 ).shape)
139         msgs += (__marge + "  Minimum value......: %." + str(__p) + "e\n")%numpy.min(  X0 )
140         msgs += (__marge + "  Maximum value......: %." + str(__p) + "e\n")%numpy.max(  X0 )
141         msgs += (__marge + "  Mean of vector.....: %." + str(__p) + "e\n")%numpy.mean( X0, dtype=mfp )
142         msgs += (__marge + "  Standard error.....: %." + str(__p) + "e\n")%numpy.std(  X0, dtype=mfp )
143         msgs += (__marge + "  L2 norm of vector..: %." + str(__p) + "e\n")%numpy.linalg.norm( X0 )
144         msgs += ("\n")
145         msgs += (__marge + "%s\n\n"%("-" * 75,))
146         msgs += (__flech + "Numerical quality indicators:\n")
147         msgs += (__marge + "-----------------------------\n")
148         msgs += ("\n")
149         #
150         if self._parameters["ResiduFormula"] == "ScalarProduct":
151             msgs += (__marge + "Using the \"%s\" formula, one observes the residue R which is the\n"%self._parameters["ResiduFormula"])  # noqa: E501
152             msgs += (__marge + "difference of two scalar products:\n")
153             msgs += ("\n")
154             msgs += (__marge + "    R(Alpha) = | < TangentF_X(dX) , Y > - < dX , AdjointF_X(Y) > |\n")
155             msgs += ("\n")
156             msgs += (__marge + "which must remain constantly equal to zero to the accuracy of the calculation.\n")
157             msgs += (__marge + "One takes dX0 = Normal(0,X) and dX = Alpha*dX0, where F is the calculation\n")
158             msgs += (__marge + "operator. If it is given, Y must be in the image of F. If it is not given,\n")
159             msgs += (__marge + "one takes Y = F(X).\n")
160             #
161             __entete = str.rstrip(
162                 "  i   Alpha  " + \
163                 str.center("||X||", 2 + __p + 7)  + \
164                 str.center("||Y||", 2 + __p + 7)  + \
165                 str.center("||dX||", 2 + __p + 7) + \
166                 str.center("R(Alpha)", 2 + __p + 7)
167             )
168             __nbtirets = len(__entete) + 2
169             #
170         msgs += ("\n")
171         msgs += (__marge + "(Remark: numbers that are (about) under %.0e represent 0 to machine precision)\n"%mpr)
172         print(msgs)  # 1
173         #
174         Perturbations = [ 10**i for i in range(self._parameters["EpsilonMinimumExponent"], 1) ]
175         Perturbations.reverse()
176         #
177         NormeX  = numpy.linalg.norm( X0 )
178         if Y is None:
179             Yn = numpy.ravel( Hm( X0 ) ).reshape((-1, 1))
180         else:
181             Yn = numpy.ravel( Y ).reshape((-1, 1))
182         NormeY = numpy.linalg.norm( Yn )
183         if self._toStore("CurrentState"):
184             self.StoredVariables["CurrentState"].store( X0 )
185         if self._toStore("SimulatedObservationAtCurrentState"):
186             self.StoredVariables["SimulatedObservationAtCurrentState"].store( Yn )
187         #
188         dX0 = NumericObjects.SetInitialDirection(
189             self._parameters["InitialDirection"],
190             self._parameters["AmplitudeOfInitialDirection"],
191             X0,
192         )
193         #
194         # Boucle sur les perturbations
195         # ----------------------------
196         msgs  = ("")  # 2
197         msgs += "\n" + __marge + "-" * __nbtirets
198         msgs += "\n" + __marge + __entete
199         msgs += "\n" + __marge + "-" * __nbtirets
200         msgs += ("\n")
201         __pf = "  %" + str(__p + 7) + "." + str(__p) + "e"
202         __ms = "  %2i  %5.0e" + (__pf * 4) + "\n"
203         for ip, amplitude in enumerate(Perturbations):
204             dX          = amplitude * dX0
205             NormedX     = numpy.linalg.norm( dX )
206             #
207             if self._parameters["ResiduFormula"] == "ScalarProduct":
208                 TangentFXdX = numpy.ravel( Ht( (X0, dX) ) )
209                 AdjointFXY  = numpy.ravel( Ha( (X0, Yn)  ) )
210                 #
211                 Residu = abs(vfloat(numpy.dot( TangentFXdX, Yn ) - numpy.dot( dX, AdjointFXY )))
212                 #
213                 self.StoredVariables["Residu"].store( Residu )
214                 ttsep = __ms%(ip, amplitude, NormeX, NormeY, NormedX, Residu)
215                 msgs += __marge + ttsep
216         #
217         msgs += (__marge + "-" * __nbtirets + "\n\n")
218         msgs += (__marge + "End of the \"%s\" verification by the \"%s\" formula.\n\n"%(self._name, self._parameters["ResiduFormula"]))  # noqa: E501
219         msgs += (__marge + "%s\n"%("-" * 75,))
220         print(msgs)  # 2
221         #
222         self._post_run(HO, EM)
223         return 0
224
225 # ==============================================================================
226 if __name__ == "__main__":
227     print("\n AUTODIAGNOSTIC\n")