Salome HOME
Documentation corrections and code performance update
[modules/adao.git] / src / daComposant / daCore / BasicObjects.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 """
24     Définit les outils généraux élémentaires.
25 """
26 __author__ = "Jean-Philippe ARGAUD"
27 __all__ = []
28
29 import os
30 import sys
31 import logging
32 import copy
33 import time
34 import numpy
35 import warnings
36 from functools import partial
37 from daCore import Persistence
38 from daCore import PlatformInfo
39 from daCore import Interfaces
40 from daCore import Templates
41
42 # ==============================================================================
43 class CacheManager(object):
44     """
45     Classe générale de gestion d'un cache de calculs
46     """
47     __slots__ = (
48         "__tolerBP", "__lengthOR", "__initlnOR", "__seenNames", "__enabled",
49         "__listOPCV",
50         )
51     #
52     def __init__(self,
53                  toleranceInRedundancy = 1.e-18,
54                  lengthOfRedundancy    = -1,
55                 ):
56         """
57         Les caractéristiques de tolérance peuvent être modifiées à la création.
58         """
59         self.__tolerBP   = float(toleranceInRedundancy)
60         self.__lengthOR  = int(lengthOfRedundancy)
61         self.__initlnOR  = self.__lengthOR
62         self.__seenNames = []
63         self.__enabled   = True
64         self.clearCache()
65
66     def clearCache(self):
67         "Vide le cache"
68         self.__listOPCV  = []
69         self.__seenNames = []
70
71     def wasCalculatedIn(self, xValue, oName="" ):
72         "Vérifie l'existence d'un calcul correspondant à la valeur"
73         __alc = False
74         __HxV = None
75         if self.__enabled:
76             for i in range(min(len(self.__listOPCV),self.__lengthOR)-1,-1,-1):
77                 if not hasattr(xValue, 'size'):
78                     pass
79                 elif (str(oName) != self.__listOPCV[i][3]):
80                     pass
81                 elif (xValue.size != self.__listOPCV[i][0].size):
82                     pass
83                 elif (numpy.ravel(xValue)[0] - self.__listOPCV[i][0][0]) > (self.__tolerBP * self.__listOPCV[i][2] / self.__listOPCV[i][0].size):
84                     pass
85                 elif numpy.linalg.norm(numpy.ravel(xValue) - self.__listOPCV[i][0]) < (self.__tolerBP * self.__listOPCV[i][2]):
86                     __alc  = True
87                     __HxV = self.__listOPCV[i][1]
88                     break
89         return __alc, __HxV
90
91     def storeValueInX(self, xValue, HxValue, oName="" ):
92         "Stocke pour un opérateur o un calcul Hx correspondant à la valeur x"
93         if self.__lengthOR < 0:
94             self.__lengthOR = 2 * min(numpy.size(xValue), 50) + 2
95             self.__initlnOR = self.__lengthOR
96             self.__seenNames.append(str(oName))
97         if str(oName) not in self.__seenNames: # Etend la liste si nouveau
98             self.__lengthOR += 2 * min(numpy.size(xValue), 50) + 2
99             self.__initlnOR += self.__lengthOR
100             self.__seenNames.append(str(oName))
101         while len(self.__listOPCV) > self.__lengthOR:
102             self.__listOPCV.pop(0)
103         self.__listOPCV.append( (
104             copy.copy(numpy.ravel(xValue)), # 0 Previous point
105             copy.copy(HxValue),             # 1 Previous value
106             numpy.linalg.norm(xValue),      # 2 Norm
107             str(oName),                     # 3 Operator name
108             ) )
109
110     def disable(self):
111         "Inactive le cache"
112         self.__initlnOR = self.__lengthOR
113         self.__lengthOR = 0
114         self.__enabled  = False
115
116     def enable(self):
117         "Active le cache"
118         self.__lengthOR = self.__initlnOR
119         self.__enabled  = True
120
121 # ==============================================================================
122 class Operator(object):
123     """
124     Classe générale d'interface de type opérateur simple
125     """
126     __slots__ = (
127         "__name", "__NbCallsAsMatrix", "__NbCallsAsMethod",
128         "__NbCallsOfCached", "__reduceM", "__avoidRC", "__inputAsMF",
129         "__mpEnabled", "__extraArgs", "__Method", "__Matrix", "__Type",
130         )
131     #
132     NbCallsAsMatrix = 0
133     NbCallsAsMethod = 0
134     NbCallsOfCached = 0
135     CM = CacheManager()
136     #
137     def __init__(self,
138         name                 = "GenericOperator",
139         fromMethod           = None,
140         fromMatrix           = None,
141         avoidingRedundancy   = True,
142         reducingMemoryUse    = False,
143         inputAsMultiFunction = False,
144         enableMultiProcess   = False,
145         extraArguments       = None,
146         ):
147         """
148         On construit un objet de ce type en fournissant, à l'aide de l'un des
149         deux mots-clé, soit une fonction ou un multi-fonction python, soit une
150         matrice.
151         Arguments :
152         - name : nom d'opérateur
153         - fromMethod : argument de type fonction Python
154         - fromMatrix : argument adapté au constructeur numpy.array/matrix
155         - avoidingRedundancy : booléen évitant (ou pas) les calculs redondants
156         - reducingMemoryUse : booléen forçant (ou pas) des calculs moins
157           gourmands en mémoire
158         - inputAsMultiFunction : booléen indiquant une fonction explicitement
159           définie (ou pas) en multi-fonction
160         - extraArguments : arguments supplémentaires passés à la fonction de
161           base et ses dérivées (tuple ou dictionnaire)
162         """
163         self.__name      = str(name)
164         self.__NbCallsAsMatrix, self.__NbCallsAsMethod, self.__NbCallsOfCached = 0, 0, 0
165         self.__reduceM   = bool( reducingMemoryUse )
166         self.__avoidRC   = bool( avoidingRedundancy )
167         self.__inputAsMF = bool( inputAsMultiFunction )
168         self.__mpEnabled = bool( enableMultiProcess )
169         self.__extraArgs = extraArguments
170         if   fromMethod is not None and self.__inputAsMF:
171             self.__Method = fromMethod # logtimer(fromMethod)
172             self.__Matrix = None
173             self.__Type   = "Method"
174         elif fromMethod is not None and not self.__inputAsMF:
175             self.__Method = partial( MultiFonction, _sFunction=fromMethod, _mpEnabled=self.__mpEnabled)
176             self.__Matrix = None
177             self.__Type   = "Method"
178         elif fromMatrix is not None:
179             self.__Method = None
180             if isinstance(fromMatrix, str):
181                fromMatrix = PlatformInfo.strmatrix2liststr( fromMatrix )
182             self.__Matrix = numpy.asarray( fromMatrix, dtype=float )
183             self.__Type   = "Matrix"
184         else:
185             self.__Method = None
186             self.__Matrix = None
187             self.__Type   = None
188
189     def disableAvoidingRedundancy(self):
190         "Inactive le cache"
191         Operator.CM.disable()
192
193     def enableAvoidingRedundancy(self):
194         "Active le cache"
195         if self.__avoidRC:
196             Operator.CM.enable()
197         else:
198             Operator.CM.disable()
199
200     def isType(self):
201         "Renvoie le type"
202         return self.__Type
203
204     def appliedTo(self, xValue, HValue = None, argsAsSerie = False, returnSerieAsArrayMatrix = False):
205         """
206         Permet de restituer le résultat de l'application de l'opérateur à une
207         série d'arguments xValue. Cette méthode se contente d'appliquer, chaque
208         argument devant a priori être du bon type.
209         Arguments :
210         - les arguments par série sont :
211             - xValue : argument adapté pour appliquer l'opérateur
212             - HValue : valeur précalculée de l'opérateur en ce point
213         - argsAsSerie : indique si les arguments sont une mono ou multi-valeur
214         """
215         if argsAsSerie:
216             _xValue = xValue
217             _HValue = HValue
218         else:
219             _xValue = (xValue,)
220             if HValue is not None:
221                 _HValue = (HValue,)
222             else:
223                 _HValue = HValue
224         PlatformInfo.isIterable( _xValue, True, " in Operator.appliedTo" )
225         #
226         if _HValue is not None:
227             assert len(_xValue) == len(_HValue), "Incompatible number of elements in xValue and HValue"
228             _HxValue = []
229             for i in range(len(_HValue)):
230                 _HxValue.append( _HValue[i] )
231                 if self.__avoidRC:
232                     Operator.CM.storeValueInX(_xValue[i],_HxValue[-1],self.__name)
233         else:
234             _HxValue = []
235             _xserie = []
236             _hindex = []
237             for i, xv in enumerate(_xValue):
238                 if self.__avoidRC:
239                     __alreadyCalculated, __HxV = Operator.CM.wasCalculatedIn(xv,self.__name)
240                 else:
241                     __alreadyCalculated = False
242                 #
243                 if __alreadyCalculated:
244                     self.__addOneCacheCall()
245                     _hv = __HxV
246                 else:
247                     if self.__Matrix is not None:
248                         self.__addOneMatrixCall()
249                         _hv = self.__Matrix @ numpy.ravel(xv)
250                     else:
251                         self.__addOneMethodCall()
252                         _xserie.append( xv )
253                         _hindex.append(  i )
254                         _hv = None
255                 _HxValue.append( _hv )
256             #
257             if len(_xserie)>0 and self.__Matrix is None:
258                 if self.__extraArgs is None:
259                     _hserie = self.__Method( _xserie ) # Calcul MF
260                 else:
261                     _hserie = self.__Method( _xserie, self.__extraArgs ) # Calcul MF
262                 if not hasattr(_hserie, "pop"):
263                     raise TypeError(
264                         "The user input multi-function doesn't seem to return a"+\
265                         " result sequence, behaving like a mono-function. It has"+\
266                         " to be checked."
267                         )
268                 for i in _hindex:
269                     _xv = _xserie.pop(0)
270                     _hv = _hserie.pop(0)
271                     _HxValue[i] = _hv
272                     if self.__avoidRC:
273                         Operator.CM.storeValueInX(_xv,_hv,self.__name)
274         #
275         if returnSerieAsArrayMatrix:
276             _HxValue = numpy.stack([numpy.ravel(_hv) for _hv in _HxValue], axis=1)
277         #
278         if argsAsSerie: return _HxValue
279         else:           return _HxValue[-1]
280
281     def appliedControledFormTo(self, paires, argsAsSerie = False, returnSerieAsArrayMatrix = False):
282         """
283         Permet de restituer le résultat de l'application de l'opérateur à des
284         paires (xValue, uValue). Cette méthode se contente d'appliquer, son
285         argument devant a priori être du bon type. Si la uValue est None,
286         on suppose que l'opérateur ne s'applique qu'à xValue.
287         Arguments :
288         - paires : les arguments par paire sont :
289             - xValue : argument X adapté pour appliquer l'opérateur
290             - uValue : argument U adapté pour appliquer l'opérateur
291         - argsAsSerie : indique si l'argument est une mono ou multi-valeur
292         """
293         if argsAsSerie: _xuValue = paires
294         else:           _xuValue = (paires,)
295         PlatformInfo.isIterable( _xuValue, True, " in Operator.appliedControledFormTo" )
296         #
297         if self.__Matrix is not None:
298             _HxValue = []
299             for paire in _xuValue:
300                 _xValue, _uValue = paire
301                 self.__addOneMatrixCall()
302                 _HxValue.append( self.__Matrix @ numpy.ravel(_xValue) )
303         else:
304             _xuArgs = []
305             for paire in _xuValue:
306                 _xValue, _uValue = paire
307                 if _uValue is not None:
308                     _xuArgs.append( paire )
309                 else:
310                     _xuArgs.append( _xValue )
311             self.__addOneMethodCall( len(_xuArgs) )
312             if self.__extraArgs is None:
313                 _HxValue = self.__Method( _xuArgs ) # Calcul MF
314             else:
315                 _HxValue = self.__Method( _xuArgs, self.__extraArgs ) # Calcul MF
316         #
317         if returnSerieAsArrayMatrix:
318             _HxValue = numpy.stack([numpy.ravel(_hv) for _hv in _HxValue], axis=1)
319         #
320         if argsAsSerie: return _HxValue
321         else:           return _HxValue[-1]
322
323     def appliedInXTo(self, paires, argsAsSerie = False, returnSerieAsArrayMatrix = False):
324         """
325         Permet de restituer le résultat de l'application de l'opérateur à une
326         série d'arguments xValue, sachant que l'opérateur est valable en
327         xNominal. Cette méthode se contente d'appliquer, son argument devant a
328         priori être du bon type. Si l'opérateur est linéaire car c'est une
329         matrice, alors il est valable en tout point nominal et xNominal peut
330         être quelconque. Il n'y a qu'une seule paire par défaut, et argsAsSerie
331         permet d'indiquer que l'argument est multi-paires.
332         Arguments :
333         - paires : les arguments par paire sont :
334             - xNominal : série d'arguments permettant de donner le point où
335               l'opérateur est construit pour être ensuite appliqué
336             - xValue : série d'arguments adaptés pour appliquer l'opérateur
337         - argsAsSerie : indique si l'argument est une mono ou multi-valeur
338         """
339         if argsAsSerie: _nxValue = paires
340         else:           _nxValue = (paires,)
341         PlatformInfo.isIterable( _nxValue, True, " in Operator.appliedInXTo" )
342         #
343         if self.__Matrix is not None:
344             _HxValue = []
345             for paire in _nxValue:
346                 _xNominal, _xValue = paire
347                 self.__addOneMatrixCall()
348                 _HxValue.append( self.__Matrix @ numpy.ravel(_xValue) )
349         else:
350             self.__addOneMethodCall( len(_nxValue) )
351             if self.__extraArgs is None:
352                 _HxValue = self.__Method( _nxValue ) # Calcul MF
353             else:
354                 _HxValue = self.__Method( _nxValue, self.__extraArgs ) # Calcul MF
355         #
356         if returnSerieAsArrayMatrix:
357             _HxValue = numpy.stack([numpy.ravel(_hv) for _hv in _HxValue], axis=1)
358         #
359         if argsAsSerie: return _HxValue
360         else:           return _HxValue[-1]
361
362     def asMatrix(self, ValueForMethodForm = "UnknownVoidValue", argsAsSerie = False):
363         """
364         Permet de renvoyer l'opérateur sous la forme d'une matrice
365         """
366         if self.__Matrix is not None:
367             self.__addOneMatrixCall()
368             mValue = [self.__Matrix,]
369         elif not isinstance(ValueForMethodForm,str) or ValueForMethodForm != "UnknownVoidValue": # Ne pas utiliser "None"
370             mValue = []
371             if argsAsSerie:
372                 self.__addOneMethodCall( len(ValueForMethodForm) )
373                 for _vfmf in ValueForMethodForm:
374                     mValue.append( self.__Method(((_vfmf, None),)) )
375             else:
376                 self.__addOneMethodCall()
377                 mValue = self.__Method(((ValueForMethodForm, None),))
378         else:
379             raise ValueError("Matrix form of the operator defined as a function/method requires to give an operating point.")
380         #
381         if argsAsSerie: return mValue
382         else:           return mValue[-1]
383
384     def shape(self):
385         """
386         Renvoie la taille sous forme numpy si l'opérateur est disponible sous
387         la forme d'une matrice
388         """
389         if self.__Matrix is not None:
390             return self.__Matrix.shape
391         else:
392             raise ValueError("Matrix form of the operator is not available, nor the shape")
393
394     def nbcalls(self, which=None):
395         """
396         Renvoie les nombres d'évaluations de l'opérateur
397         """
398         __nbcalls = (
399             self.__NbCallsAsMatrix+self.__NbCallsAsMethod,
400             self.__NbCallsAsMatrix,
401             self.__NbCallsAsMethod,
402             self.__NbCallsOfCached,
403             Operator.NbCallsAsMatrix+Operator.NbCallsAsMethod,
404             Operator.NbCallsAsMatrix,
405             Operator.NbCallsAsMethod,
406             Operator.NbCallsOfCached,
407             )
408         if which is None: return __nbcalls
409         else:             return __nbcalls[which]
410
411     def __addOneMatrixCall(self):
412         "Comptabilise un appel"
413         self.__NbCallsAsMatrix   += 1 # Decompte local
414         Operator.NbCallsAsMatrix += 1 # Decompte global
415
416     def __addOneMethodCall(self, nb = 1):
417         "Comptabilise un appel"
418         self.__NbCallsAsMethod   += nb # Decompte local
419         Operator.NbCallsAsMethod += nb # Decompte global
420
421     def __addOneCacheCall(self):
422         "Comptabilise un appel"
423         self.__NbCallsOfCached   += 1 # Decompte local
424         Operator.NbCallsOfCached += 1 # Decompte global
425
426 # ==============================================================================
427 class FullOperator(object):
428     """
429     Classe générale d'interface de type opérateur complet
430     (Direct, Linéaire Tangent, Adjoint)
431     """
432     __slots__ = (
433         "__name", "__check", "__extraArgs", "__FO", "__T",
434         )
435     #
436     def __init__(self,
437                  name             = "GenericFullOperator",
438                  asMatrix         = None,
439                  asOneFunction    = None, # 1 Fonction
440                  asThreeFunctions = None, # 3 Fonctions in a dictionary
441                  asScript         = None, # 1 or 3 Fonction(s) by script
442                  asDict           = None, # Parameters
443                  appliedInX       = None,
444                  extraArguments   = None,
445                  performancePrf   = None,
446                  inputAsMF        = False,# Fonction(s) as Multi-Functions
447                  scheduledBy      = None,
448                  toBeChecked      = False,
449                  ):
450         ""
451         self.__name      = str(name)
452         self.__check     = bool(toBeChecked)
453         self.__extraArgs = extraArguments
454         #
455         self.__FO        = {}
456         #
457         __Parameters = {}
458         if (asDict is not None) and isinstance(asDict, dict):
459             __Parameters.update( asDict )
460         # Priorité à EnableMultiProcessingInDerivatives=True
461         if "EnableMultiProcessing" in __Parameters and __Parameters["EnableMultiProcessing"]:
462             __Parameters["EnableMultiProcessingInDerivatives"] = True
463             __Parameters["EnableMultiProcessingInEvaluation"]  = False
464         if "EnableMultiProcessingInDerivatives"  not in __Parameters:
465             __Parameters["EnableMultiProcessingInDerivatives"]  = False
466         if __Parameters["EnableMultiProcessingInDerivatives"]:
467             __Parameters["EnableMultiProcessingInEvaluation"]  = False
468         if "EnableMultiProcessingInEvaluation"  not in __Parameters:
469             __Parameters["EnableMultiProcessingInEvaluation"]  = False
470         if "withIncrement" in __Parameters: # Temporaire
471             __Parameters["DifferentialIncrement"] = __Parameters["withIncrement"]
472         # Le défaut est équivalent à "ReducedOverallRequirements"
473         __reduceM, __avoidRC = True, True
474         if performancePrf is not None:
475             if   performancePrf == "ReducedAmountOfCalculation":
476                 __reduceM, __avoidRC = False, True
477             elif performancePrf == "ReducedMemoryFootprint":
478                 __reduceM, __avoidRC = True, False
479             elif performancePrf == "NoSavings":
480                 __reduceM, __avoidRC = False, False
481         #
482         if asScript is not None:
483             __Matrix, __Function = None, None
484             if asMatrix:
485                 __Matrix = Interfaces.ImportFromScript(asScript).getvalue( self.__name )
486             elif asOneFunction:
487                 __Function = { "Direct":Interfaces.ImportFromScript(asScript).getvalue( "DirectOperator" ) }
488                 __Function.update({"useApproximatedDerivatives":True})
489                 __Function.update(__Parameters)
490             elif asThreeFunctions:
491                 __Function = {
492                     "Direct" :Interfaces.ImportFromScript(asScript).getvalue( "DirectOperator" ),
493                     "Tangent":Interfaces.ImportFromScript(asScript).getvalue( "TangentOperator" ),
494                     "Adjoint":Interfaces.ImportFromScript(asScript).getvalue( "AdjointOperator" ),
495                     }
496                 __Function.update(__Parameters)
497         else:
498             __Matrix = asMatrix
499             if asOneFunction is not None:
500                 if isinstance(asOneFunction, dict) and "Direct" in asOneFunction:
501                     if asOneFunction["Direct"] is not None:
502                         __Function = asOneFunction
503                     else:
504                         raise ValueError("The function has to be given in a dictionnary which have 1 key (\"Direct\")")
505                 else:
506                     __Function = { "Direct":asOneFunction }
507                 __Function.update({"useApproximatedDerivatives":True})
508                 __Function.update(__Parameters)
509             elif asThreeFunctions is not None:
510                 if isinstance(asThreeFunctions, dict) and \
511                    ("Tangent" in asThreeFunctions) and (asThreeFunctions["Tangent"] is not None) and \
512                    ("Adjoint" in asThreeFunctions) and (asThreeFunctions["Adjoint"] is not None) and \
513                    (("useApproximatedDerivatives" not in asThreeFunctions) or not bool(asThreeFunctions["useApproximatedDerivatives"])):
514                     __Function = asThreeFunctions
515                 elif isinstance(asThreeFunctions, dict) and \
516                    ("Direct" in asThreeFunctions) and (asThreeFunctions["Direct"] is not None):
517                     __Function = asThreeFunctions
518                     __Function.update({"useApproximatedDerivatives":True})
519                 else:
520                     raise ValueError(
521                         "The functions has to be given in a dictionnary which have either"+\
522                         " 1 key (\"Direct\") or"+\
523                         " 3 keys (\"Direct\" (optionnal), \"Tangent\" and \"Adjoint\")")
524                 if "Direct"  not in asThreeFunctions:
525                     __Function["Direct"] = asThreeFunctions["Tangent"]
526                 __Function.update(__Parameters)
527             else:
528                 __Function = None
529         #
530         if   appliedInX is not None and isinstance(appliedInX, dict):
531             __appliedInX = appliedInX
532         elif appliedInX is not None:
533             __appliedInX = {"HXb":appliedInX}
534         else:
535             __appliedInX = None
536         #
537         if scheduledBy is not None:
538             self.__T = scheduledBy
539         #
540         if isinstance(__Function, dict) and \
541                 ("useApproximatedDerivatives" in __Function) and bool(__Function["useApproximatedDerivatives"]) and \
542                 ("Direct" in __Function) and (__Function["Direct"] is not None):
543             if "CenteredFiniteDifference"           not in __Function: __Function["CenteredFiniteDifference"]           = False
544             if "DifferentialIncrement"              not in __Function: __Function["DifferentialIncrement"]              = 0.01
545             if "withdX"                             not in __Function: __Function["withdX"]                             = None
546             if "withReducingMemoryUse"              not in __Function: __Function["withReducingMemoryUse"]              = __reduceM
547             if "withAvoidingRedundancy"             not in __Function: __Function["withAvoidingRedundancy"]             = __avoidRC
548             if "withToleranceInRedundancy"          not in __Function: __Function["withToleranceInRedundancy"]          = 1.e-18
549             if "withLengthOfRedundancy"             not in __Function: __Function["withLengthOfRedundancy"]             = -1
550             if "NumberOfProcesses"                  not in __Function: __Function["NumberOfProcesses"]                  = None
551             if "withmfEnabled"                      not in __Function: __Function["withmfEnabled"]                      = inputAsMF
552             from daCore import NumericObjects
553             FDA = NumericObjects.FDApproximation(
554                 name                  = self.__name,
555                 Function              = __Function["Direct"],
556                 centeredDF            = __Function["CenteredFiniteDifference"],
557                 increment             = __Function["DifferentialIncrement"],
558                 dX                    = __Function["withdX"],
559                 extraArguments        = self.__extraArgs,
560                 reducingMemoryUse     = __Function["withReducingMemoryUse"],
561                 avoidingRedundancy    = __Function["withAvoidingRedundancy"],
562                 toleranceInRedundancy = __Function["withToleranceInRedundancy"],
563                 lengthOfRedundancy    = __Function["withLengthOfRedundancy"],
564                 mpEnabled             = __Function["EnableMultiProcessingInDerivatives"],
565                 mpWorkers             = __Function["NumberOfProcesses"],
566                 mfEnabled             = __Function["withmfEnabled"],
567                 )
568             self.__FO["Direct"]  = Operator(
569                 name = self.__name,
570                 fromMethod = FDA.DirectOperator,
571                 reducingMemoryUse = __reduceM,
572                 avoidingRedundancy = __avoidRC,
573                 inputAsMultiFunction = inputAsMF,
574                 extraArguments = self.__extraArgs,
575                 enableMultiProcess = __Parameters["EnableMultiProcessingInEvaluation"] )
576             self.__FO["Tangent"] = Operator(
577                 name = self.__name+"Tangent",
578                 fromMethod = FDA.TangentOperator,
579                 reducingMemoryUse = __reduceM,
580                 avoidingRedundancy = __avoidRC,
581                 inputAsMultiFunction = inputAsMF,
582                 extraArguments = self.__extraArgs )
583             self.__FO["Adjoint"] = Operator(
584                 name = self.__name+"Adjoint",
585                 fromMethod = FDA.AdjointOperator,
586                 reducingMemoryUse = __reduceM,
587                 avoidingRedundancy = __avoidRC,
588                 inputAsMultiFunction = inputAsMF,
589                 extraArguments = self.__extraArgs )
590             self.__FO["DifferentialIncrement"] = __Function["DifferentialIncrement"]
591         elif isinstance(__Function, dict) and \
592                 ("Direct" in __Function) and ("Tangent" in __Function) and ("Adjoint" in __Function) and \
593                 (__Function["Direct"] is not None) and (__Function["Tangent"] is not None) and (__Function["Adjoint"] is not None):
594             self.__FO["Direct"]  = Operator(
595                 name = self.__name,
596                 fromMethod = __Function["Direct"],
597                 reducingMemoryUse = __reduceM,
598                 avoidingRedundancy = __avoidRC,
599                 inputAsMultiFunction = inputAsMF,
600                 extraArguments = self.__extraArgs,
601                 enableMultiProcess = __Parameters["EnableMultiProcessingInEvaluation"] )
602             self.__FO["Tangent"] = Operator(
603                 name = self.__name+"Tangent",
604                 fromMethod = __Function["Tangent"],
605                 reducingMemoryUse = __reduceM,
606                 avoidingRedundancy = __avoidRC,
607                 inputAsMultiFunction = inputAsMF,
608                 extraArguments = self.__extraArgs )
609             self.__FO["Adjoint"] = Operator(
610                 name = self.__name+"Adjoint",
611                 fromMethod = __Function["Adjoint"],
612                 reducingMemoryUse = __reduceM,
613                 avoidingRedundancy = __avoidRC,
614                 inputAsMultiFunction = inputAsMF,
615                 extraArguments = self.__extraArgs )
616             self.__FO["DifferentialIncrement"] = None
617         elif asMatrix is not None:
618             if isinstance(__Matrix, str):
619                 __Matrix = PlatformInfo.strmatrix2liststr( __Matrix )
620             __matrice = numpy.asarray( __Matrix, dtype=float )
621             self.__FO["Direct"]  = Operator(
622                 name = self.__name,
623                 fromMatrix = __matrice,
624                 reducingMemoryUse = __reduceM,
625                 avoidingRedundancy = __avoidRC,
626                 inputAsMultiFunction = inputAsMF,
627                 enableMultiProcess = __Parameters["EnableMultiProcessingInEvaluation"] )
628             self.__FO["Tangent"] = Operator(
629                 name = self.__name+"Tangent",
630                 fromMatrix = __matrice,
631                 reducingMemoryUse = __reduceM,
632                 avoidingRedundancy = __avoidRC,
633                 inputAsMultiFunction = inputAsMF )
634             self.__FO["Adjoint"] = Operator(
635                 name = self.__name+"Adjoint",
636                 fromMatrix = __matrice.T,
637                 reducingMemoryUse = __reduceM,
638                 avoidingRedundancy = __avoidRC,
639                 inputAsMultiFunction = inputAsMF )
640             del __matrice
641             self.__FO["DifferentialIncrement"] = None
642         else:
643             raise ValueError(
644                 "The %s object is improperly defined or undefined,"%self.__name+\
645                 " it requires at minima either a matrix, a Direct operator for"+\
646                 " approximate derivatives or a Tangent/Adjoint operators pair."+\
647                 " Please check your operator input.")
648         #
649         if __appliedInX is not None:
650             self.__FO["AppliedInX"] = {}
651             for key in __appliedInX:
652                 if isinstance(__appliedInX[key], str):
653                     __appliedInX[key] = PlatformInfo.strvect2liststr( __appliedInX[key] )
654                 self.__FO["AppliedInX"][key] = numpy.ravel( __appliedInX[key] ).reshape((-1,1))
655         else:
656             self.__FO["AppliedInX"] = None
657
658     def getO(self):
659         return self.__FO
660
661     def __repr__(self):
662         "x.__repr__() <==> repr(x)"
663         return repr(self.__FO)
664
665     def __str__(self):
666         "x.__str__() <==> str(x)"
667         return str(self.__FO)
668
669 # ==============================================================================
670 class Algorithm(object):
671     """
672     Classe générale d'interface de type algorithme
673
674     Elle donne un cadre pour l'écriture d'une classe élémentaire d'algorithme
675     d'assimilation, en fournissant un container (dictionnaire) de variables
676     persistantes initialisées, et des méthodes d'accès à ces variables stockées.
677
678     Une classe élémentaire d'algorithme doit implémenter la méthode "run".
679     """
680     __slots__ = (
681         "_name", "_parameters", "__internal_state", "__required_parameters",
682         "_m", "__variable_names_not_public", "__canonical_parameter_name",
683         "__canonical_stored_name", "__replace_by_the_new_name",
684         "StoredVariables",
685         )
686     #
687     def __init__(self, name):
688         """
689         L'initialisation présente permet de fabriquer des variables de stockage
690         disponibles de manière générique dans les algorithmes élémentaires. Ces
691         variables de stockage sont ensuite conservées dans un dictionnaire
692         interne à l'objet, mais auquel on accède par la méthode "get".
693
694         Les variables prévues sont :
695             - APosterioriCorrelations : matrice de corrélations de la matrice A
696             - APosterioriCovariance : matrice de covariances a posteriori : A
697             - APosterioriStandardDeviations : vecteur des écart-types de la matrice A
698             - APosterioriVariances : vecteur des variances de la matrice A
699             - Analysis : vecteur d'analyse : Xa
700             - BMA : Background moins Analysis : Xa - Xb
701             - CostFunctionJ  : fonction-coût globale, somme des deux parties suivantes Jb et Jo
702             - CostFunctionJAtCurrentOptimum : fonction-coût globale à l'état optimal courant lors d'itérations
703             - CostFunctionJb : partie ébauche ou background de la fonction-coût : Jb
704             - CostFunctionJbAtCurrentOptimum : partie ébauche à l'état optimal courant lors d'itérations
705             - CostFunctionJo : partie observations de la fonction-coût : Jo
706             - CostFunctionJoAtCurrentOptimum : partie observations à l'état optimal courant lors d'itérations
707             - CurrentIterationNumber : numéro courant d'itération dans les algorithmes itératifs, à partir de 0
708             - CurrentOptimum : état optimal courant lors d'itérations
709             - CurrentState : état courant lors d'itérations
710             - CurrentStepNumber : pas courant d'avancement dans les algorithmes en évolution, à partir de 0
711             - EnsembleOfSimulations : ensemble d'états (sorties, simulations) rangés par colonne dans une matrice
712             - EnsembleOfSnapshots : ensemble d'états rangés par colonne dans une matrice
713             - EnsembleOfStates : ensemble d'états (entrées, paramètres) rangés par colonne dans une matrice
714             - ForecastCovariance : covariance de l'état prédit courant lors d'itérations
715             - ForecastState : état prédit courant lors d'itérations
716             - GradientOfCostFunctionJ  : gradient de la fonction-coût globale
717             - GradientOfCostFunctionJb : gradient de la partie ébauche de la fonction-coût
718             - GradientOfCostFunctionJo : gradient de la partie observations de la fonction-coût
719             - IndexOfOptimum : index de l'état optimal courant lors d'itérations
720             - Innovation : l'innovation : d = Y - H(X)
721             - InnovationAtCurrentAnalysis : l'innovation à l'état analysé : da = Y - H(Xa)
722             - InnovationAtCurrentState : l'innovation à l'état courant : dn = Y - H(Xn)
723             - InternalCostFunctionJ : ensemble de valeurs internes de fonction-coût J dans un vecteur
724             - InternalCostFunctionJb : ensemble de valeurs internes de fonction-coût Jb dans un vecteur
725             - InternalCostFunctionJb : ensemble de valeurs internes de fonction-coût Jo dans un vecteur
726             - InternalStates : ensemble d'états internes rangés par colonne dans une matrice (=EnsembleOfSnapshots)
727             - JacobianMatrixAtBackground : matrice jacobienne à l'état d'ébauche
728             - JacobianMatrixAtCurrentState : matrice jacobienne à l'état courant
729             - JacobianMatrixAtOptimum : matrice jacobienne à l'optimum
730             - KalmanGainAtOptimum : gain de Kalman à l'optimum
731             - MahalanobisConsistency : indicateur de consistance des covariances
732             - OMA : Observation moins Analyse : Y - Xa
733             - OMB : Observation moins Background : Y - Xb
734             - ReducedCoordinates : coordonnées dans la base réduite
735             - Residu : dans le cas des algorithmes de vérification
736             - SampledStateForQuantiles : échantillons d'états pour l'estimation des quantiles
737             - SigmaBck2 : indicateur de correction optimale des erreurs d'ébauche
738             - SigmaObs2 : indicateur de correction optimale des erreurs d'observation
739             - SimulatedObservationAtBackground : l'état observé H(Xb) à l'ébauche
740             - SimulatedObservationAtCurrentOptimum : l'état observé H(X) à l'état optimal courant
741             - SimulatedObservationAtCurrentState : l'état observé H(X) à l'état courant
742             - SimulatedObservationAtOptimum : l'état observé H(Xa) à l'optimum
743             - SimulationQuantiles : états observés H(X) pour les quantiles demandés
744             - SingularValues : valeurs singulières provenant d'une décomposition SVD
745         On peut rajouter des variables à stocker dans l'initialisation de
746         l'algorithme élémentaire qui va hériter de cette classe
747         """
748         logging.debug("%s Initialisation", str(name))
749         self._m = PlatformInfo.SystemUsage()
750         #
751         self._name = str( name )
752         self._parameters = {"StoreSupplementaryCalculations":[]}
753         self.__internal_state = {}
754         self.__required_parameters = {}
755         self.__required_inputs = {
756             "RequiredInputValues":{"mandatory":(), "optional":()},
757             "ClassificationTags":[],
758             }
759         self.__variable_names_not_public = {"nextStep":False} # Duplication dans AlgorithmAndParameters
760         self.__canonical_parameter_name = {} # Correspondance "lower"->"correct"
761         self.__canonical_stored_name = {}    # Correspondance "lower"->"correct"
762         self.__replace_by_the_new_name = {}  # Nouveau nom à partir d'un nom ancien
763         #
764         self.StoredVariables = {}
765         self.StoredVariables["APosterioriCorrelations"]              = Persistence.OneMatrix(name = "APosterioriCorrelations")
766         self.StoredVariables["APosterioriCovariance"]                = Persistence.OneMatrix(name = "APosterioriCovariance")
767         self.StoredVariables["APosterioriStandardDeviations"]        = Persistence.OneVector(name = "APosterioriStandardDeviations")
768         self.StoredVariables["APosterioriVariances"]                 = Persistence.OneVector(name = "APosterioriVariances")
769         self.StoredVariables["Analysis"]                             = Persistence.OneVector(name = "Analysis")
770         self.StoredVariables["BMA"]                                  = Persistence.OneVector(name = "BMA")
771         self.StoredVariables["CostFunctionJ"]                        = Persistence.OneScalar(name = "CostFunctionJ")
772         self.StoredVariables["CostFunctionJAtCurrentOptimum"]        = Persistence.OneScalar(name = "CostFunctionJAtCurrentOptimum")
773         self.StoredVariables["CostFunctionJb"]                       = Persistence.OneScalar(name = "CostFunctionJb")
774         self.StoredVariables["CostFunctionJbAtCurrentOptimum"]       = Persistence.OneScalar(name = "CostFunctionJbAtCurrentOptimum")
775         self.StoredVariables["CostFunctionJo"]                       = Persistence.OneScalar(name = "CostFunctionJo")
776         self.StoredVariables["CostFunctionJoAtCurrentOptimum"]       = Persistence.OneScalar(name = "CostFunctionJoAtCurrentOptimum")
777         self.StoredVariables["CurrentEnsembleState"]                 = Persistence.OneMatrix(name = "CurrentEnsembleState")
778         self.StoredVariables["CurrentIterationNumber"]               = Persistence.OneIndex(name  = "CurrentIterationNumber")
779         self.StoredVariables["CurrentOptimum"]                       = Persistence.OneVector(name = "CurrentOptimum")
780         self.StoredVariables["CurrentState"]                         = Persistence.OneVector(name = "CurrentState")
781         self.StoredVariables["CurrentStepNumber"]                    = Persistence.OneIndex(name  = "CurrentStepNumber")
782         self.StoredVariables["EnsembleOfSimulations"]                = Persistence.OneMatrice(name = "EnsembleOfSimulations")
783         self.StoredVariables["EnsembleOfSnapshots"]                  = Persistence.OneMatrice(name = "EnsembleOfSnapshots")
784         self.StoredVariables["EnsembleOfStates"]                     = Persistence.OneMatrice(name = "EnsembleOfStates")
785         self.StoredVariables["ExcludedPoints"]                       = Persistence.OneVector(name = "ExcludedPoints")
786         self.StoredVariables["ForecastCovariance"]                   = Persistence.OneMatrix(name = "ForecastCovariance")
787         self.StoredVariables["ForecastState"]                        = Persistence.OneVector(name = "ForecastState")
788         self.StoredVariables["GradientOfCostFunctionJ"]              = Persistence.OneVector(name = "GradientOfCostFunctionJ")
789         self.StoredVariables["GradientOfCostFunctionJb"]             = Persistence.OneVector(name = "GradientOfCostFunctionJb")
790         self.StoredVariables["GradientOfCostFunctionJo"]             = Persistence.OneVector(name = "GradientOfCostFunctionJo")
791         self.StoredVariables["IndexOfOptimum"]                       = Persistence.OneIndex(name  = "IndexOfOptimum")
792         self.StoredVariables["Innovation"]                           = Persistence.OneVector(name = "Innovation")
793         self.StoredVariables["InnovationAtCurrentAnalysis"]          = Persistence.OneVector(name = "InnovationAtCurrentAnalysis")
794         self.StoredVariables["InnovationAtCurrentState"]             = Persistence.OneVector(name = "InnovationAtCurrentState")
795         self.StoredVariables["InternalCostFunctionJ"]                = Persistence.OneVector(name = "InternalCostFunctionJ")
796         self.StoredVariables["InternalCostFunctionJb"]               = Persistence.OneVector(name = "InternalCostFunctionJb")
797         self.StoredVariables["InternalCostFunctionJo"]               = Persistence.OneVector(name = "InternalCostFunctionJo")
798         self.StoredVariables["InternalStates"]                       = Persistence.OneMatrix(name = "InternalStates")
799         self.StoredVariables["JacobianMatrixAtBackground"]           = Persistence.OneMatrix(name = "JacobianMatrixAtBackground")
800         self.StoredVariables["JacobianMatrixAtCurrentState"]         = Persistence.OneMatrix(name = "JacobianMatrixAtCurrentState")
801         self.StoredVariables["JacobianMatrixAtOptimum"]              = Persistence.OneMatrix(name = "JacobianMatrixAtOptimum")
802         self.StoredVariables["KalmanGainAtOptimum"]                  = Persistence.OneMatrix(name = "KalmanGainAtOptimum")
803         self.StoredVariables["MahalanobisConsistency"]               = Persistence.OneScalar(name = "MahalanobisConsistency")
804         self.StoredVariables["OMA"]                                  = Persistence.OneVector(name = "OMA")
805         self.StoredVariables["OMB"]                                  = Persistence.OneVector(name = "OMB")
806         self.StoredVariables["OptimalPoints"]                        = Persistence.OneVector(name = "OptimalPoints")
807         self.StoredVariables["ReducedBasis"]                         = Persistence.OneMatrix(name = "ReducedBasis")
808         self.StoredVariables["ReducedCoordinates"]                   = Persistence.OneVector(name = "ReducedCoordinates")
809         self.StoredVariables["Residu"]                               = Persistence.OneScalar(name = "Residu")
810         self.StoredVariables["Residus"]                              = Persistence.OneVector(name = "Residus")
811         self.StoredVariables["SampledStateForQuantiles"]             = Persistence.OneMatrix(name = "SampledStateForQuantiles")
812         self.StoredVariables["SigmaBck2"]                            = Persistence.OneScalar(name = "SigmaBck2")
813         self.StoredVariables["SigmaObs2"]                            = Persistence.OneScalar(name = "SigmaObs2")
814         self.StoredVariables["SimulatedObservationAtBackground"]     = Persistence.OneVector(name = "SimulatedObservationAtBackground")
815         self.StoredVariables["SimulatedObservationAtCurrentAnalysis"]= Persistence.OneVector(name = "SimulatedObservationAtCurrentAnalysis")
816         self.StoredVariables["SimulatedObservationAtCurrentOptimum"] = Persistence.OneVector(name = "SimulatedObservationAtCurrentOptimum")
817         self.StoredVariables["SimulatedObservationAtCurrentState"]   = Persistence.OneVector(name = "SimulatedObservationAtCurrentState")
818         self.StoredVariables["SimulatedObservationAtOptimum"]        = Persistence.OneVector(name = "SimulatedObservationAtOptimum")
819         self.StoredVariables["SimulationQuantiles"]                  = Persistence.OneMatrix(name = "SimulationQuantiles")
820         self.StoredVariables["SingularValues"]                       = Persistence.OneVector(name = "SingularValues")
821         #
822         for k in self.StoredVariables:
823             self.__canonical_stored_name[k.lower()] = k
824         #
825         for k, v in self.__variable_names_not_public.items():
826             self.__canonical_parameter_name[k.lower()] = k
827         self.__canonical_parameter_name["algorithm"] = "Algorithm"
828         self.__canonical_parameter_name["storesupplementarycalculations"] = "StoreSupplementaryCalculations"
829
830     def _pre_run(self, Parameters, Xb=None, Y=None, U=None, HO=None, EM=None, CM=None, R=None, B=None, Q=None ):
831         "Pré-calcul"
832         logging.debug("%s Lancement", self._name)
833         logging.debug("%s Taille mémoire utilisée de %.0f Mio"%(self._name, self._m.getUsedMemory("Mio")))
834         self._getTimeState(reset=True)
835         #
836         # Mise à jour des paramètres internes avec le contenu de Parameters, en
837         # reprenant les valeurs par défauts pour toutes celles non définies
838         self.__setParameters(Parameters, reset=True) # Copie
839         for k, v in self.__variable_names_not_public.items():
840             if k not in self._parameters:  self.__setParameters( {k:v} )
841
842         # Corrections et compléments des vecteurs
843         def __test_vvalue(argument, variable, argname, symbol=None):
844             if symbol is None: symbol = variable
845             if argument is None:
846                 if variable in self.__required_inputs["RequiredInputValues"]["mandatory"]:
847                     raise ValueError("%s %s vector %s is not set and has to be properly defined!"%(self._name,argname,symbol))
848                 elif variable in self.__required_inputs["RequiredInputValues"]["optional"]:
849                     logging.debug("%s %s vector %s is not set, but is optional."%(self._name,argname,symbol))
850                 else:
851                     logging.debug("%s %s vector %s is not set, but is not required."%(self._name,argname,symbol))
852             else:
853                 if variable in self.__required_inputs["RequiredInputValues"]["mandatory"]:
854                     logging.debug(
855                         "%s %s vector %s is required and set, and its size is %i."%(
856                         self._name,argname,symbol,numpy.array(argument).size))
857                 elif variable in self.__required_inputs["RequiredInputValues"]["optional"]:
858                     logging.debug(
859                         "%s %s vector %s is optional and set, and its size is %i."%(
860                         self._name,argname,symbol,numpy.array(argument).size))
861                 else:
862                     logging.debug(
863                         "%s %s vector %s is set although neither required nor optional, and its size is %i."%(
864                         self._name,argname,symbol,numpy.array(argument).size))
865             return 0
866         __test_vvalue( Xb, "Xb", "Background or initial state" )
867         __test_vvalue( Y,  "Y",  "Observation" )
868         __test_vvalue( U,  "U",  "Control" )
869         #
870         # Corrections et compléments des covariances
871         def __test_cvalue(argument, variable, argname, symbol=None):
872             if symbol is None: symbol = variable
873             if argument is None:
874                 if variable in self.__required_inputs["RequiredInputValues"]["mandatory"]:
875                     raise ValueError("%s %s error covariance matrix %s is not set and has to be properly defined!"%(self._name,argname,symbol))
876                 elif variable in self.__required_inputs["RequiredInputValues"]["optional"]:
877                     logging.debug("%s %s error covariance matrix %s is not set, but is optional."%(self._name,argname,symbol))
878                 else:
879                     logging.debug("%s %s error covariance matrix %s is not set, but is not required."%(self._name,argname,symbol))
880             else:
881                 if variable in self.__required_inputs["RequiredInputValues"]["mandatory"]:
882                     logging.debug("%s %s error covariance matrix %s is required and set."%(self._name,argname,symbol))
883                 elif variable in self.__required_inputs["RequiredInputValues"]["optional"]:
884                     logging.debug("%s %s error covariance matrix %s is optional and set."%(self._name,argname,symbol))
885                 else:
886                     logging.debug(
887                         "%s %s error covariance matrix %s is set although neither required nor optional."%(
888                         self._name,argname,symbol))
889             return 0
890         __test_cvalue( B, "B", "Background" )
891         __test_cvalue( R, "R", "Observation" )
892         __test_cvalue( Q, "Q", "Evolution" )
893         #
894         # Corrections et compléments des opérateurs
895         def __test_ovalue(argument, variable, argname, symbol=None):
896             if symbol is None: symbol = variable
897             if argument is None or (isinstance(argument,dict) and len(argument)==0):
898                 if variable in self.__required_inputs["RequiredInputValues"]["mandatory"]:
899                     raise ValueError("%s %s operator %s is not set and has to be properly defined!"%(self._name,argname,symbol))
900                 elif variable in self.__required_inputs["RequiredInputValues"]["optional"]:
901                     logging.debug("%s %s operator %s is not set, but is optional."%(self._name,argname,symbol))
902                 else:
903                     logging.debug("%s %s operator %s is not set, but is not required."%(self._name,argname,symbol))
904             else:
905                 if variable in self.__required_inputs["RequiredInputValues"]["mandatory"]:
906                     logging.debug("%s %s operator %s is required and set."%(self._name,argname,symbol))
907                 elif variable in self.__required_inputs["RequiredInputValues"]["optional"]:
908                     logging.debug("%s %s operator %s is optional and set."%(self._name,argname,symbol))
909                 else:
910                     logging.debug("%s %s operator %s is set although neither required nor optional."%(self._name,argname,symbol))
911             return 0
912         __test_ovalue( HO, "HO", "Observation", "H" )
913         __test_ovalue( EM, "EM", "Evolution", "M" )
914         __test_ovalue( CM, "CM", "Control Model", "C" )
915         #
916         # Corrections et compléments des bornes
917         if ("Bounds" in self._parameters) and isinstance(self._parameters["Bounds"], (list, tuple)) and (len(self._parameters["Bounds"]) > 0):
918             logging.debug("%s Bounds taken into account"%(self._name,))
919         else:
920             self._parameters["Bounds"] = None
921         if ("StateBoundsForQuantiles" in self._parameters) \
922             and isinstance(self._parameters["StateBoundsForQuantiles"], (list, tuple)) \
923             and (len(self._parameters["StateBoundsForQuantiles"]) > 0):
924             logging.debug("%s Bounds for quantiles states taken into account"%(self._name,))
925             # Attention : contrairement à Bounds, pas de défaut à None, sinon on ne peut pas être sans bornes
926         #
927         # Corrections et compléments de l'initialisation en X
928         if  "InitializationPoint" in self._parameters:
929             if Xb is not None:
930                 if self._parameters["InitializationPoint"] is not None and hasattr(self._parameters["InitializationPoint"],'size'):
931                     if self._parameters["InitializationPoint"].size != numpy.ravel(Xb).size:
932                         raise ValueError("Incompatible size %i of forced initial point that have to replace the background of size %i" \
933                             %(self._parameters["InitializationPoint"].size,numpy.ravel(Xb).size))
934                     # Obtenu par typecast : numpy.ravel(self._parameters["InitializationPoint"])
935                 else:
936                     self._parameters["InitializationPoint"] = numpy.ravel(Xb)
937             else:
938                 if self._parameters["InitializationPoint"] is None:
939                     raise ValueError("Forced initial point can not be set without any given Background or required value")
940         #
941         # Correction pour pallier a un bug de TNC sur le retour du Minimum
942         if "Minimizer" in self._parameters and self._parameters["Minimizer"] == "TNC":
943             self.setParameterValue("StoreInternalVariables",True)
944         #
945         # Verbosité et logging
946         if logging.getLogger().level < logging.WARNING:
947             self._parameters["optiprint"], self._parameters["optdisp"] = 1, 1
948             self._parameters["optmessages"] = 15
949         else:
950             self._parameters["optiprint"], self._parameters["optdisp"] = -1, 0
951             self._parameters["optmessages"] = 0
952         #
953         return 0
954
955     def _post_run(self,_oH=None):
956         "Post-calcul"
957         if ("StoreSupplementaryCalculations" in self._parameters) and \
958             "APosterioriCovariance" in self._parameters["StoreSupplementaryCalculations"]:
959             for _A in self.StoredVariables["APosterioriCovariance"]:
960                 if "APosterioriVariances" in self._parameters["StoreSupplementaryCalculations"]:
961                     self.StoredVariables["APosterioriVariances"].store( numpy.diag(_A) )
962                 if "APosterioriStandardDeviations" in self._parameters["StoreSupplementaryCalculations"]:
963                     self.StoredVariables["APosterioriStandardDeviations"].store( numpy.sqrt(numpy.diag(_A)) )
964                 if "APosterioriCorrelations" in self._parameters["StoreSupplementaryCalculations"]:
965                     _EI = numpy.diag(1./numpy.sqrt(numpy.diag(_A)))
966                     _C = numpy.dot(_EI, numpy.dot(_A, _EI))
967                     self.StoredVariables["APosterioriCorrelations"].store( _C )
968         if _oH is not None and "Direct" in _oH and "Tangent" in _oH and "Adjoint" in _oH:
969             logging.debug(
970                 "%s Nombre d'évaluation(s) de l'opérateur d'observation direct/tangent/adjoint.: %i/%i/%i",
971                 self._name, _oH["Direct"].nbcalls(0),_oH["Tangent"].nbcalls(0),_oH["Adjoint"].nbcalls(0))
972             logging.debug(
973                 "%s Nombre d'appels au cache d'opérateur d'observation direct/tangent/adjoint..: %i/%i/%i",
974                 self._name, _oH["Direct"].nbcalls(3),_oH["Tangent"].nbcalls(3),_oH["Adjoint"].nbcalls(3))
975         logging.debug("%s Taille mémoire utilisée de %.0f Mio", self._name, self._m.getUsedMemory("Mio"))
976         logging.debug("%s Durées d'utilisation CPU de %.1fs et elapsed de %.1fs", self._name, self._getTimeState()[0], self._getTimeState()[1])
977         logging.debug("%s Terminé", self._name)
978         return 0
979
980     def _toStore(self, key):
981         "True if in StoreSupplementaryCalculations, else False"
982         return key in self._parameters["StoreSupplementaryCalculations"]
983
984     def get(self, key=None):
985         """
986         Renvoie l'une des variables stockées identifiée par la clé, ou le
987         dictionnaire de l'ensemble des variables disponibles en l'absence de
988         clé. Ce sont directement les variables sous forme objet qui sont
989         renvoyées, donc les méthodes d'accès à l'objet individuel sont celles
990         des classes de persistance.
991         """
992         if key is not None:
993             return self.StoredVariables[self.__canonical_stored_name[key.lower()]]
994         else:
995             return self.StoredVariables
996
997     def __contains__(self, key=None):
998         "D.__contains__(k) -> True if D has a key k, else False"
999         if key is None or key.lower() not in self.__canonical_stored_name:
1000             return False
1001         else:
1002             return self.__canonical_stored_name[key.lower()] in self.StoredVariables
1003
1004     def keys(self):
1005         "D.keys() -> list of D's keys"
1006         if hasattr(self, "StoredVariables"):
1007             return self.StoredVariables.keys()
1008         else:
1009             return []
1010
1011     def pop(self, k, d):
1012         "D.pop(k[,d]) -> v, remove specified key and return the corresponding value"
1013         if hasattr(self, "StoredVariables") and k.lower() in self.__canonical_stored_name:
1014             return self.StoredVariables.pop(self.__canonical_stored_name[k.lower()], d)
1015         else:
1016             try:
1017                 msg = "'%s'"%k
1018             except Exception:
1019                 raise TypeError("pop expected at least 1 arguments, got 0")
1020             "If key is not found, d is returned if given, otherwise KeyError is raised"
1021             try:
1022                 return d
1023             except Exception:
1024                 raise KeyError(msg)
1025
1026     def run(self, Xb=None, Y=None, U=None, HO=None, EM=None, CM=None, R=None, B=None, Q=None, Parameters=None):
1027         """
1028         Doit implémenter l'opération élémentaire de calcul algorithmique.
1029         """
1030         raise NotImplementedError("Mathematical algorithmic calculation has not been implemented!")
1031
1032     def defineRequiredParameter(self,
1033         name     = None,
1034         default  = None,
1035         typecast = None,
1036         message  = None,
1037         minval   = None,
1038         maxval   = None,
1039         listval  = None,
1040         listadv  = None,
1041         oldname  = None,
1042         ):
1043         """
1044         Permet de définir dans l'algorithme des paramètres requis et leurs
1045         caractéristiques par défaut.
1046         """
1047         if name is None:
1048             raise ValueError("A name is mandatory to define a required parameter.")
1049         #
1050         self.__required_parameters[name] = {
1051             "default"  : default,
1052             "typecast" : typecast,
1053             "minval"   : minval,
1054             "maxval"   : maxval,
1055             "listval"  : listval,
1056             "listadv"  : listadv,
1057             "message"  : message,
1058             "oldname"  : oldname,
1059             }
1060         self.__canonical_parameter_name[name.lower()] = name
1061         if oldname is not None:
1062             self.__canonical_parameter_name[oldname.lower()] = name # Conversion
1063             self.__replace_by_the_new_name[oldname.lower()] = name
1064         logging.debug("%s %s (valeur par défaut = %s)", self._name, message, self.setParameterValue(name))
1065
1066     def getRequiredParameters(self, noDetails=True):
1067         """
1068         Renvoie la liste des noms de paramètres requis ou directement le
1069         dictionnaire des paramètres requis.
1070         """
1071         if noDetails:
1072             return sorted(self.__required_parameters.keys())
1073         else:
1074             return self.__required_parameters
1075
1076     def setParameterValue(self, name=None, value=None):
1077         """
1078         Renvoie la valeur d'un paramètre requis de manière contrôlée
1079         """
1080         __k = self.__canonical_parameter_name[name.lower()]
1081         default  = self.__required_parameters[__k]["default"]
1082         typecast = self.__required_parameters[__k]["typecast"]
1083         minval   = self.__required_parameters[__k]["minval"]
1084         maxval   = self.__required_parameters[__k]["maxval"]
1085         listval  = self.__required_parameters[__k]["listval"]
1086         listadv  = self.__required_parameters[__k]["listadv"]
1087         #
1088         if value is None and default is None:
1089             __val = None
1090         elif value is None and default is not None:
1091             if typecast is None: __val = default
1092             else:                __val = typecast( default )
1093         else:
1094             if typecast is None: __val = value
1095             else:
1096                 try:
1097                     __val = typecast( value )
1098                 except Exception:
1099                     raise ValueError("The value '%s' for the parameter named '%s' can not be correctly evaluated with type '%s'."%(value, __k, typecast))
1100         #
1101         if minval is not None and (numpy.array(__val, float) < minval).any():
1102             raise ValueError("The parameter named '%s' of value '%s' can not be less than %s."%(__k, __val, minval))
1103         if maxval is not None and (numpy.array(__val, float) > maxval).any():
1104             raise ValueError("The parameter named '%s' of value '%s' can not be greater than %s."%(__k, __val, maxval))
1105         if listval is not None or listadv is not None:
1106             if typecast is list or typecast is tuple or isinstance(__val,list) or isinstance(__val,tuple):
1107                 for v in __val:
1108                     if listval is not None and v in listval: continue
1109                     elif listadv is not None and v in listadv: continue
1110                     else:
1111                         raise ValueError("The value '%s' is not allowed for the parameter named '%s', it has to be in the list %s."%(v, __k, listval))
1112             elif not (listval is not None and __val in listval) and not (listadv is not None and __val in listadv):
1113                 raise ValueError("The value '%s' is not allowed for the parameter named '%s', it has to be in the list %s."%( __val, __k,listval))
1114         #
1115         if __k in ["SetSeed",]:
1116             __val = value
1117         #
1118         return __val
1119
1120     def requireInputArguments(self, mandatory=(), optional=()):
1121         """
1122         Permet d'imposer des arguments de calcul requis en entrée.
1123         """
1124         self.__required_inputs["RequiredInputValues"]["mandatory"] = tuple( mandatory )
1125         self.__required_inputs["RequiredInputValues"]["optional"]  = tuple( optional )
1126
1127     def getInputArguments(self):
1128         """
1129         Permet d'obtenir les listes des arguments de calcul requis en entrée.
1130         """
1131         return self.__required_inputs["RequiredInputValues"]["mandatory"], self.__required_inputs["RequiredInputValues"]["optional"]
1132
1133     def setAttributes(self, tags=()):
1134         """
1135         Permet d'adjoindre des attributs comme les tags de classification.
1136         Renvoie la liste actuelle dans tous les cas.
1137         """
1138         self.__required_inputs["ClassificationTags"].extend( tags )
1139         return self.__required_inputs["ClassificationTags"]
1140
1141     def __setParameters(self, fromDico={}, reset=False):
1142         """
1143         Permet de stocker les paramètres reçus dans le dictionnaire interne.
1144         """
1145         self._parameters.update( fromDico )
1146         __inverse_fromDico_keys = {}
1147         for k in fromDico.keys():
1148             if k.lower() in self.__canonical_parameter_name:
1149                 __inverse_fromDico_keys[self.__canonical_parameter_name[k.lower()]] = k
1150         #~ __inverse_fromDico_keys = dict([(self.__canonical_parameter_name[k.lower()],k) for k in fromDico.keys()])
1151         __canonic_fromDico_keys = __inverse_fromDico_keys.keys()
1152         #
1153         for k in __inverse_fromDico_keys.values():
1154             if k.lower() in self.__replace_by_the_new_name:
1155                 __newk = self.__replace_by_the_new_name[k.lower()]
1156                 __msg  = "the parameter \"%s\" used in \"%s\" algorithm case is deprecated and has to be replaced by \"%s\"."%(k,self._name,__newk)
1157                 __msg += " Please update your code."
1158                 warnings.warn(__msg, FutureWarning, stacklevel=50)
1159         #
1160         for k in self.__required_parameters.keys():
1161             if k in __canonic_fromDico_keys:
1162                 self._parameters[k] = self.setParameterValue(k,fromDico[__inverse_fromDico_keys[k]])
1163             elif reset:
1164                 self._parameters[k] = self.setParameterValue(k)
1165             else:
1166                 pass
1167             if hasattr(self._parameters[k],"size") and self._parameters[k].size > 100:
1168                 logging.debug("%s %s d'une taille totale de %s", self._name, self.__required_parameters[k]["message"], self._parameters[k].size)
1169             elif hasattr(self._parameters[k],"__len__") and len(self._parameters[k]) > 100:
1170                 logging.debug("%s %s de longueur %s", self._name, self.__required_parameters[k]["message"], len(self._parameters[k]))
1171             else:
1172                 logging.debug("%s %s : %s", self._name, self.__required_parameters[k]["message"], self._parameters[k])
1173
1174     def _setInternalState(self, key=None, value=None, fromDico={}, reset=False):
1175         """
1176         Permet de stocker des variables nommées constituant l'état interne
1177         """
1178         if reset: # Vide le dictionnaire préalablement
1179             self.__internal_state = {}
1180         if key is not None and value is not None:
1181             self.__internal_state[key] = value
1182         self.__internal_state.update( dict(fromDico) )
1183
1184     def _getInternalState(self, key=None):
1185         """
1186         Restitue un état interne sous la forme d'un dictionnaire de variables nommées
1187         """
1188         if key is not None and key in self.__internal_state:
1189             return self.__internal_state[key]
1190         else:
1191             return self.__internal_state
1192
1193     def _getTimeState(self, reset=False):
1194         """
1195         Initialise ou restitue le temps de calcul (cpu/elapsed) à la seconde
1196         """
1197         if reset:
1198             self.__initial_cpu_time      = time.process_time()
1199             self.__initial_elapsed_time  = time.perf_counter()
1200             return 0., 0.
1201         else:
1202             self.__cpu_time     = time.process_time() - self.__initial_cpu_time
1203             self.__elapsed_time = time.perf_counter() - self.__initial_elapsed_time
1204             return self.__cpu_time, self.__elapsed_time
1205
1206     def _StopOnTimeLimit(self, X=None, withReason=False):
1207         "Stop criteria on time limit: True/False [+ Reason]"
1208         c, e = self._getTimeState()
1209         if "MaximumCpuTime" in self._parameters and c > self._parameters["MaximumCpuTime"]:
1210             __SC, __SR = True, "Reached maximum CPU time (%.1fs > %.1fs)"%(c, self._parameters["MaximumCpuTime"])
1211         elif "MaximumElapsedTime" in self._parameters and e > self._parameters["MaximumElapsedTime"]:
1212             __SC, __SR = True, "Reached maximum elapsed time (%.1fs > %.1fs)"%(e, self._parameters["MaximumElapsedTime"])
1213         else:
1214             __SC, __SR = False, ""
1215         if withReason:
1216             return __SC, __SR
1217         else:
1218             return __SC
1219
1220 # ==============================================================================
1221 class PartialAlgorithm(object):
1222     """
1223     Classe pour mimer "Algorithm" du point de vue stockage, mais sans aucune
1224     action avancée comme la vérification . Pour les méthodes reprises ici,
1225     le fonctionnement est identique à celles de la classe "Algorithm".
1226     """
1227     __slots__ = (
1228         "_name", "_parameters", "StoredVariables", "__canonical_stored_name",
1229         )
1230     #
1231     def __init__(self, name):
1232         self._name = str( name )
1233         self._parameters = {"StoreSupplementaryCalculations":[]}
1234         #
1235         self.StoredVariables = {}
1236         self.StoredVariables["Analysis"]                             = Persistence.OneVector(name = "Analysis")
1237         self.StoredVariables["CostFunctionJ"]                        = Persistence.OneScalar(name = "CostFunctionJ")
1238         self.StoredVariables["CostFunctionJb"]                       = Persistence.OneScalar(name = "CostFunctionJb")
1239         self.StoredVariables["CostFunctionJo"]                       = Persistence.OneScalar(name = "CostFunctionJo")
1240         self.StoredVariables["CurrentIterationNumber"]               = Persistence.OneIndex(name  = "CurrentIterationNumber")
1241         self.StoredVariables["CurrentStepNumber"]                    = Persistence.OneIndex(name  = "CurrentStepNumber")
1242         #
1243         self.__canonical_stored_name = {}
1244         for k in self.StoredVariables:
1245             self.__canonical_stored_name[k.lower()] = k
1246
1247     def _toStore(self, key):
1248         "True if in StoreSupplementaryCalculations, else False"
1249         return key in self._parameters["StoreSupplementaryCalculations"]
1250
1251     def get(self, key=None):
1252         """
1253         Renvoie l'une des variables stockées identifiée par la clé, ou le
1254         dictionnaire de l'ensemble des variables disponibles en l'absence de
1255         clé. Ce sont directement les variables sous forme objet qui sont
1256         renvoyées, donc les méthodes d'accès à l'objet individuel sont celles
1257         des classes de persistance.
1258         """
1259         if key is not None:
1260             return self.StoredVariables[self.__canonical_stored_name[key.lower()]]
1261         else:
1262             return self.StoredVariables
1263
1264 # ==============================================================================
1265 class AlgorithmAndParameters(object):
1266     """
1267     Classe générale d'interface d'action pour l'algorithme et ses paramètres
1268     """
1269     __slots__ = (
1270         "__name", "__algorithm", "__algorithmFile", "__algorithmName", "__A",
1271         "__P", "__Xb", "__Y", "__U", "__HO", "__EM", "__CM", "__B", "__R",
1272         "__Q", "__variable_names_not_public",
1273         )
1274     #
1275     def __init__(self,
1276                  name               = "GenericAlgorithm",
1277                  asAlgorithm        = None,
1278                  asDict             = None,
1279                  asScript           = None,
1280                 ):
1281         """
1282         """
1283         self.__name       = str(name)
1284         self.__A          = None
1285         self.__P          = {}
1286         #
1287         self.__algorithm         = {}
1288         self.__algorithmFile     = None
1289         self.__algorithmName     = None
1290         #
1291         self.updateParameters( asDict, asScript )
1292         #
1293         if asAlgorithm is None and asScript is not None:
1294             __Algo = Interfaces.ImportFromScript(asScript).getvalue( "Algorithm" )
1295         else:
1296             __Algo = asAlgorithm
1297         #
1298         if __Algo is not None:
1299             self.__A = str(__Algo)
1300             self.__P.update( {"Algorithm":self.__A} )
1301         #
1302         self.__setAlgorithm( self.__A )
1303         #
1304         self.__variable_names_not_public = {"nextStep":False} # Duplication dans Algorithm
1305
1306     def updateParameters(self,
1307                  asDict     = None,
1308                  asScript   = None,
1309                 ):
1310         "Mise à jour des paramètres"
1311         if asDict is None and asScript is not None:
1312             __Dict = Interfaces.ImportFromScript(asScript).getvalue( self.__name, "Parameters" )
1313         else:
1314             __Dict = asDict
1315         #
1316         if __Dict is not None:
1317             self.__P.update( dict(__Dict) )
1318
1319     def executePythonScheme(self, asDictAO = None):
1320         "Permet de lancer le calcul d'assimilation"
1321         Operator.CM.clearCache()
1322         #
1323         if not isinstance(asDictAO, dict):
1324             raise ValueError("The objects for algorithm calculation have to be given together as a dictionnary, and they are not")
1325         if   hasattr(asDictAO["Background"],"getO"):        self.__Xb = asDictAO["Background"].getO()
1326         elif hasattr(asDictAO["CheckingPoint"],"getO"):     self.__Xb = asDictAO["CheckingPoint"].getO()
1327         else:                                               self.__Xb = None
1328         if hasattr(asDictAO["Observation"],"getO"):         self.__Y  = asDictAO["Observation"].getO()
1329         else:                                               self.__Y  = asDictAO["Observation"]
1330         if hasattr(asDictAO["ControlInput"],"getO"):        self.__U  = asDictAO["ControlInput"].getO()
1331         else:                                               self.__U  = asDictAO["ControlInput"]
1332         if hasattr(asDictAO["ObservationOperator"],"getO"): self.__HO = asDictAO["ObservationOperator"].getO()
1333         else:                                               self.__HO = asDictAO["ObservationOperator"]
1334         if hasattr(asDictAO["EvolutionModel"],"getO"):      self.__EM = asDictAO["EvolutionModel"].getO()
1335         else:                                               self.__EM = asDictAO["EvolutionModel"]
1336         if hasattr(asDictAO["ControlModel"],"getO"):        self.__CM = asDictAO["ControlModel"].getO()
1337         else:                                               self.__CM = asDictAO["ControlModel"]
1338         self.__B = asDictAO["BackgroundError"]
1339         self.__R = asDictAO["ObservationError"]
1340         self.__Q = asDictAO["EvolutionError"]
1341         #
1342         self.__shape_validate()
1343         #
1344         self.__algorithm.run(
1345             Xb         = self.__Xb,
1346             Y          = self.__Y,
1347             U          = self.__U,
1348             HO         = self.__HO,
1349             EM         = self.__EM,
1350             CM         = self.__CM,
1351             R          = self.__R,
1352             B          = self.__B,
1353             Q          = self.__Q,
1354             Parameters = self.__P,
1355             )
1356         return 0
1357
1358     def executeYACSScheme(self, FileName=None):
1359         "Permet de lancer le calcul d'assimilation"
1360         if FileName is None or not os.path.exists(FileName):
1361             raise ValueError("a YACS file name has to be given for YACS execution.\n")
1362         else:
1363             __file    = os.path.abspath(FileName)
1364             logging.debug("The YACS file name is \"%s\"."%__file)
1365         if not PlatformInfo.has_salome or \
1366             not PlatformInfo.has_yacs or \
1367             not PlatformInfo.has_adao:
1368             raise ImportError("\n\n"+\
1369                 "Unable to get SALOME, YACS or ADAO environnement variables.\n"+\
1370                 "Please load the right environnement before trying to use it.\n")
1371         #
1372         import pilot
1373         import SALOMERuntime
1374         import loader
1375         SALOMERuntime.RuntimeSALOME_setRuntime()
1376
1377         r = pilot.getRuntime()
1378         xmlLoader = loader.YACSLoader()
1379         xmlLoader.registerProcCataLoader()
1380         try:
1381             catalogAd = r.loadCatalog("proc", __file)
1382             r.addCatalog(catalogAd)
1383         except Exception:
1384             pass
1385
1386         try:
1387             p = xmlLoader.load(__file)
1388         except IOError as ex:
1389             print("The YACS XML schema file can not be loaded: %s"%(ex,))
1390
1391         logger = p.getLogger("parser")
1392         if not logger.isEmpty():
1393             print("The imported YACS XML schema has errors on parsing:")
1394             print(logger.getStr())
1395
1396         if not p.isValid():
1397             print("The YACS XML schema is not valid and will not be executed:")
1398             print(p.getErrorReport())
1399
1400         info=pilot.LinkInfo(pilot.LinkInfo.ALL_DONT_STOP)
1401         p.checkConsistency(info)
1402         if info.areWarningsOrErrors():
1403             print("The YACS XML schema is not coherent and will not be executed:")
1404             print(info.getGlobalRepr())
1405
1406         e = pilot.ExecutorSwig()
1407         e.RunW(p)
1408         if p.getEffectiveState() != pilot.DONE:
1409             print(p.getErrorReport())
1410         #
1411         return 0
1412
1413     def get(self, key = None):
1414         "Vérifie l'existence d'une clé de variable ou de paramètres"
1415         if key in self.__algorithm:
1416             return self.__algorithm.get( key )
1417         elif key in self.__P:
1418             return self.__P[key]
1419         else:
1420             allvariables = self.__P
1421             for k in self.__variable_names_not_public: allvariables.pop(k, None)
1422             return allvariables
1423
1424     def pop(self, k, d):
1425         "Necessaire pour le pickling"
1426         return self.__algorithm.pop(k, d)
1427
1428     def getAlgorithmRequiredParameters(self, noDetails=True):
1429         "Renvoie la liste des paramètres requis selon l'algorithme"
1430         return self.__algorithm.getRequiredParameters(noDetails)
1431
1432     def getAlgorithmInputArguments(self):
1433         "Renvoie la liste des entrées requises selon l'algorithme"
1434         return self.__algorithm.getInputArguments()
1435
1436     def getAlgorithmAttributes(self):
1437         "Renvoie la liste des attributs selon l'algorithme"
1438         return self.__algorithm.setAttributes()
1439
1440     def setObserver(self, __V, __O, __I, __S):
1441         if self.__algorithm is None \
1442             or isinstance(self.__algorithm, dict) \
1443             or not hasattr(self.__algorithm,"StoredVariables"):
1444             raise ValueError("No observer can be build before choosing an algorithm.")
1445         if __V not in self.__algorithm:
1446             raise ValueError("An observer requires to be set on a variable named %s which does not exist."%__V)
1447         else:
1448             self.__algorithm.StoredVariables[ __V ].setDataObserver(
1449                     Scheduler      = __S,
1450                     HookFunction   = __O,
1451                     HookParameters = __I,
1452                     )
1453
1454     def removeObserver(self, __V, __O, __A = False):
1455         if self.__algorithm is None \
1456             or isinstance(self.__algorithm, dict) \
1457             or not hasattr(self.__algorithm,"StoredVariables"):
1458             raise ValueError("No observer can be removed before choosing an algorithm.")
1459         if __V not in self.__algorithm:
1460             raise ValueError("An observer requires to be removed on a variable named %s which does not exist."%__V)
1461         else:
1462             return self.__algorithm.StoredVariables[ __V ].removeDataObserver(
1463                     HookFunction   = __O,
1464                     AllObservers   = __A,
1465                     )
1466
1467     def hasObserver(self, __V):
1468         if self.__algorithm is None \
1469             or isinstance(self.__algorithm, dict) \
1470             or not hasattr(self.__algorithm,"StoredVariables"):
1471             return False
1472         if __V not in self.__algorithm:
1473             return False
1474         return self.__algorithm.StoredVariables[ __V ].hasDataObserver()
1475
1476     def keys(self):
1477         __allvariables = list(self.__algorithm.keys()) + list(self.__P.keys())
1478         for k in self.__variable_names_not_public:
1479             if k in __allvariables: __allvariables.remove(k)
1480         return __allvariables
1481
1482     def __contains__(self, key=None):
1483         "D.__contains__(k) -> True if D has a key k, else False"
1484         return key in self.__algorithm or key in self.__P
1485
1486     def __repr__(self):
1487         "x.__repr__() <==> repr(x)"
1488         return repr(self.__A)+", "+repr(self.__P)
1489
1490     def __str__(self):
1491         "x.__str__() <==> str(x)"
1492         return str(self.__A)+", "+str(self.__P)
1493
1494     def __setAlgorithm(self, choice = None ):
1495         """
1496         Permet de sélectionner l'algorithme à utiliser pour mener à bien l'étude
1497         d'assimilation. L'argument est un champ caractère se rapportant au nom
1498         d'un algorithme réalisant l'opération sur les arguments fixes.
1499         """
1500         if choice is None:
1501             raise ValueError("Error: algorithm choice has to be given")
1502         if self.__algorithmName is not None:
1503             raise ValueError("Error: algorithm choice has already been done as \"%s\", it can't be changed."%self.__algorithmName)
1504         daDirectory = "daAlgorithms"
1505         #
1506         # Recherche explicitement le fichier complet
1507         # ------------------------------------------
1508         module_path = None
1509         for directory in sys.path:
1510             if os.path.isfile(os.path.join(directory, daDirectory, str(choice)+'.py')):
1511                 module_path = os.path.abspath(os.path.join(directory, daDirectory))
1512         if module_path is None:
1513             raise ImportError(
1514                 "No algorithm module named \"%s\" has been found in the search path.\n             The search path is %s"%(choice, sys.path))
1515         #
1516         # Importe le fichier complet comme un module
1517         # ------------------------------------------
1518         try:
1519             sys_path_tmp = sys.path ; sys.path.insert(0,module_path)
1520             self.__algorithmFile = __import__(str(choice), globals(), locals(), [])
1521             if not hasattr(self.__algorithmFile, "ElementaryAlgorithm"):
1522                 raise ImportError("this module does not define a valid elementary algorithm.")
1523             self.__algorithmName = str(choice)
1524             sys.path = sys_path_tmp ; del sys_path_tmp
1525         except ImportError as e:
1526             raise ImportError(
1527                 "The module named \"%s\" was found, but is incorrect at the import stage.\n             The import error message is: %s"%(choice,e))
1528         #
1529         # Instancie un objet du type élémentaire du fichier
1530         # -------------------------------------------------
1531         self.__algorithm = self.__algorithmFile.ElementaryAlgorithm()
1532         return 0
1533
1534     def __shape_validate(self):
1535         """
1536         Validation de la correspondance correcte des tailles des variables et
1537         des matrices s'il y en a.
1538         """
1539         if self.__Xb is None:                      __Xb_shape = (0,)
1540         elif hasattr(self.__Xb,"size"):            __Xb_shape = (self.__Xb.size,)
1541         elif hasattr(self.__Xb,"shape"):
1542             if isinstance(self.__Xb.shape, tuple): __Xb_shape = self.__Xb.shape
1543             else:                                  __Xb_shape = self.__Xb.shape()
1544         else: raise TypeError("The background (Xb) has no attribute of shape: problem !")
1545         #
1546         if self.__Y is None:                       __Y_shape = (0,)
1547         elif hasattr(self.__Y,"size"):             __Y_shape = (self.__Y.size,)
1548         elif hasattr(self.__Y,"shape"):
1549             if isinstance(self.__Y.shape, tuple):  __Y_shape = self.__Y.shape
1550             else:                                  __Y_shape = self.__Y.shape()
1551         else: raise TypeError("The observation (Y) has no attribute of shape: problem !")
1552         #
1553         if self.__U is None:                       __U_shape = (0,)
1554         elif hasattr(self.__U,"size"):             __U_shape = (self.__U.size,)
1555         elif hasattr(self.__U,"shape"):
1556             if isinstance(self.__U.shape, tuple):  __U_shape = self.__U.shape
1557             else:                                  __U_shape = self.__U.shape()
1558         else: raise TypeError("The control (U) has no attribute of shape: problem !")
1559         #
1560         if self.__B is None:                       __B_shape = (0,0)
1561         elif hasattr(self.__B,"shape"):
1562             if isinstance(self.__B.shape, tuple):  __B_shape = self.__B.shape
1563             else:                                  __B_shape = self.__B.shape()
1564         else: raise TypeError("The a priori errors covariance matrix (B) has no attribute of shape: problem !")
1565         #
1566         if self.__R is None:                       __R_shape = (0,0)
1567         elif hasattr(self.__R,"shape"):
1568             if isinstance(self.__R.shape, tuple):  __R_shape = self.__R.shape
1569             else:                                  __R_shape = self.__R.shape()
1570         else: raise TypeError("The observation errors covariance matrix (R) has no attribute of shape: problem !")
1571         #
1572         if self.__Q is None:                       __Q_shape = (0,0)
1573         elif hasattr(self.__Q,"shape"):
1574             if isinstance(self.__Q.shape, tuple):  __Q_shape = self.__Q.shape
1575             else:                                  __Q_shape = self.__Q.shape()
1576         else: raise TypeError("The evolution errors covariance matrix (Q) has no attribute of shape: problem !")
1577         #
1578         if len(self.__HO) == 0:                              __HO_shape = (0,0)
1579         elif isinstance(self.__HO, dict):                    __HO_shape = (0,0)
1580         elif hasattr(self.__HO["Direct"],"shape"):
1581             if isinstance(self.__HO["Direct"].shape, tuple): __HO_shape = self.__HO["Direct"].shape
1582             else:                                            __HO_shape = self.__HO["Direct"].shape()
1583         else: raise TypeError("The observation operator (H) has no attribute of shape: problem !")
1584         #
1585         if len(self.__EM) == 0:                              __EM_shape = (0,0)
1586         elif isinstance(self.__EM, dict):                    __EM_shape = (0,0)
1587         elif hasattr(self.__EM["Direct"],"shape"):
1588             if isinstance(self.__EM["Direct"].shape, tuple): __EM_shape = self.__EM["Direct"].shape
1589             else:                                            __EM_shape = self.__EM["Direct"].shape()
1590         else: raise TypeError("The evolution model (EM) has no attribute of shape: problem !")
1591         #
1592         if len(self.__CM) == 0:                              __CM_shape = (0,0)
1593         elif isinstance(self.__CM, dict):                    __CM_shape = (0,0)
1594         elif hasattr(self.__CM["Direct"],"shape"):
1595             if isinstance(self.__CM["Direct"].shape, tuple): __CM_shape = self.__CM["Direct"].shape
1596             else:                                            __CM_shape = self.__CM["Direct"].shape()
1597         else: raise TypeError("The control model (CM) has no attribute of shape: problem !")
1598         #
1599         # Vérification des conditions
1600         # ---------------------------
1601         if not( len(__Xb_shape) == 1 or min(__Xb_shape) == 1 ):
1602             raise ValueError("Shape characteristic of background (Xb) is incorrect: \"%s\"."%(__Xb_shape,))
1603         if not( len(__Y_shape) == 1 or min(__Y_shape) == 1 ):
1604             raise ValueError("Shape characteristic of observation (Y) is incorrect: \"%s\"."%(__Y_shape,))
1605         #
1606         if not( min(__B_shape) == max(__B_shape) ):
1607             raise ValueError("Shape characteristic of a priori errors covariance matrix (B) is incorrect: \"%s\"."%(__B_shape,))
1608         if not( min(__R_shape) == max(__R_shape) ):
1609             raise ValueError("Shape characteristic of observation errors covariance matrix (R) is incorrect: \"%s\"."%(__R_shape,))
1610         if not( min(__Q_shape) == max(__Q_shape) ):
1611             raise ValueError("Shape characteristic of evolution errors covariance matrix (Q) is incorrect: \"%s\"."%(__Q_shape,))
1612         if not( min(__EM_shape) == max(__EM_shape) ):
1613             raise ValueError("Shape characteristic of evolution operator (EM) is incorrect: \"%s\"."%(__EM_shape,))
1614         #
1615         if len(self.__HO) > 0 and not isinstance(self.__HO, dict) and not( __HO_shape[1] == max(__Xb_shape) ):
1616             raise ValueError(
1617                 "Shape characteristic of observation operator (H)"+\
1618                 " \"%s\" and state (X) \"%s\" are incompatible."%(__HO_shape,__Xb_shape))
1619         if len(self.__HO) > 0 and not isinstance(self.__HO, dict) and not( __HO_shape[0] == max(__Y_shape) ):
1620             raise ValueError(
1621                 "Shape characteristic of observation operator (H)"+\
1622                 " \"%s\" and observation (Y) \"%s\" are incompatible."%(__HO_shape,__Y_shape))
1623         if len(self.__HO) > 0 and not isinstance(self.__HO, dict) and len(self.__B) > 0 and not( __HO_shape[1] == __B_shape[0] ):
1624             raise ValueError(
1625                 "Shape characteristic of observation operator (H)"+\
1626                 " \"%s\" and a priori errors covariance matrix (B) \"%s\" are incompatible."%(__HO_shape,__B_shape))
1627         if len(self.__HO) > 0 and not isinstance(self.__HO, dict) and len(self.__R) > 0 and not( __HO_shape[0] == __R_shape[1] ):
1628             raise ValueError(
1629                 "Shape characteristic of observation operator (H)"+\
1630                 " \"%s\" and observation errors covariance matrix (R) \"%s\" are incompatible."%(__HO_shape,__R_shape))
1631         #
1632         if self.__B is not None and len(self.__B) > 0 and not( __B_shape[1] == max(__Xb_shape) ):
1633             if self.__algorithmName in ["EnsembleBlue",]:
1634                 asPersistentVector = self.__Xb.reshape((-1,min(__B_shape)))
1635                 self.__Xb = Persistence.OneVector("Background")
1636                 for member in asPersistentVector:
1637                     self.__Xb.store( numpy.asarray(member, dtype=float) )
1638                 __Xb_shape = min(__B_shape)
1639             else:
1640                 raise ValueError(
1641                     "Shape characteristic of a priori errors covariance matrix (B)"+\
1642                     " \"%s\" and background vector (Xb) \"%s\" are incompatible."%(__B_shape,__Xb_shape))
1643         #
1644         if self.__R is not None and len(self.__R) > 0 and not( __R_shape[1] == max(__Y_shape) ):
1645             raise ValueError(
1646                 "Shape characteristic of observation errors covariance matrix (R)"+\
1647                 " \"%s\" and observation vector (Y) \"%s\" are incompatible."%(__R_shape,__Y_shape))
1648         #
1649         if self.__EM is not None and len(self.__EM) > 0 and not isinstance(self.__EM, dict) and not( __EM_shape[1] == max(__Xb_shape) ):
1650             raise ValueError(
1651                 "Shape characteristic of evolution model (EM)"+\
1652                 " \"%s\" and state (X) \"%s\" are incompatible."%(__EM_shape,__Xb_shape))
1653         #
1654         if self.__CM is not None and len(self.__CM) > 0 and not isinstance(self.__CM, dict) and not( __CM_shape[1] == max(__U_shape) ):
1655             raise ValueError(
1656                 "Shape characteristic of control model (CM)"+\
1657                 " \"%s\" and control (U) \"%s\" are incompatible."%(__CM_shape,__U_shape))
1658         #
1659         if ("Bounds" in self.__P) \
1660             and (isinstance(self.__P["Bounds"], list) or isinstance(self.__P["Bounds"], tuple)) \
1661             and (len(self.__P["Bounds"]) != max(__Xb_shape)):
1662             raise ValueError("The number \"%s\" of bound pairs for the state (X) components is different of the size \"%s\" of the state itself." \
1663                 %(len(self.__P["Bounds"]),max(__Xb_shape)))
1664         #
1665         if ("StateBoundsForQuantiles" in self.__P) \
1666             and (isinstance(self.__P["StateBoundsForQuantiles"], list) or isinstance(self.__P["StateBoundsForQuantiles"], tuple)) \
1667             and (len(self.__P["StateBoundsForQuantiles"]) != max(__Xb_shape)):
1668             raise ValueError("The number \"%s\" of bound pairs for the quantile state (X) components is different of the size \"%s\" of the state itself." \
1669                 %(len(self.__P["StateBoundsForQuantiles"]),max(__Xb_shape)))
1670         #
1671         return 1
1672
1673 # ==============================================================================
1674 class RegulationAndParameters(object):
1675     """
1676     Classe générale d'interface d'action pour la régulation et ses paramètres
1677     """
1678     __slots__ = ("__name", "__P")
1679     #
1680     def __init__(self,
1681                  name               = "GenericRegulation",
1682                  asAlgorithm        = None,
1683                  asDict             = None,
1684                  asScript           = None,
1685                 ):
1686         """
1687         """
1688         self.__name       = str(name)
1689         self.__P          = {}
1690         #
1691         if asAlgorithm is None and asScript is not None:
1692             __Algo = Interfaces.ImportFromScript(asScript).getvalue( "Algorithm" )
1693         else:
1694             __Algo = asAlgorithm
1695         #
1696         if asDict is None and asScript is not None:
1697             __Dict = Interfaces.ImportFromScript(asScript).getvalue( self.__name, "Parameters" )
1698         else:
1699             __Dict = asDict
1700         #
1701         if __Dict is not None:
1702             self.__P.update( dict(__Dict) )
1703         #
1704         if __Algo is not None:
1705             self.__P.update( {"Algorithm":str(__Algo)} )
1706
1707     def get(self, key = None):
1708         "Vérifie l'existence d'une clé de variable ou de paramètres"
1709         if key in self.__P:
1710             return self.__P[key]
1711         else:
1712             return self.__P
1713
1714 # ==============================================================================
1715 class DataObserver(object):
1716     """
1717     Classe générale d'interface de type observer
1718     """
1719     __slots__ = ("__name", "__V", "__O", "__I")
1720     #
1721     def __init__(self,
1722                  name        = "GenericObserver",
1723                  onVariable  = None,
1724                  asTemplate  = None,
1725                  asString    = None,
1726                  asScript    = None,
1727                  asObsObject = None,
1728                  withInfo    = None,
1729                  scheduledBy = None,
1730                  withAlgo    = None,
1731                 ):
1732         """
1733         """
1734         self.__name       = str(name)
1735         self.__V          = None
1736         self.__O          = None
1737         self.__I          = None
1738         #
1739         if onVariable is None:
1740             raise ValueError("setting an observer has to be done over a variable name or a list of variable names, not over None.")
1741         elif type(onVariable) in (tuple, list):
1742             self.__V = tuple(map( str, onVariable ))
1743             if withInfo is None:
1744                 self.__I = self.__V
1745             else:
1746                 self.__I = (str(withInfo),)*len(self.__V)
1747         elif isinstance(onVariable, str):
1748             self.__V = (onVariable,)
1749             if withInfo is None:
1750                 self.__I = (onVariable,)
1751             else:
1752                 self.__I = (str(withInfo),)
1753         else:
1754             raise ValueError("setting an observer has to be done over a variable name or a list of variable names.")
1755         #
1756         if asObsObject is not None:
1757             self.__O = asObsObject
1758         else:
1759             __FunctionText = str(UserScript('Observer', asTemplate, asString, asScript))
1760             __Function = Observer2Func(__FunctionText)
1761             self.__O = __Function.getfunc()
1762         #
1763         for k in range(len(self.__V)):
1764             ename = self.__V[k]
1765             einfo = self.__I[k]
1766             if ename not in withAlgo:
1767                 raise ValueError("An observer is asked to be set on a variable named %s which does not exist."%ename)
1768             else:
1769                 withAlgo.setObserver(ename, self.__O, einfo, scheduledBy)
1770
1771     def __repr__(self):
1772         "x.__repr__() <==> repr(x)"
1773         return repr(self.__V)+"\n"+repr(self.__O)
1774
1775     def __str__(self):
1776         "x.__str__() <==> str(x)"
1777         return str(self.__V)+"\n"+str(self.__O)
1778
1779 # ==============================================================================
1780 class UserScript(object):
1781     """
1782     Classe générale d'interface de type texte de script utilisateur
1783     """
1784     __slots__ = ("__name", "__F")
1785     #
1786     def __init__(self,
1787                  name       = "GenericUserScript",
1788                  asTemplate = None,
1789                  asString   = None,
1790                  asScript   = None,
1791                 ):
1792         """
1793         """
1794         self.__name       = str(name)
1795         #
1796         if asString is not None:
1797             self.__F = asString
1798         elif self.__name == "UserPostAnalysis" and (asTemplate is not None) and (asTemplate in Templates.UserPostAnalysisTemplates):
1799             self.__F = Templates.UserPostAnalysisTemplates[asTemplate]
1800         elif self.__name == "Observer" and (asTemplate is not None) and (asTemplate in Templates.ObserverTemplates):
1801             self.__F = Templates.ObserverTemplates[asTemplate]
1802         elif asScript is not None:
1803             self.__F = Interfaces.ImportFromScript(asScript).getstring()
1804         else:
1805             self.__F = ""
1806
1807     def __repr__(self):
1808         "x.__repr__() <==> repr(x)"
1809         return repr(self.__F)
1810
1811     def __str__(self):
1812         "x.__str__() <==> str(x)"
1813         return str(self.__F)
1814
1815 # ==============================================================================
1816 class ExternalParameters(object):
1817     """
1818     Classe générale d'interface pour le stockage des paramètres externes
1819     """
1820     __slots__ = ("__name", "__P")
1821     #
1822     def __init__(self,
1823                  name        = "GenericExternalParameters",
1824                  asDict      = None,
1825                  asScript    = None,
1826                 ):
1827         """
1828         """
1829         self.__name = str(name)
1830         self.__P    = {}
1831         #
1832         self.updateParameters( asDict, asScript )
1833
1834     def updateParameters(self,
1835                  asDict     = None,
1836                  asScript   = None,
1837                 ):
1838         "Mise à jour des paramètres"
1839         if asDict is None and asScript is not None:
1840             __Dict = Interfaces.ImportFromScript(asScript).getvalue( self.__name, "ExternalParameters" )
1841         else:
1842             __Dict = asDict
1843         #
1844         if __Dict is not None:
1845             self.__P.update( dict(__Dict) )
1846
1847     def get(self, key = None):
1848         if key in self.__P:
1849             return self.__P[key]
1850         else:
1851             return list(self.__P.keys())
1852
1853     def keys(self):
1854         return list(self.__P.keys())
1855
1856     def pop(self, k, d):
1857         return self.__P.pop(k, d)
1858
1859     def items(self):
1860         return self.__P.items()
1861
1862     def __contains__(self, key=None):
1863         "D.__contains__(k) -> True if D has a key k, else False"
1864         return key in self.__P
1865
1866 # ==============================================================================
1867 class State(object):
1868     """
1869     Classe générale d'interface de type état
1870     """
1871     __slots__ = (
1872         "__name", "__check", "__V", "__T", "__is_vector", "__is_series",
1873         "shape", "size",
1874         )
1875     #
1876     def __init__(self,
1877                  name               = "GenericVector",
1878                  asVector           = None,
1879                  asPersistentVector = None,
1880                  asScript           = None,
1881                  asDataFile         = None,
1882                  colNames           = None,
1883                  colMajor           = False,
1884                  scheduledBy        = None,
1885                  toBeChecked        = False,
1886                 ):
1887         """
1888         Permet de définir un vecteur :
1889         - asVector : entrée des données, comme un vecteur compatible avec le
1890           constructeur de numpy.matrix, ou "True" si entrée par script.
1891         - asPersistentVector : entrée des données, comme une série de vecteurs
1892           compatible avec le constructeur de numpy.matrix, ou comme un objet de
1893           type Persistence, ou "True" si entrée par script.
1894         - asScript : si un script valide est donné contenant une variable
1895           nommée "name", la variable est de type "asVector" (par défaut) ou
1896           "asPersistentVector" selon que l'une de ces variables est placée à
1897           "True".
1898         - asDataFile : si un ou plusieurs fichiers valides sont donnés
1899           contenant des valeurs en colonnes, elles-mêmes nommées "colNames"
1900           (s'il n'y a pas de nom de colonne indiquée, on cherche une colonne
1901           nommée "name"), on récupère les colonnes et on les range ligne après
1902           ligne (colMajor=False, par défaut) ou colonne après colonne
1903           (colMajor=True). La variable résultante est de type "asVector" (par
1904           défaut) ou "asPersistentVector" selon que l'une de ces variables est
1905           placée à "True".
1906         """
1907         self.__name       = str(name)
1908         self.__check      = bool(toBeChecked)
1909         #
1910         self.__V          = None
1911         self.__T          = None
1912         self.__is_vector  = False
1913         self.__is_series  = False
1914         #
1915         if asScript is not None:
1916             __Vector, __Series = None, None
1917             if asPersistentVector:
1918                 __Series = Interfaces.ImportFromScript(asScript).getvalue( self.__name )
1919             else:
1920                 __Vector = Interfaces.ImportFromScript(asScript).getvalue( self.__name )
1921         elif asDataFile is not None:
1922             __Vector, __Series = None, None
1923             if asPersistentVector:
1924                 if colNames is not None:
1925                     __Series = Interfaces.ImportFromFile(asDataFile).getvalue( colNames )[1]
1926                 else:
1927                     __Series = Interfaces.ImportFromFile(asDataFile).getvalue( [self.__name,] )[1]
1928                 if bool(colMajor) and not Interfaces.ImportFromFile(asDataFile).getformat() == "application/numpy.npz":
1929                     __Series = numpy.transpose(__Series)
1930                 elif not bool(colMajor) and Interfaces.ImportFromFile(asDataFile).getformat() == "application/numpy.npz":
1931                     __Series = numpy.transpose(__Series)
1932             else:
1933                 if colNames is not None:
1934                     __Vector = Interfaces.ImportFromFile(asDataFile).getvalue( colNames )[1]
1935                 else:
1936                     __Vector = Interfaces.ImportFromFile(asDataFile).getvalue( [self.__name,] )[1]
1937                 if bool(colMajor):
1938                     __Vector = numpy.ravel(__Vector, order = "F")
1939                 else:
1940                     __Vector = numpy.ravel(__Vector, order = "C")
1941         else:
1942             __Vector, __Series = asVector, asPersistentVector
1943         #
1944         if __Vector is not None:
1945             self.__is_vector = True
1946             if isinstance(__Vector, str):
1947                __Vector = PlatformInfo.strvect2liststr( __Vector )
1948             self.__V         = numpy.ravel(numpy.asarray( __Vector, dtype=float )).reshape((-1,1))
1949             self.shape       = self.__V.shape
1950             self.size        = self.__V.size
1951         elif __Series is not None:
1952             self.__is_series  = True
1953             if isinstance(__Series, (tuple, list, numpy.ndarray, numpy.matrix, str)):
1954                 self.__V = Persistence.OneVector(self.__name)
1955                 if isinstance(__Series, str):
1956                     __Series = PlatformInfo.strmatrix2liststr(__Series)
1957                 for member in __Series:
1958                     if isinstance(member, str):
1959                         member = PlatformInfo.strvect2liststr( member )
1960                     self.__V.store(numpy.asarray( member, dtype=float ))
1961             else:
1962                 self.__V = __Series
1963             if isinstance(self.__V.shape, (tuple, list)):
1964                 self.shape       = self.__V.shape
1965             else:
1966                 self.shape       = self.__V.shape()
1967             if len(self.shape) == 1:
1968                 self.shape       = (self.shape[0],1)
1969             self.size        = self.shape[0] * self.shape[1]
1970         else:
1971             raise ValueError(
1972                 "The %s object is improperly defined or undefined,"%self.__name+\
1973                 " it requires at minima either a vector, a list/tuple of"+\
1974                 " vectors or a persistent object. Please check your vector input.")
1975         #
1976         if scheduledBy is not None:
1977             self.__T = scheduledBy
1978
1979     def getO(self, withScheduler=False):
1980         if withScheduler:
1981             return self.__V, self.__T
1982         elif self.__T is None:
1983             return self.__V
1984         else:
1985             return self.__V
1986
1987     def isvector(self):
1988         "Vérification du type interne"
1989         return self.__is_vector
1990
1991     def isseries(self):
1992         "Vérification du type interne"
1993         return self.__is_series
1994
1995     def __repr__(self):
1996         "x.__repr__() <==> repr(x)"
1997         return repr(self.__V)
1998
1999     def __str__(self):
2000         "x.__str__() <==> str(x)"
2001         return str(self.__V)
2002
2003 # ==============================================================================
2004 class Covariance(object):
2005     """
2006     Classe générale d'interface de type covariance
2007     """
2008     __slots__ = (
2009         "__name", "__check", "__C", "__is_scalar", "__is_vector", "__is_matrix",
2010         "__is_object", "shape", "size",
2011         )
2012     #
2013     def __init__(self,
2014                  name          = "GenericCovariance",
2015                  asCovariance  = None,
2016                  asEyeByScalar = None,
2017                  asEyeByVector = None,
2018                  asCovObject   = None,
2019                  asScript      = None,
2020                  toBeChecked   = False,
2021                 ):
2022         """
2023         Permet de définir une covariance :
2024         - asCovariance : entrée des données, comme une matrice compatible avec
2025           le constructeur de numpy.matrix
2026         - asEyeByScalar : entrée des données comme un seul scalaire de variance,
2027           multiplicatif d'une matrice de corrélation identité, aucune matrice
2028           n'étant donc explicitement à donner
2029         - asEyeByVector : entrée des données comme un seul vecteur de variance,
2030           à mettre sur la diagonale d'une matrice de corrélation, aucune matrice
2031           n'étant donc explicitement à donner
2032         - asCovObject : entrée des données comme un objet python, qui a les
2033           methodes obligatoires "getT", "getI", "diag", "trace", "__add__",
2034           "__sub__", "__neg__", "__mul__", "__rmul__" et facultatives "shape",
2035           "size", "cholesky", "choleskyI", "asfullmatrix", "__repr__", "__str__"
2036         - toBeChecked : booléen indiquant si le caractère SDP de la matrice
2037           pleine doit être vérifié
2038         """
2039         self.__name       = str(name)
2040         self.__check      = bool(toBeChecked)
2041         #
2042         self.__C          = None
2043         self.__is_scalar  = False
2044         self.__is_vector  = False
2045         self.__is_matrix  = False
2046         self.__is_object  = False
2047         #
2048         if asScript is not None:
2049             __Matrix, __Scalar, __Vector, __Object = None, None, None, None
2050             if asEyeByScalar:
2051                 __Scalar = Interfaces.ImportFromScript(asScript).getvalue( self.__name )
2052             elif asEyeByVector:
2053                 __Vector = Interfaces.ImportFromScript(asScript).getvalue( self.__name )
2054             elif asCovObject:
2055                 __Object = Interfaces.ImportFromScript(asScript).getvalue( self.__name )
2056             else:
2057                 __Matrix = Interfaces.ImportFromScript(asScript).getvalue( self.__name )
2058         else:
2059             __Matrix, __Scalar, __Vector, __Object = asCovariance, asEyeByScalar, asEyeByVector, asCovObject
2060         #
2061         if __Scalar is not None:
2062             if isinstance(__Scalar, str):
2063                 __Scalar = PlatformInfo.strvect2liststr( __Scalar )
2064                 if len(__Scalar) > 0: __Scalar = __Scalar[0]
2065             if numpy.array(__Scalar).size != 1:
2066                 raise ValueError(
2067                     "  The diagonal multiplier given to define a sparse matrix is"+\
2068                     " not a unique scalar value.\n  Its actual measured size is"+\
2069                     " %i. Please check your scalar input."%numpy.array(__Scalar).size)
2070             self.__is_scalar = True
2071             self.__C         = numpy.abs( float(__Scalar) )
2072             self.shape       = (0,0)
2073             self.size        = 0
2074         elif __Vector is not None:
2075             if isinstance(__Vector, str):
2076                 __Vector = PlatformInfo.strvect2liststr( __Vector )
2077             self.__is_vector = True
2078             self.__C         = numpy.abs( numpy.ravel(numpy.asarray( __Vector, dtype=float )) )
2079             self.shape       = (self.__C.size,self.__C.size)
2080             self.size        = self.__C.size**2
2081         elif __Matrix is not None:
2082             self.__is_matrix = True
2083             self.__C         = numpy.matrix( __Matrix, float )
2084             self.shape       = self.__C.shape
2085             self.size        = self.__C.size
2086         elif __Object is not None:
2087             self.__is_object = True
2088             self.__C         = __Object
2089             for at in ("getT","getI","diag","trace","__add__","__sub__","__neg__","__matmul__","__mul__","__rmatmul__","__rmul__"):
2090                 if not hasattr(self.__C,at):
2091                     raise ValueError("The matrix given for %s as an object has no attribute \"%s\". Please check your object input."%(self.__name,at))
2092             if hasattr(self.__C,"shape"):
2093                 self.shape       = self.__C.shape
2094             else:
2095                 self.shape       = (0,0)
2096             if hasattr(self.__C,"size"):
2097                 self.size        = self.__C.size
2098             else:
2099                 self.size        = 0
2100         else:
2101             pass
2102         #
2103         self.__validate()
2104
2105     def __validate(self):
2106         "Validation"
2107         if self.__C is None:
2108             raise UnboundLocalError("%s covariance matrix value has not been set!"%(self.__name,))
2109         if self.ismatrix() and min(self.shape) != max(self.shape):
2110             raise ValueError("The given matrix for %s is not a square one, its shape is %s. Please check your matrix input."%(self.__name,self.shape))
2111         if self.isobject() and min(self.shape) != max(self.shape):
2112             raise ValueError("The matrix given for \"%s\" is not a square one, its shape is %s. Please check your object input."%(self.__name,self.shape))
2113         if self.isscalar() and self.__C <= 0:
2114             raise ValueError("The \"%s\" covariance matrix is not positive-definite. Please check your scalar input %s."%(self.__name,self.__C))
2115         if self.isvector() and (self.__C <= 0).any():
2116             raise ValueError("The \"%s\" covariance matrix is not positive-definite. Please check your vector input."%(self.__name,))
2117         if self.ismatrix() and (self.__check or logging.getLogger().level < logging.WARNING):
2118             try:
2119                 numpy.linalg.cholesky( self.__C )
2120             except Exception:
2121                 raise ValueError("The %s covariance matrix is not symmetric positive-definite. Please check your matrix input."%(self.__name,))
2122         if self.isobject() and (self.__check or logging.getLogger().level < logging.WARNING):
2123             try:
2124                 self.__C.cholesky()
2125             except Exception:
2126                 raise ValueError("The %s covariance object is not symmetric positive-definite. Please check your matrix input."%(self.__name,))
2127
2128     def isscalar(self):
2129         "Vérification du type interne"
2130         return self.__is_scalar
2131
2132     def isvector(self):
2133         "Vérification du type interne"
2134         return self.__is_vector
2135
2136     def ismatrix(self):
2137         "Vérification du type interne"
2138         return self.__is_matrix
2139
2140     def isobject(self):
2141         "Vérification du type interne"
2142         return self.__is_object
2143
2144     def getI(self):
2145         "Inversion"
2146         if   self.ismatrix():
2147             return Covariance(self.__name+"I", asCovariance  = numpy.linalg.inv(self.__C) )
2148         elif self.isvector():
2149             return Covariance(self.__name+"I", asEyeByVector = 1. / self.__C )
2150         elif self.isscalar():
2151             return Covariance(self.__name+"I", asEyeByScalar = 1. / self.__C )
2152         elif self.isobject() and hasattr(self.__C,"getI"):
2153             return Covariance(self.__name+"I", asCovObject   = self.__C.getI() )
2154         else:
2155             return None # Indispensable
2156
2157     def getT(self):
2158         "Transposition"
2159         if   self.ismatrix():
2160             return Covariance(self.__name+"T", asCovariance  = self.__C.T )
2161         elif self.isvector():
2162             return Covariance(self.__name+"T", asEyeByVector = self.__C )
2163         elif self.isscalar():
2164             return Covariance(self.__name+"T", asEyeByScalar = self.__C )
2165         elif self.isobject() and hasattr(self.__C,"getT"):
2166             return Covariance(self.__name+"T", asCovObject   = self.__C.getT() )
2167         else:
2168             raise AttributeError("the %s covariance matrix has no getT attribute."%(self.__name,))
2169
2170     def cholesky(self):
2171         "Décomposition de Cholesky"
2172         if   self.ismatrix():
2173             return Covariance(self.__name+"C", asCovariance  = numpy.linalg.cholesky(self.__C) )
2174         elif self.isvector():
2175             return Covariance(self.__name+"C", asEyeByVector = numpy.sqrt( self.__C ) )
2176         elif self.isscalar():
2177             return Covariance(self.__name+"C", asEyeByScalar = numpy.sqrt( self.__C ) )
2178         elif self.isobject() and hasattr(self.__C,"cholesky"):
2179             return Covariance(self.__name+"C", asCovObject   = self.__C.cholesky() )
2180         else:
2181             raise AttributeError("the %s covariance matrix has no cholesky attribute."%(self.__name,))
2182
2183     def choleskyI(self):
2184         "Inversion de la décomposition de Cholesky"
2185         if   self.ismatrix():
2186             return Covariance(self.__name+"H", asCovariance  = numpy.linalg.inv(numpy.linalg.cholesky(self.__C)) )
2187         elif self.isvector():
2188             return Covariance(self.__name+"H", asEyeByVector = 1.0 / numpy.sqrt( self.__C ) )
2189         elif self.isscalar():
2190             return Covariance(self.__name+"H", asEyeByScalar = 1.0 / numpy.sqrt( self.__C ) )
2191         elif self.isobject() and hasattr(self.__C,"choleskyI"):
2192             return Covariance(self.__name+"H", asCovObject   = self.__C.choleskyI() )
2193         else:
2194             raise AttributeError("the %s covariance matrix has no choleskyI attribute."%(self.__name,))
2195
2196     def sqrtm(self):
2197         "Racine carrée matricielle"
2198         if   self.ismatrix():
2199             import scipy
2200             return Covariance(self.__name+"C", asCovariance  = numpy.real(scipy.linalg.sqrtm(self.__C)) )
2201         elif self.isvector():
2202             return Covariance(self.__name+"C", asEyeByVector = numpy.sqrt( self.__C ) )
2203         elif self.isscalar():
2204             return Covariance(self.__name+"C", asEyeByScalar = numpy.sqrt( self.__C ) )
2205         elif self.isobject() and hasattr(self.__C,"sqrtm"):
2206             return Covariance(self.__name+"C", asCovObject   = self.__C.sqrtm() )
2207         else:
2208             raise AttributeError("the %s covariance matrix has no sqrtm attribute."%(self.__name,))
2209
2210     def sqrtmI(self):
2211         "Inversion de la racine carrée matricielle"
2212         if   self.ismatrix():
2213             import scipy
2214             return Covariance(self.__name+"H", asCovariance  = numpy.linalg.inv(numpy.real(scipy.linalg.sqrtm(self.__C))) )
2215         elif self.isvector():
2216             return Covariance(self.__name+"H", asEyeByVector = 1.0 / numpy.sqrt( self.__C ) )
2217         elif self.isscalar():
2218             return Covariance(self.__name+"H", asEyeByScalar = 1.0 / numpy.sqrt( self.__C ) )
2219         elif self.isobject() and hasattr(self.__C,"sqrtmI"):
2220             return Covariance(self.__name+"H", asCovObject   = self.__C.sqrtmI() )
2221         else:
2222             raise AttributeError("the %s covariance matrix has no sqrtmI attribute."%(self.__name,))
2223
2224     def diag(self, msize=None):
2225         "Diagonale de la matrice"
2226         if   self.ismatrix():
2227             return numpy.diag(self.__C)
2228         elif self.isvector():
2229             return self.__C
2230         elif self.isscalar():
2231             if msize is None:
2232                 raise ValueError("the size of the %s covariance matrix has to be given in case of definition as a scalar over the diagonal."%(self.__name,))
2233             else:
2234                 return self.__C * numpy.ones(int(msize))
2235         elif self.isobject() and hasattr(self.__C,"diag"):
2236             return self.__C.diag()
2237         else:
2238             raise AttributeError("the %s covariance matrix has no diag attribute."%(self.__name,))
2239
2240     def trace(self, msize=None):
2241         "Trace de la matrice"
2242         if   self.ismatrix():
2243             return numpy.trace(self.__C)
2244         elif self.isvector():
2245             return float(numpy.sum(self.__C))
2246         elif self.isscalar():
2247             if msize is None:
2248                 raise ValueError("the size of the %s covariance matrix has to be given in case of definition as a scalar over the diagonal."%(self.__name,))
2249             else:
2250                 return self.__C * int(msize)
2251         elif self.isobject():
2252             return self.__C.trace()
2253         else:
2254             raise AttributeError("the %s covariance matrix has no trace attribute."%(self.__name,))
2255
2256     def asfullmatrix(self, msize=None):
2257         "Matrice pleine"
2258         if   self.ismatrix():
2259             return numpy.asarray(self.__C, dtype=float)
2260         elif self.isvector():
2261             return numpy.asarray( numpy.diag(self.__C), dtype=float )
2262         elif self.isscalar():
2263             if msize is None:
2264                 raise ValueError("the size of the %s covariance matrix has to be given in case of definition as a scalar over the diagonal."%(self.__name,))
2265             else:
2266                 return numpy.asarray( self.__C * numpy.eye(int(msize)), dtype=float )
2267         elif self.isobject() and hasattr(self.__C,"asfullmatrix"):
2268             return self.__C.asfullmatrix()
2269         else:
2270             raise AttributeError("the %s covariance matrix has no asfullmatrix attribute."%(self.__name,))
2271
2272     def assparsematrix(self):
2273         "Valeur sparse"
2274         return self.__C
2275
2276     def getO(self):
2277         return self
2278
2279     def __repr__(self):
2280         "x.__repr__() <==> repr(x)"
2281         return repr(self.__C)
2282
2283     def __str__(self):
2284         "x.__str__() <==> str(x)"
2285         return str(self.__C)
2286
2287     def __add__(self, other):
2288         "x.__add__(y) <==> x+y"
2289         if   self.ismatrix() or self.isobject():
2290             return self.__C + numpy.asmatrix(other)
2291         elif self.isvector() or self.isscalar():
2292             _A = numpy.asarray(other)
2293             if len(_A.shape) == 1:
2294                 _A.reshape((-1,1))[::2] += self.__C
2295             else:
2296                 _A.reshape(_A.size)[::_A.shape[1]+1] += self.__C
2297             return numpy.asmatrix(_A)
2298
2299     def __radd__(self, other):
2300         "x.__radd__(y) <==> y+x"
2301         raise NotImplementedError("%s covariance matrix __radd__ method not available for %s type!"%(self.__name,type(other)))
2302
2303     def __sub__(self, other):
2304         "x.__sub__(y) <==> x-y"
2305         if   self.ismatrix() or self.isobject():
2306             return self.__C - numpy.asmatrix(other)
2307         elif self.isvector() or self.isscalar():
2308             _A = numpy.asarray(other)
2309             _A.reshape(_A.size)[::_A.shape[1]+1] = self.__C - _A.reshape(_A.size)[::_A.shape[1]+1]
2310             return numpy.asmatrix(_A)
2311
2312     def __rsub__(self, other):
2313         "x.__rsub__(y) <==> y-x"
2314         raise NotImplementedError("%s covariance matrix __rsub__ method not available for %s type!"%(self.__name,type(other)))
2315
2316     def __neg__(self):
2317         "x.__neg__() <==> -x"
2318         return - self.__C
2319
2320     def __matmul__(self, other):
2321         "x.__mul__(y) <==> x@y"
2322         if   self.ismatrix() and isinstance(other, (int, float)):
2323             return numpy.asarray(self.__C) * other
2324         elif self.ismatrix() and isinstance(other, (list, numpy.matrix, numpy.ndarray, tuple)):
2325             if numpy.ravel(other).size == self.shape[1]: # Vecteur
2326                 return numpy.ravel(self.__C @ numpy.ravel(other))
2327             elif numpy.asarray(other).shape[0] == self.shape[1]: # Matrice
2328                 return numpy.asarray(self.__C) @ numpy.asarray(other)
2329             else:
2330                 raise ValueError("operands could not be broadcast together with shapes %s %s in %s matrix"%(self.shape,numpy.asarray(other).shape,self.__name))
2331         elif self.isvector() and isinstance(other, (list, numpy.matrix, numpy.ndarray, tuple)):
2332             if numpy.ravel(other).size == self.shape[1]: # Vecteur
2333                 return numpy.ravel(self.__C) * numpy.ravel(other)
2334             elif numpy.asarray(other).shape[0] == self.shape[1]: # Matrice
2335                 return numpy.ravel(self.__C).reshape((-1,1)) * numpy.asarray(other)
2336             else:
2337                 raise ValueError("operands could not be broadcast together with shapes %s %s in %s matrix"%(self.shape,numpy.ravel(other).shape,self.__name))
2338         elif self.isscalar() and isinstance(other,numpy.matrix):
2339             return numpy.asarray(self.__C * other)
2340         elif self.isscalar() and isinstance(other, (list, numpy.ndarray, tuple)):
2341             if len(numpy.asarray(other).shape) == 1 or numpy.asarray(other).shape[1] == 1 or numpy.asarray(other).shape[0] == 1:
2342                 return self.__C * numpy.ravel(other)
2343             else:
2344                 return self.__C * numpy.asarray(other)
2345         elif self.isobject():
2346             return self.__C.__matmul__(other)
2347         else:
2348             raise NotImplementedError("%s covariance matrix __matmul__ method not available for %s type!"%(self.__name,type(other)))
2349
2350     def __mul__(self, other):
2351         "x.__mul__(y) <==> x*y"
2352         if   self.ismatrix() and isinstance(other, (int, numpy.matrix, float)):
2353             return self.__C * other
2354         elif self.ismatrix() and isinstance(other, (list, numpy.ndarray, tuple)):
2355             if numpy.ravel(other).size == self.shape[1]: # Vecteur
2356                 return self.__C * numpy.asmatrix(numpy.ravel(other)).T
2357             elif numpy.asmatrix(other).shape[0] == self.shape[1]: # Matrice
2358                 return self.__C * numpy.asmatrix(other)
2359             else:
2360                 raise ValueError(
2361                     "operands could not be broadcast together with shapes %s %s in %s matrix"%(self.shape,numpy.asmatrix(other).shape,self.__name))
2362         elif self.isvector() and isinstance(other, (list, numpy.matrix, numpy.ndarray, tuple)):
2363             if numpy.ravel(other).size == self.shape[1]: # Vecteur
2364                 return numpy.asmatrix(self.__C * numpy.ravel(other)).T
2365             elif numpy.asmatrix(other).shape[0] == self.shape[1]: # Matrice
2366                 return numpy.asmatrix((self.__C * (numpy.asarray(other).transpose())).transpose())
2367             else:
2368                 raise ValueError(
2369                     "operands could not be broadcast together with shapes %s %s in %s matrix"%(self.shape,numpy.ravel(other).shape,self.__name))
2370         elif self.isscalar() and isinstance(other,numpy.matrix):
2371             return self.__C * other
2372         elif self.isscalar() and isinstance(other, (list, numpy.ndarray, tuple)):
2373             if len(numpy.asarray(other).shape) == 1 or numpy.asarray(other).shape[1] == 1 or numpy.asarray(other).shape[0] == 1:
2374                 return self.__C * numpy.asmatrix(numpy.ravel(other)).T
2375             else:
2376                 return self.__C * numpy.asmatrix(other)
2377         elif self.isobject():
2378             return self.__C.__mul__(other)
2379         else:
2380             raise NotImplementedError(
2381                 "%s covariance matrix __mul__ method not available for %s type!"%(self.__name,type(other)))
2382
2383     def __rmatmul__(self, other):
2384         "x.__rmul__(y) <==> y@x"
2385         if self.ismatrix() and isinstance(other, (int, numpy.matrix, float)):
2386             return other * self.__C
2387         elif self.ismatrix() and isinstance(other, (list, numpy.ndarray, tuple)):
2388             if numpy.ravel(other).size == self.shape[1]: # Vecteur
2389                 return numpy.asmatrix(numpy.ravel(other)) * self.__C
2390             elif numpy.asmatrix(other).shape[0] == self.shape[1]: # Matrice
2391                 return numpy.asmatrix(other) * self.__C
2392             else:
2393                 raise ValueError(
2394                     "operands could not be broadcast together with shapes %s %s in %s matrix"%(numpy.asmatrix(other).shape,self.shape,self.__name))
2395         elif self.isvector() and isinstance(other,numpy.matrix):
2396             if numpy.ravel(other).size == self.shape[0]: # Vecteur
2397                 return numpy.asmatrix(numpy.ravel(other) * self.__C)
2398             elif numpy.asmatrix(other).shape[1] == self.shape[0]: # Matrice
2399                 return numpy.asmatrix(numpy.array(other) * self.__C)
2400             else:
2401                 raise ValueError(
2402                     "operands could not be broadcast together with shapes %s %s in %s matrix"%(numpy.ravel(other).shape,self.shape,self.__name))
2403         elif self.isscalar() and isinstance(other,numpy.matrix):
2404             return other * self.__C
2405         elif self.isobject():
2406             return self.__C.__rmatmul__(other)
2407         else:
2408             raise NotImplementedError(
2409                 "%s covariance matrix __rmatmul__ method not available for %s type!"%(self.__name,type(other)))
2410
2411     def __rmul__(self, other):
2412         "x.__rmul__(y) <==> y*x"
2413         if self.ismatrix() and isinstance(other, (int, numpy.matrix, float)):
2414             return other * self.__C
2415         elif self.ismatrix() and isinstance(other, (list, numpy.ndarray, tuple)):
2416             if numpy.ravel(other).size == self.shape[1]: # Vecteur
2417                 return numpy.asmatrix(numpy.ravel(other)) * self.__C
2418             elif numpy.asmatrix(other).shape[0] == self.shape[1]: # Matrice
2419                 return numpy.asmatrix(other) * self.__C
2420             else:
2421                 raise ValueError(
2422                     "operands could not be broadcast together with shapes %s %s in %s matrix"%(numpy.asmatrix(other).shape,self.shape,self.__name))
2423         elif self.isvector() and isinstance(other,numpy.matrix):
2424             if numpy.ravel(other).size == self.shape[0]: # Vecteur
2425                 return numpy.asmatrix(numpy.ravel(other) * self.__C)
2426             elif numpy.asmatrix(other).shape[1] == self.shape[0]: # Matrice
2427                 return numpy.asmatrix(numpy.array(other) * self.__C)
2428             else:
2429                 raise ValueError(
2430                     "operands could not be broadcast together with shapes %s %s in %s matrix"%(numpy.ravel(other).shape,self.shape,self.__name))
2431         elif self.isscalar() and isinstance(other,numpy.matrix):
2432             return other * self.__C
2433         elif self.isscalar() and isinstance(other,float):
2434             return other * self.__C
2435         elif self.isobject():
2436             return self.__C.__rmul__(other)
2437         else:
2438             raise NotImplementedError(
2439                 "%s covariance matrix __rmul__ method not available for %s type!"%(self.__name,type(other)))
2440
2441     def __len__(self):
2442         "x.__len__() <==> len(x)"
2443         return self.shape[0]
2444
2445 # ==============================================================================
2446 class Observer2Func(object):
2447     """
2448     Création d'une fonction d'observateur a partir de son texte
2449     """
2450     __slots__ = ("__corps")
2451     #
2452     def __init__(self, corps=""):
2453         self.__corps = corps
2454     def func(self,var,info):
2455         "Fonction d'observation"
2456         exec(self.__corps)
2457     def getfunc(self):
2458         "Restitution du pointeur de fonction dans l'objet"
2459         return self.func
2460
2461 # ==============================================================================
2462 class CaseLogger(object):
2463     """
2464     Conservation des commandes de création d'un cas
2465     """
2466     __slots__ = (
2467         "__name", "__objname", "__logSerie", "__switchoff", "__viewers",
2468         "__loaders",
2469         )
2470     #
2471     def __init__(self, __name="", __objname="case", __addViewers=None, __addLoaders=None):
2472         self.__name     = str(__name)
2473         self.__objname  = str(__objname)
2474         self.__logSerie = []
2475         self.__switchoff = False
2476         self.__viewers = {
2477             "TUI" :Interfaces._TUIViewer,
2478             "SCD" :Interfaces._SCDViewer,
2479             "YACS":Interfaces._YACSViewer,
2480             "SimpleReportInRst":Interfaces._SimpleReportInRstViewer,
2481             "SimpleReportInHtml":Interfaces._SimpleReportInHtmlViewer,
2482             "SimpleReportInPlainTxt":Interfaces._SimpleReportInPlainTxtViewer,
2483             }
2484         self.__loaders = {
2485             "TUI" :Interfaces._TUIViewer,
2486             "COM" :Interfaces._COMViewer,
2487             }
2488         if __addViewers is not None:
2489             self.__viewers.update(dict(__addViewers))
2490         if __addLoaders is not None:
2491             self.__loaders.update(dict(__addLoaders))
2492
2493     def register(self, __command=None, __keys=None, __local=None, __pre=None, __switchoff=False):
2494         "Enregistrement d'une commande individuelle"
2495         if __command is not None and __keys is not None and __local is not None and not self.__switchoff:
2496             if "self" in __keys: __keys.remove("self")
2497             self.__logSerie.append( (str(__command), __keys, __local, __pre, __switchoff) )
2498             if __switchoff:
2499                 self.__switchoff = True
2500         if not __switchoff:
2501             self.__switchoff = False
2502
2503     def dump(self, __filename=None, __format="TUI", __upa=""):
2504         "Restitution normalisée des commandes"
2505         if __format in self.__viewers:
2506             __formater = self.__viewers[__format](self.__name, self.__objname, self.__logSerie)
2507         else:
2508             raise ValueError("Dumping as \"%s\" is not available"%__format)
2509         return __formater.dump(__filename, __upa)
2510
2511     def load(self, __filename=None, __content=None, __object=None, __format="TUI"):
2512         "Chargement normalisé des commandes"
2513         if __format in self.__loaders:
2514             __formater = self.__loaders[__format]()
2515         else:
2516             raise ValueError("Loading as \"%s\" is not available"%__format)
2517         return __formater.load(__filename, __content, __object)
2518
2519 # ==============================================================================
2520 def MultiFonction(
2521         __xserie,
2522         _extraArguments = None,
2523         _sFunction      = lambda x: x,
2524         _mpEnabled      = False,
2525         _mpWorkers      = None,
2526         ):
2527     """
2528     Pour une liste ordonnée de vecteurs en entrée, renvoie en sortie la liste
2529     correspondante de valeurs de la fonction en argument
2530     """
2531     # Vérifications et définitions initiales
2532     # logging.debug("MULTF Internal multifonction calculations begin with function %s"%(_sFunction.__name__,))
2533     if not PlatformInfo.isIterable( __xserie ):
2534         raise TypeError("MultiFonction not iterable unkown input type: %s"%(type(__xserie),))
2535     if _mpEnabled:
2536         if (_mpWorkers is None) or (_mpWorkers is not None and _mpWorkers < 1):
2537             __mpWorkers = None
2538         else:
2539             __mpWorkers = int(_mpWorkers)
2540         try:
2541             import multiprocessing
2542             __mpEnabled = True
2543         except ImportError:
2544             __mpEnabled = False
2545     else:
2546         __mpEnabled = False
2547         __mpWorkers = None
2548     #
2549     # Calculs effectifs
2550     if __mpEnabled:
2551         _jobs = __xserie
2552         # logging.debug("MULTF Internal multiprocessing calculations begin : evaluation of %i point(s)"%(len(_jobs),))
2553         with multiprocessing.Pool(__mpWorkers) as pool:
2554             __multiHX = pool.map( _sFunction, _jobs )
2555             pool.close()
2556             pool.join()
2557         # logging.debug("MULTF Internal multiprocessing calculation end")
2558     else:
2559         # logging.debug("MULTF Internal monoprocessing calculation begin")
2560         __multiHX = []
2561         if _extraArguments is None:
2562             for __xvalue in __xserie:
2563                 __multiHX.append( _sFunction( __xvalue ) )
2564         elif _extraArguments is not None and isinstance(_extraArguments, (list, tuple, map)):
2565             for __xvalue in __xserie:
2566                 __multiHX.append( _sFunction( __xvalue, *_extraArguments ) )
2567         elif _extraArguments is not None and isinstance(_extraArguments, dict):
2568             for __xvalue in __xserie:
2569                 __multiHX.append( _sFunction( __xvalue, **_extraArguments ) )
2570         else:
2571             raise TypeError("MultiFonction extra arguments unkown input type: %s"%(type(_extraArguments),))
2572         # logging.debug("MULTF Internal monoprocessing calculation end")
2573     #
2574     # logging.debug("MULTF Internal multifonction calculations end")
2575     return __multiHX
2576
2577 # ==============================================================================
2578 if __name__ == "__main__":
2579     print("\n AUTODIAGNOSTIC\n")