]> SALOME platform Git repositories - modules/adao.git/blob - doc/en/ref_operator_requirements.rst
Salome HOME
Improvement of operator documentation
[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 "a vector similar to 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 "a vector similar to 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 "a vector similar to 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 (in particular filters without ensemble), it is required
197 that the tangent and adjoint functions can return the matrix equivalent to the
198 linear operator. In this case, when respectively the ``dX`` or the ``Y``
199 arguments are ``None``, the user script has to return the associated matrix.
200 The templates of the "*TangentOperator*" and "*AddOperator*" functions then
201 become the following::
202
203     def TangentOperator( pair = (X, dX) ):
204         """ Tangent linear operator, around X, applied to dX """
205         X, dX = pair
206         ...
207         ...
208         ...
209         if dX is None or len(dX) == 0:
210             return "the matrix of the tangent linear operator"
211         else:
212             return "a vector similar to Y"
213
214     def AdjointOperator( pair = (X, Y) ):
215         """ Adjoint operator, around X, applied to Y """
216         X, Y = pair
217         ...
218         ...
219         ...
220         if Y is None or len(Y) == 0:
221             return "the adjoint linear operator matrix"
222         else:
223             return "a vector similar to X"
224
225 **Important warning:** the names "*DirectOperator*", "*TangentOperator*" and
226 "*AdjointOperator*" are mandatory, and the type of the ``X``, Y``, ``dX``
227 arguments can be either a list of float values, a Numpy array or a Numpy
228 matrix. The user function has to treat these cases in his script.
229
230 .. _section_ref_operator_switch:
231
232 Third functional form: three operators with a switch
233 ++++++++++++++++++++++++++++++++++++++++++++++++++++
234
235 .. index:: single: ScriptWithSwitch
236 .. index:: single: DirectOperator
237 .. index:: single: TangentOperator
238 .. index:: single: AdjointOperator
239
240 **It is recommended not to use this third functional form without a strong
241 numerical or physical reason. A performance improvement is not a good reason to
242 use the implementation complexity of this third functional form. Only an
243 inability to use the first or second forms justifies the use of the third.**
244
245 This third form give more possibilities to control the execution of the three
246 functions representing the operator, allowing advanced usage and control over
247 each execution of the simulation code. This is done by using the keyword
248 "*ScriptWithSwitch*" for the description of the chosen operator in the ADAO
249 graphical interface EFICAS. In the textual interface, you only have to use the
250 keyword "*ThreeFunctions*" above to also define this case, with the right
251 functions. The user have to provide a switch in one script to control the
252 execution of the direct, tangent and adjoint forms of its simulation code. The
253 user can then, for example, use other approximations for the tangent and
254 adjoint codes, or introduce more complexity in the argument treatment of the
255 functions. But it will be far more complicated to implement and debug.
256
257 If, however, you want to use this third form, we recommend using the following
258 template for the switch. It requires an external script or code named here
259 "*Physical_simulation_functions.py*", containing three functions named
260 "*DirectOperator*", "*TangentOperator*" and "*AdjointOperator*" as previously.
261 Here is the switch template::
262
263     import Physical_simulation_functions
264     import numpy, logging, codecs, pickle
265     def loads( data ):
266         return pickle.loads(codecs.decode(data.encode(), "base64"))
267     #
268     method = ""
269     for param in computation["specificParameters"]:
270         if param["name"] == "method":
271             method = loads(param["value"])
272     if method not in ["Direct", "Tangent", "Adjoint"]:
273         raise ValueError("No valid computation method is given")
274     logging.info("Found method is \'%s\'"%method)
275     #
276     logging.info("Loading operator functions")
277     Function = Physical_simulation_functions.DirectOperator
278     Tangent  = Physical_simulation_functions.TangentOperator
279     Adjoint  = Physical_simulation_functions.AdjointOperator
280     #
281     logging.info("Executing the possible computations")
282     data = []
283     if method == "Direct":
284         logging.info("Direct computation")
285         Xcurrent = computation["inputValues"][0][0][0]
286         data = Function(numpy.matrix( Xcurrent ).T)
287     if method == "Tangent":
288         logging.info("Tangent computation")
289         Xcurrent  = computation["inputValues"][0][0][0]
290         dXcurrent = computation["inputValues"][0][0][1]
291         data = Tangent(numpy.matrix(Xcurrent).T, numpy.matrix(dXcurrent).T)
292     if method == "Adjoint":
293         logging.info("Adjoint computation")
294         Xcurrent = computation["inputValues"][0][0][0]
295         Ycurrent = computation["inputValues"][0][0][1]
296         data = Adjoint((numpy.matrix(Xcurrent).T, numpy.matrix(Ycurrent).T))
297     #
298     logging.info("Formatting the output")
299     it = numpy.ravel(data)
300     outputValues = [[[[]]]]
301     for val in it:
302       outputValues[0][0][0].append(val)
303     #
304     result = {}
305     result["outputValues"]        = outputValues
306     result["specificOutputInfos"] = []
307     result["returnCode"]          = 0
308     result["errorMessage"]        = ""
309
310 All various modifications could be done from this template hypothesis.
311
312 .. _section_ref_operator_control:
313
314 Special case of controlled evolution or observation operator
315 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
316
317 In some cases, the evolution or the observation operator is required to be
318 controlled by an external input control, given *a priori*. In this case, the
319 generic form of the incremental model :math:`O` is slightly modified as
320 follows:
321
322 .. math:: \mathbf{y} = O( \mathbf{x}, \mathbf{u})
323
324 where :math:`\mathbf{u}` is the control over one state increment. In fact, the
325 direct operator has to be applied to a pair of variables :math:`(X,U)`.
326 Schematically, the operator has to be set as::
327
328     def DirectOperator( pair = (X, U) ):
329         """ Direct non-linear simulation operator """
330         X, U = pair
331         ...
332         ...
333         ...
334         return something like X(n+1) (evolution) or Y(n+1) (observation)
335
336 The tangent and adjoint operators have the same signature as previously, noting
337 that the derivatives has to be done only partially against :math:`\mathbf{x}`.
338 In such a case with explicit control, only the second functional form (using
339 "*ScriptWithFunctions*") and third functional form (using "*ScriptWithSwitch*")
340 can be used.
341
342 .. _section_ref_operator_dimensionless:
343
344 Additional notes on dimensionless transformation of operators
345 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
346
347 .. index:: single: Nondimensionalization
348 .. index:: single: Dimensionless
349
350 It is common that physical quantities, in input or output of the operators,
351 have significant differences in magnitude or rate of change. One way to avoid
352 numerical difficulties is to use, or to set, a dimensionless version of
353 calculations carried out in operators [WikipediaND]_. In principle, since
354 physical simulation should be as dimensionless as possible, it is at first
355 recommended to use the existing dimensionless capacity of the calculation code.
356
357 However, in the common case where we can not dispose of it, it is often useful
358 to surround the calculation to remove dimension for input or output. A simple
359 way to do this is to convert the input parameters :math:`\mathbf{x}` which are
360 arguments of a function like "*DirectOperator*". One mostly use the default
361 values :math:`\mathbf{x}^b` (background, or nominal value). Provided that each
362 component of :math:`\mathbf{x}^b` is non zero, one can indeed use a
363 multiplicative correction. For this, one can for example state:
364
365 .. math:: \mathbf{x} = \mathbf{\alpha}\mathbf{x}^b
366
367 and then optimize the multiplicative parameter :math:`\mathbf{\alpha}`.  This
368 parameter has as default value (or as background) a vector of 1. In the same
369 way, one can use additive correction if it is more interesting from a physical
370 point of view. In this case, one can state:
371
372 .. math:: \mathbf{x} =\mathbf{x}^b + \mathbf{\alpha}
373
374 and then optimize the additive parameter :math:`\mathbf{\alpha}`. In this case,
375 the parameter has for background value a vector of 0.
376
377 Be careful, applying a dimensionless transformation also requires changing the
378 associated error covariances in an ADAO formulation of the optimization
379 problem.
380
381 Such a process is rarely enough to avoid all the numerical problems, but it
382 often improves a lot the numeric conditioning of the optimization.
383
384 Dealing explicitly with "multiple" functions
385 ++++++++++++++++++++++++++++++++++++++++++++
386
387 .. warning::
388
389   it is strongly recommended not to use this explicit "multiple" functions
390   definition without a very strong computing justification. This treatment is
391   already done by default in ADAO to increase performances. Only the very
392   experienced user, seeking to manage particularly difficult cases, can be
393   interested in this extension. Despite its simplicity, there is an explicit
394   risk of significantly worsening performance.
395
396 It is possible, when defining operator's functions, to set them as functions
397 that treat not only one argument, but a series of arguments, to give back on
398 output the corresponding value series. Writing it as pseudo-code, the
399 "multiple" function, here named ``MultiFunctionO``, representing the classical
400 operator :math:`O` named "*DirectOperator*", does::
401
402     def MultiFunctionO( Inputs ):
403         """ Multiple ! """
404         Outputs = []
405         for X in Inputs:
406             Y = DirectOperator( X )
407             Outputs.append( Y )
408         return Outputs
409
410 The length of the output (that is, the number of calculated values) is equal to
411 the length of the input (that is, the number of states for which one want to
412 calculate the value by the operator).
413
414 This possibility is only available in the textual interface for ADAO. For this,
415 when defining an operator's function, in the same time one usually define the
416 function or the external script, it can be set using a boolean parameter
417 "*InputFunctionAsMulti*" that the definition is one of a "multiple" function.