2 Copyright (C) 2008-2020 EDF R&D
4 This file is part of SALOME ADAO module.
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.
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.
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
20 See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
22 Author: Jean-Philippe Argaud, jean-philippe.argaud@edf.fr, EDF R&D
24 .. _section_ref_operator_requirements:
26 Requirements for functions describing an operator
27 -------------------------------------------------
29 .. index:: single: setObservationOperator
30 .. index:: single: setEvolutionModel
31 .. index:: single: setControlModel
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.
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:
46 .. math:: \mathbf{y} = O( \mathbf{x} )
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
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).
60 Then, **to fully describe an operator, the user has only to provide a function
61 that completely and only realize the functional operation**.
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.
70 There are for the user 3 practical methods to provide an operator functional
71 representation, which are different depending on the chosen argument:
73 - :ref:`section_ref_operator_one`
74 - :ref:`section_ref_operator_funcs`
75 - :ref:`section_ref_operator_switch`
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
83 .. eficas_operator_function:
84 .. image:: images/eficas_operator_function.png
88 **Choosing graphically an operator functional representation**
90 .. _section_ref_operator_one:
92 First functional form: one direct operator only
93 +++++++++++++++++++++++++++++++++++++++++++++++
95 .. index:: single: OneFunction
96 .. index:: single: ScriptWithOneFunction
97 .. index:: single: DirectOperator
98 .. index:: single: DifferentialIncrement
99 .. index:: single: CenteredFiniteDifference
101 The first one consist in providing only one function, potentially non-linear,
102 and to approximate the associated tangent and adjoint operators.
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::
112 def DirectOperator( X ):
113 """ Direct non-linear simulation operator """
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.
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
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.
144 .. _section_ref_operator_funcs:
146 Second functional form: three operators direct, tangent and adjoint
147 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
149 .. index:: single: ThreeFunctions
150 .. index:: single: ScriptWithFunctions
151 .. index:: single: DirectOperator
152 .. index:: single: TangentOperator
153 .. index:: single: AdjointOperator
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.**
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::
169 def DirectOperator( X ):
170 """ Direct non-linear simulation operator """
174 return "a vector similar to Y"
176 def TangentOperator( pair = (X, dX) ):
177 """ Tangent linear operator, around X, applied to dX """
182 return "a vector similar to Y"
184 def AdjointOperator( pair = (X, Y) ):
185 """ Adjoint operator, around X, applied to Y """
190 return "a vector similar to X"
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.
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::
203 def TangentOperator( pair = (X, dX) ):
204 """ Tangent linear operator, around X, applied to dX """
209 if dX is None or len(dX) == 0:
210 return "the matrix of the tangent linear operator"
212 return "a vector similar to Y"
214 def AdjointOperator( pair = (X, Y) ):
215 """ Adjoint operator, around X, applied to Y """
220 if Y is None or len(Y) == 0:
221 return "the adjoint linear operator matrix"
223 return "a vector similar to X"
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.
230 .. _section_ref_operator_switch:
232 Third functional form: three operators with a switch
233 ++++++++++++++++++++++++++++++++++++++++++++++++++++
235 .. index:: single: ScriptWithSwitch
236 .. index:: single: DirectOperator
237 .. index:: single: TangentOperator
238 .. index:: single: AdjointOperator
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.**
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.
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::
263 import Physical_simulation_functions
264 import numpy, logging, codecs, pickle
266 return pickle.loads(codecs.decode(data.encode(), "base64"))
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)
276 logging.info("Loading operator functions")
277 Function = Physical_simulation_functions.DirectOperator
278 Tangent = Physical_simulation_functions.TangentOperator
279 Adjoint = Physical_simulation_functions.AdjointOperator
281 logging.info("Executing the possible computations")
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))
298 logging.info("Formatting the output")
299 it = numpy.ravel(data)
300 outputValues = [[[[]]]]
302 outputValues[0][0][0].append(val)
305 result["outputValues"] = outputValues
306 result["specificOutputInfos"] = []
307 result["returnCode"] = 0
308 result["errorMessage"] = ""
310 All various modifications could be done from this template hypothesis.
312 .. _section_ref_operator_control:
314 Special case of controlled evolution or observation operator
315 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
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
322 .. math:: \mathbf{y} = O( \mathbf{x}, \mathbf{u})
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::
328 def DirectOperator( pair = (X, U) ):
329 """ Direct non-linear simulation operator """
334 return something like X(n+1) (evolution) or Y(n+1) (observation)
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*")
342 .. _section_ref_operator_dimensionless:
344 Additional notes on dimensionless transformation of operators
345 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
347 .. index:: single: Nondimensionalization
348 .. index:: single: Dimensionless
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.
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:
365 .. math:: \mathbf{x} = \mathbf{\alpha}\mathbf{x}^b
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:
372 .. math:: \mathbf{x} =\mathbf{x}^b + \mathbf{\alpha}
374 and then optimize the additive parameter :math:`\mathbf{\alpha}`. In this case,
375 the parameter has for background value a vector of 0.
377 Be careful, applying a dimensionless transformation also requires changing the
378 associated error covariances in an ADAO formulation of the optimization
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.
384 Dealing explicitly with "multiple" functions
385 ++++++++++++++++++++++++++++++++++++++++++++
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.
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::
402 def MultiFunctionO( Inputs ):
406 Y = DirectOperator( X )
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).
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.