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