]> SALOME platform Git repositories - modules/adao.git/blob - doc/en/ref_operator_requirements.rst
Salome HOME
0844a29c44f291c7d9f7336888bc92d75fb0000e
[modules/adao.git] / doc / en / ref_operator_requirements.rst
1 ..
2    Copyright (C) 2008-2020 EDF R&D
3
4    This file is part of SALOME ADAO module.
5
6    This library is free software; you can redistribute it and/or
7    modify it under the terms of the GNU Lesser General Public
8    License as published by the Free Software Foundation; either
9    version 2.1 of the License, or (at your option) any later version.
10
11    This library is distributed in the hope that it will be useful,
12    but WITHOUT ANY WARRANTY; without even the implied warranty of
13    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14    Lesser General Public License for more details.
15
16    You should have received a copy of the GNU Lesser General Public
17    License along with this library; if not, write to the Free Software
18    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
19
20    See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
21
22    Author: Jean-Philippe Argaud, jean-philippe.argaud@edf.fr, EDF R&D
23
24 .. _section_ref_operator_requirements:
25
26 Requirements for functions describing an operator
27 -------------------------------------------------
28
29 .. index:: single: setObservationOperator
30 .. index:: single: setEvolutionModel
31 .. index:: single: setControlModel
32
33 The operators for observation and evolution are required to implement the data
34 assimilation or optimization procedures. They include the physical simulation
35 by numerical calculations, but also the filtering and restriction to compare
36 the simulation to observation. The evolution operator is considered here in its
37 incremental form, representing the transition between two successive states,
38 and is then similar to the observation operator.
39
40 Schematically, an operator :math:`O` has to give a output solution for
41 specified input parameters. Part of the input parameters can be modified during
42 the optimization procedure. So the mathematical representation of such a
43 process is a function. It was briefly described in the section
44 :ref:`section_theory` and is generalized here by the relation:
45
46 .. math:: \mathbf{y} = O( \mathbf{x} )
47
48 between the pseudo-observations outputs :math:`\mathbf{y}` and the input
49 parameters :math:`\mathbf{x}` using the observation or evolution operator
50 :math:`O`. The same functional representation can be used for the linear
51 tangent model :math:`\mathbf{O}` of :math:`O` and its adjoint
52 :math:`\mathbf{O}^*`, also required by some data assimilation or optimization
53 algorithms.
54
55 On input and output of these operators, the :math:`\mathbf{x}` and
56 :math:`\mathbf{y}` variables, or their increments, are mathematically vectors,
57 and they can be given by the user as non-oriented vectors (of type list or
58 Numpy array) or oriented ones (of type Numpy matrix).
59
60 Then, **to fully describe an operator, the user has only to provide a function
61 that completely and only realize the functional operation**.
62
63 This function is usually given as a Python function or script, that can be in
64 particular executed as an independent Python function or in a YACS node. These
65 function or script can, with no differences, launch external codes or use
66 internal Python or SALOME calls and methods. If the algorithm requires the 3
67 aspects of the operator (direct form, tangent form and adjoint form), the user
68 has to give the 3 functions or to approximate them using ADAO.
69
70 There are for the user 3 practical methods to provide an operator functional
71 representation, which are different depending on the chosen argument:
72
73 - :ref:`section_ref_operator_one`
74 - :ref:`section_ref_operator_funcs`
75 - :ref:`section_ref_operator_switch`
76
77 In case of ADAO scripted interface (TUI), only the first two are necessary
78 because the third is included in the second. In case of ADAO graphical
79 interface EFICAS, these methods are chosen in the "*FROM*"  field of each
80 operator having a "*Function*" value as "*INPUT_TYPE*", as shown by the
81 following figure:
82
83   .. eficas_operator_function:
84   .. image:: images/eficas_operator_function.png
85     :align: center
86     :width: 100%
87   .. centered::
88     **Choosing graphically an operator functional representation**
89
90 .. _section_ref_operator_one:
91
92 First functional form: one direct operator only
93 +++++++++++++++++++++++++++++++++++++++++++++++
94
95 .. index:: single: OneFunction
96 .. index:: single: ScriptWithOneFunction
97 .. index:: single: DirectOperator
98 .. index:: single: DifferentialIncrement
99 .. index:: single: CenteredFiniteDifference
100
101 The first one consist in providing only one function, potentially non-linear,
102 and to approximate the associated tangent and adjoint operators.
103
104 This is done in ADAO by using in the graphical interface EFICAS the keyword
105 "*ScriptWithOneFunction*" for the description by a script. In the textual
106 interface, it is the keyword "*OneFunction*", possibly combined with "*Script*"
107 keyword depending on whether it is a function or a script. If it is by external
108 script, the user must provide a file containing a function that has the
109 mandatory name "*DirectOperator*". For example, an external script can follow
110 the generic template::
111
112     def DirectOperator( X ):
113         """ Direct non-linear simulation operator """
114         ...
115         ...
116         ...
117         return Y=O(X)
118
119 In this case, the user has also provide a value for the differential increment
120 (or keep the default value), using through the graphical interface (GUI) or
121 textual one (TUI) the keyword "*DifferentialIncrement*" as parameter, which has
122 a default value of 1%. This coefficient will be used in the finite differences
123 approximation to build the tangent and adjoint operators. The finite
124 differences approximation order can also be chosen through the GUI, using the
125 keyword "*CenteredFiniteDifference*", with 0 for an uncentered schema of first
126 order (which is the default value), and with 1 for a centered schema of second
127 order (and of twice the first order computational cost). If necessary and if
128 possible, :ref:`subsection_ref_parallel_df` can be used. In all cases, an
129 internal cache mechanism is used to restrict the number of operator evaluations
130 at the minimum possible in a sequential or parallel execution scheme for
131 numerical approximations of the tangent and adjoint operators, to avoid
132 redundant calculations.
133
134 This first operator definition form allows easily to test the functional form
135 before its use in an ADAO case, greatly reducing the complexity of operator
136 implementation. One can then use the "*FunctionTest*" ADAO checking algorithm
137 (see the section on the :ref:`section_ref_algorithm_FunctionTest`) for this
138 test.
139
140 **Important warning:** the name "*DirectOperator*" is mandatory, and the type
141 of the ``X`` argument can be either a list of float values, a Numpy array or a
142 Numpy matrix. The user function has to accept and treat all these cases.
143
144 .. _section_ref_operator_funcs:
145
146 Second functional form: three operators direct, tangent and adjoint
147 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
148
149 .. index:: single: ThreeFunctions
150 .. index:: single: ScriptWithFunctions
151 .. index:: single: DirectOperator
152 .. index:: single: TangentOperator
153 .. index:: single: AdjointOperator
154
155 **In general, it is recommended to use the first functional form rather than
156 the second one. A small performance improvement is not a good reason to use a
157 detailed implementation as this second functional form.**
158
159 The second one consist in providing directly the three associated operators
160 :math:`O`, :math:`\mathbf{O}` and :math:`\mathbf{O}^*`. This is done by using
161 the keyword "*ScriptWithFunctions*" for the description of the chosen operator
162 in the ADAO graphical interface EFICAS. In the textual interface, it is the
163 keyword "*ThreeFunctions*", possibly combined with "*Script*" keyword depending
164 on whether it is a function or a script. The user have to provide in one script
165 three functions, with the three mandatory names "*DirectOperator*",
166 "*TangentOperator*" and "*AdjointOperator*". For example, the external script
167 can follow the template::
168
169     def DirectOperator( X ):
170         """ Direct non-linear simulation operator """
171         ...
172         ...
173         ...
174         return something like Y
175
176     def TangentOperator( pair = (X, dX) ):
177         """ Tangent linear operator, around X, applied to dX """
178         X, dX = pair
179         ...
180         ...
181         ...
182         return something like Y
183
184     def AdjointOperator( pair = (X, Y) ):
185         """ Adjoint operator, around X, applied to Y """
186         X, Y = pair
187         ...
188         ...
189         ...
190         return something like X
191
192 Another time, this second operator definition allow easily to test the
193 functional forms before their use in an ADAO case, reducing the complexity of
194 operator implementation.
195
196 For some algorithms, it is required that the tangent and adjoint functions can
197 return the matrix equivalent to the linear operator. In this case, when
198 respectively the ``dX`` or the ``Y`` arguments are ``None``, the user script
199 has to return the associated matrix.
200
201 **Important warning:** the names "*DirectOperator*", "*TangentOperator*" and
202 "*AdjointOperator*" are mandatory, and the type of the ``X``, Y``, ``dX``
203 arguments can be either a list of float values, a Numpy array or a Numpy
204 matrix. The user function has to treat these cases in his script.
205
206 .. _section_ref_operator_switch:
207
208 Third functional form: three operators with a switch
209 ++++++++++++++++++++++++++++++++++++++++++++++++++++
210
211 .. index:: single: ScriptWithSwitch
212 .. index:: single: DirectOperator
213 .. index:: single: TangentOperator
214 .. index:: single: AdjointOperator
215
216 **It is recommended not to use this third functional form without a strong
217 numerical or physical reason. A performance improvement is not a good reason to
218 use the implementation complexity of this third functional form. Only an
219 inability to use the first or second forms justifies the use of the third.**
220
221 This third form give more possibilities to control the execution of the three
222 functions representing the operator, allowing advanced usage and control over
223 each execution of the simulation code. This is done by using the keyword
224 "*ScriptWithSwitch*" for the description of the chosen operator in the ADAO
225 graphical interface EFICAS. In the textual interface, you only have to use the
226 keyword "*ThreeFunctions*" above to also define this case, with the right
227 functions. The user have to provide a switch in one script to control the
228 execution of the direct, tangent and adjoint forms of its simulation code. The
229 user can then, for example, use other approximations for the tangent and
230 adjoint codes, or introduce more complexity in the argument treatment of the
231 functions. But it will be far more complicated to implement and debug.
232
233 If, however, you want to use this third form, we recommend using the following
234 template for the switch. It requires an external script or code named here
235 "*Physical_simulation_functions.py*", containing three functions named
236 "*DirectOperator*", "*TangentOperator*" and "*AdjointOperator*" as previously.
237 Here is the switch template::
238
239     import Physical_simulation_functions
240     import numpy, logging, codecs, pickle
241     def loads( data ):
242         return pickle.loads(codecs.decode(data.encode(), "base64"))
243     #
244     method = ""
245     for param in computation["specificParameters"]:
246         if param["name"] == "method":
247             method = loads(param["value"])
248     if method not in ["Direct", "Tangent", "Adjoint"]:
249         raise ValueError("No valid computation method is given")
250     logging.info("Found method is \'%s\'"%method)
251     #
252     logging.info("Loading operator functions")
253     Function = Physical_simulation_functions.DirectOperator
254     Tangent  = Physical_simulation_functions.TangentOperator
255     Adjoint  = Physical_simulation_functions.AdjointOperator
256     #
257     logging.info("Executing the possible computations")
258     data = []
259     if method == "Direct":
260         logging.info("Direct computation")
261         Xcurrent = computation["inputValues"][0][0][0]
262         data = Function(numpy.matrix( Xcurrent ).T)
263     if method == "Tangent":
264         logging.info("Tangent computation")
265         Xcurrent  = computation["inputValues"][0][0][0]
266         dXcurrent = computation["inputValues"][0][0][1]
267         data = Tangent(numpy.matrix(Xcurrent).T, numpy.matrix(dXcurrent).T)
268     if method == "Adjoint":
269         logging.info("Adjoint computation")
270         Xcurrent = computation["inputValues"][0][0][0]
271         Ycurrent = computation["inputValues"][0][0][1]
272         data = Adjoint((numpy.matrix(Xcurrent).T, numpy.matrix(Ycurrent).T))
273     #
274     logging.info("Formatting the output")
275     it = numpy.ravel(data)
276     outputValues = [[[[]]]]
277     for val in it:
278       outputValues[0][0][0].append(val)
279     #
280     result = {}
281     result["outputValues"]        = outputValues
282     result["specificOutputInfos"] = []
283     result["returnCode"]          = 0
284     result["errorMessage"]        = ""
285
286 All various modifications could be done from this template hypothesis.
287
288 .. _section_ref_operator_control:
289
290 Special case of controlled evolution or observation operator
291 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
292
293 In some cases, the evolution or the observation operator is required to be
294 controlled by an external input control, given *a priori*. In this case, the
295 generic form of the incremental model :math:`O` is slightly modified as
296 follows:
297
298 .. math:: \mathbf{y} = O( \mathbf{x}, \mathbf{u})
299
300 where :math:`\mathbf{u}` is the control over one state increment. In fact, the
301 direct operator has to be applied to a pair of variables :math:`(X,U)`.
302 Schematically, the operator has to be set as::
303
304     def DirectOperator( pair = (X, U) ):
305         """ Direct non-linear simulation operator """
306         X, U = pair
307         ...
308         ...
309         ...
310         return something like X(n+1) (evolution) or Y(n+1) (observation)
311
312 The tangent and adjoint operators have the same signature as previously, noting
313 that the derivatives has to be done only partially against :math:`\mathbf{x}`.
314 In such a case with explicit control, only the second functional form (using
315 "*ScriptWithFunctions*") and third functional form (using "*ScriptWithSwitch*")
316 can be used.
317
318 .. _section_ref_operator_dimensionless:
319
320 Additional notes on dimensionless transformation of operators
321 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
322
323 .. index:: single: Nondimensionalization
324 .. index:: single: Dimensionless
325
326 It is common that physical quantities, in input or output of the operators,
327 have significant differences in magnitude or rate of change. One way to avoid
328 numerical difficulties is to use, or to set, a dimensionless version of
329 calculations carried out in operators [WikipediaND]_. In principle, since
330 physical simulation should be as dimensionless as possible, it is at first
331 recommended to use the existing dimensionless capacity of the calculation code.
332
333 However, in the common case where we can not dispose of it, it is often useful
334 to surround the calculation to remove dimension for input or output. A simple
335 way to do this is to convert the input parameters :math:`\mathbf{x}` which are
336 arguments of a function like "*DirectOperator*". One mostly use the default
337 values :math:`\mathbf{x}^b` (background, or nominal value). Provided that each
338 component of :math:`\mathbf{x}^b` is non zero, one can indeed use a
339 multiplicative correction. For this, one can for example state:
340
341 .. math:: \mathbf{x} = \mathbf{\alpha}\mathbf{x}^b
342
343 and then optimize the multiplicative parameter :math:`\mathbf{\alpha}`.  This
344 parameter has as default value (or as background) a vector of 1. In the same
345 way, one can use additive correction if it is more interesting from a physical
346 point of view. In this case, one can state:
347
348 .. math:: \mathbf{x} =\mathbf{x}^b + \mathbf{\alpha}
349
350 and then optimize the additive parameter :math:`\mathbf{\alpha}`. In this case,
351 the parameter has for background value a vector of 0.
352
353 Be careful, applying a dimensionless transformation also requires changing the
354 associated error covariances in an ADAO formulation of the optimization
355 problem.
356
357 Such a process is rarely enough to avoid all the numerical problems, but it
358 often improves a lot the numeric conditioning of the optimization.
359
360 Dealing explicitly with "multiple" functions
361 ++++++++++++++++++++++++++++++++++++++++++++
362
363 .. warning::
364
365   it is strongly recommended not to use this explicit "multiple" functions
366   definition without a very strong computing justification. This treatment is
367   already done by default in ADAO to increase performances. Only the very
368   experienced user, seeking to manage particularly difficult cases, can be
369   interested in this extension. Despite its simplicity, there is an explicit
370   risk of significantly worsening performance.
371
372 It is possible, when defining operator's functions, to set them as functions
373 that treat not only one argument, but a series of arguments, to give back on
374 output the corresponding value series. Writing it as pseudo-code, the
375 "multiple" function, here named ``MultiFunctionO``, representing the classical
376 operator :math:`O` named "*DirectOperator*", does::
377
378     def MultiFunctionO( Inputs ):
379         """ Multiple ! """
380         Outputs = []
381         for X in Inputs:
382             Y = DirectOperator( X )
383             Outputs.append( Y )
384         return Outputs
385
386 The length of the output (that is, the number of calculated values) is equal to
387 the length of the input (that is, the number of states for which one want to
388 calculate the value by the operator).
389
390 This possibility is only available in the textual interface for ADAO. For this,
391 when defining an operator's function, in the same time one usually define the
392 function or the external script, it can be set using a boolean parameter
393 "*InputFunctionAsMulti*" that the definition is one of a "multiple" function.