Salome HOME
Adding multi-functions input capabilities (4)
[modules/adao.git] / src / daComposant / daCore / BasicObjects.py
index 41c5f106306e49da36699f7c9c7ea240fea51e15..e9141206088206595b7563f8e48b5a58c5d2a176 100644 (file)
@@ -22,8 +22,6 @@
 
 """
     Définit les outils généraux élémentaires.
-
-    Ce module est destiné à être appelée par AssimilationStudy.
 """
 __author__ = "Jean-Philippe ARGAUD"
 __all__ = []
@@ -33,11 +31,12 @@ import sys
 import logging
 import copy
 import numpy
+from functools import partial
 from daCore import Persistence
 from daCore import PlatformInfo
 from daCore import Interfaces
 from daCore import Templates
-from daCore.Interfaces import ImportFromScript
+from daCore.Interfaces import ImportFromScript, ImportFromFile
 
 # ==============================================================================
 class CacheManager(object):
@@ -109,21 +108,28 @@ class Operator(object):
     NbCallsOfCached = 0
     CM = CacheManager()
     #
-    def __init__(self, fromMethod=None, fromMatrix=None, avoidingRedundancy = True):
+    def __init__(self, fromMethod=None, fromMatrix=None, avoidingRedundancy = True, inputAsMultiFunction = False):
         """
-        On construit un objet de ce type en fournissant à l'aide de l'un des
-        deux mots-clé, soit une fonction python, soit une matrice.
+        On construit un objet de ce type en fournissant, à l'aide de l'un des
+        deux mots-clé, soit une fonction ou un multi-fonction python, soit une
+        matrice.
         Arguments :
         - fromMethod : argument de type fonction Python
         - fromMatrix : argument adapté au constructeur numpy.matrix
         - avoidingRedundancy : évite ou pas les calculs redondants
+        - inputAsMultiFunction : fonction explicitement définie ou pas en multi-fonction
         """
         self.__NbCallsAsMatrix, self.__NbCallsAsMethod, self.__NbCallsOfCached = 0, 0, 0
         self.__AvoidRC = bool( avoidingRedundancy )
-        if   fromMethod is not None:
+        self.__inputAsMF = bool( inputAsMultiFunction )
+        if   fromMethod is not None and self.__inputAsMF:
             self.__Method = fromMethod # logtimer(fromMethod)
             self.__Matrix = None
             self.__Type   = "Method"
+        elif fromMethod is not None and not self.__inputAsMF:
+            self.__Method = partial( MultiFonction, _sFunction=fromMethod)
+            self.__Matrix = None
+            self.__Type   = "Method"
         elif fromMatrix is not None:
             self.__Method = None
             self.__Matrix = numpy.matrix( fromMatrix, numpy.float )
@@ -148,7 +154,7 @@ class Operator(object):
         "Renvoie le type"
         return self.__Type
 
-    def appliedTo(self, xValue, HValue = None):
+    def appliedTo(self, xValue, HValue = None, argsAsSerie = False):
         """
         Permet de restituer le résultat de l'application de l'opérateur à un
         argument xValue. Cette méthode se contente d'appliquer, son argument
@@ -156,30 +162,61 @@ class Operator(object):
         Arguments :
         - xValue : argument adapté pour appliquer l'opérateur
         """
-        if HValue is not None:
-            HxValue = numpy.asmatrix( numpy.ravel( HValue ) ).T
-            if self.__AvoidRC:
-                Operator.CM.storeValueInX(xValue,HxValue)
+        if argsAsSerie:
+            _xValue = xValue
+            _HValue = HValue
         else:
-            if self.__AvoidRC:
-                __alreadyCalculated, __HxV = Operator.CM.wasCalculatedIn(xValue)
-            else:
-                __alreadyCalculated = False
-            #
-            if __alreadyCalculated:
-                self.__addOneCacheCall()
-                HxValue = __HxV
+            _xValue = (xValue,)
+            if HValue is not None:
+                _HValue = (HValue,)
             else:
-                if self.__Matrix is not None:
-                    self.__addOneMatrixCall()
-                    HxValue = self.__Matrix * xValue
-                else:
-                    self.__addOneMethodCall()
-                    HxValue = self.__Method( xValue )
+                _HValue = HValue
+        PlatformInfo.isIterable( _xValue, True, " in Operator.appliedTo" )
+        #
+        if _HValue is not None:
+            assert len(_xValue) == len(_HValue), "Incompatible number of elements in xValue and HValue"
+            HxValue = []
+            for i in range(len(_HValue)):
+                HxValue.append( numpy.asmatrix( numpy.ravel( _HValue[i] ) ).T )
+                if self.__AvoidRC:
+                    Operator.CM.storeValueInX(_xValue[i],HxValue[-1])
+        else:
+            HxValue = []
+            _xserie = []
+            _hindex = []
+            for i, xv in enumerate(_xValue):
                 if self.__AvoidRC:
-                    Operator.CM.storeValueInX(xValue,HxValue)
+                    __alreadyCalculated, __HxV = Operator.CM.wasCalculatedIn(xv)
+                else:
+                    __alreadyCalculated = False
+                #
+                if __alreadyCalculated:
+                    self.__addOneCacheCall()
+                    _hv = __HxV
+                else:
+                    if self.__Matrix is not None:
+                        self.__addOneMatrixCall()
+                        _hv = self.__Matrix * xv
+                    else:
+                        self.__addOneMethodCall()
+                        _xserie.append( xv )
+                        _hindex.append(  i )
+                        _hv = None
+                HxValue.append( _hv )
+            #
+            if len(_xserie)>0 and self.__Matrix is None:
+                _hserie = self.__Method( _xserie ) # Calcul MF
+                if not hasattr(_hserie, "pop"):
+                    raise TypeError("The user input multi-function doesn't seem to return sequence results, behaving like a mono-function. It has to be checked.")
+                for i in _hindex:
+                    _xv = _xserie.pop(0)
+                    _hv = _hserie.pop(0)
+                    HxValue[i] = _hv
+                    if self.__AvoidRC:
+                        Operator.CM.storeValueInX(_xv,_hv)
         #
-        return HxValue
+        if argsAsSerie: return HxValue
+        else:           return HxValue[-1]
 
     def appliedControledFormTo(self, paire ):
         """
@@ -203,7 +240,7 @@ class Operator(object):
             self.__addOneMethodCall()
             return self.__Method( xValue )
 
-    def appliedInXTo(self, paire ):
+    def appliedInXTo(self, paires, argsAsSerie = False ):
         """
         Permet de restituer le résultat de l'application de l'opérateur à un
         argument xValue, sachant que l'opérateur est valable en xNominal.
@@ -216,14 +253,22 @@ class Operator(object):
           est construit pour etre ensuite appliqué
         - xValue : argument adapté pour appliquer l'opérateur
         """
-        assert len(paire) == 2, "Incorrect number of arguments"
-        xNominal, xValue = paire
+        if argsAsSerie: _nxValue = paires
+        else:           _nxValue = (paires,)
+        PlatformInfo.isIterable( _nxValue, True, " in Operator.appliedInXTo" )
+        #
         if self.__Matrix is not None:
-            self.__addOneMatrixCall()
-            return self.__Matrix * xValue
+            HxValue = []
+            for paire in _nxValue:
+                _xNominal, _xValue = paire
+                self.__addOneMatrixCall()
+                HxValue.append( self.__Matrix * _xValue )
         else:
-            self.__addOneMethodCall()
-            return self.__Method( (xNominal, xValue) )
+            self.__addOneMethodCall( len(_nxValue) )
+            HxValue = self.__Method( _nxValue ) # Calcul MF
+        #
+        if argsAsSerie: return HxValue
+        else:           return HxValue[-1]
 
     def asMatrix(self, ValueForMethodForm = "UnknownVoidValue"):
         """
@@ -295,6 +340,7 @@ class FullOperator(object):
                  asDict           = None, # Parameters
                  appliedInX       = None,
                  avoidRC          = True,
+                 inputAsMF        = False,# Fonction(s) as Multi-Functions
                  scheduledBy      = None,
                  toBeChecked      = False,
                  ):
@@ -388,6 +434,7 @@ class FullOperator(object):
             if "withLenghtOfRedundancy"    not in __Function: __Function["withLenghtOfRedundancy"]    = -1
             if "withmpEnabled"             not in __Function: __Function["withmpEnabled"]             = False
             if "withmpWorkers"             not in __Function: __Function["withmpWorkers"]             = None
+            if "withmfEnabled"             not in __Function: __Function["withmfEnabled"]             = inputAsMF
             from daNumerics.ApproximatedDerivatives import FDApproximation
             FDA = FDApproximation(
                 Function              = __Function["Direct"],
@@ -399,29 +446,28 @@ class FullOperator(object):
                 lenghtOfRedundancy    = __Function["withLenghtOfRedundancy"],
                 mpEnabled             = __Function["withmpEnabled"],
                 mpWorkers             = __Function["withmpWorkers"],
+                mfEnabled             = __Function["withmfEnabled"],
                 )
-            self.__FO["Direct"]  = Operator( fromMethod = FDA.DirectOperator,  avoidingRedundancy = avoidRC )
-            self.__FO["Tangent"] = Operator( fromMethod = FDA.TangentOperator, avoidingRedundancy = avoidRC )
-            self.__FO["Adjoint"] = Operator( fromMethod = FDA.AdjointOperator, avoidingRedundancy = avoidRC )
+            self.__FO["Direct"]  = Operator( fromMethod = FDA.DirectOperator,  avoidingRedundancy = avoidRC, inputAsMultiFunction = inputAsMF)
+            self.__FO["Tangent"] = Operator( fromMethod = FDA.TangentOperator, avoidingRedundancy = avoidRC, inputAsMultiFunction = inputAsMF )
+            self.__FO["Adjoint"] = Operator( fromMethod = FDA.AdjointOperator, avoidingRedundancy = avoidRC, inputAsMultiFunction = inputAsMF )
         elif isinstance(__Function, dict) and \
                 ("Direct" in __Function) and ("Tangent" in __Function) and ("Adjoint" in __Function) and \
                 (__Function["Direct"] is not None) and (__Function["Tangent"] is not None) and (__Function["Adjoint"] is not None):
-            self.__FO["Direct"]  = Operator( fromMethod = __Function["Direct"],  avoidingRedundancy = avoidRC )
-            self.__FO["Tangent"] = Operator( fromMethod = __Function["Tangent"], avoidingRedundancy = avoidRC )
-            self.__FO["Adjoint"] = Operator( fromMethod = __Function["Adjoint"], avoidingRedundancy = avoidRC )
+            self.__FO["Direct"]  = Operator( fromMethod = __Function["Direct"],  avoidingRedundancy = avoidRC, inputAsMultiFunction = inputAsMF )
+            self.__FO["Tangent"] = Operator( fromMethod = __Function["Tangent"], avoidingRedundancy = avoidRC, inputAsMultiFunction = inputAsMF )
+            self.__FO["Adjoint"] = Operator( fromMethod = __Function["Adjoint"], avoidingRedundancy = avoidRC, inputAsMultiFunction = inputAsMF )
         elif asMatrix is not None:
             __matrice = numpy.matrix( __Matrix, numpy.float )
-            self.__FO["Direct"]  = Operator( fromMatrix = __matrice,   avoidingRedundancy = avoidRC )
-            self.__FO["Tangent"] = Operator( fromMatrix = __matrice,   avoidingRedundancy = avoidRC )
-            self.__FO["Adjoint"] = Operator( fromMatrix = __matrice.T, avoidingRedundancy = avoidRC )
+            self.__FO["Direct"]  = Operator( fromMatrix = __matrice,   avoidingRedundancy = avoidRC, inputAsMultiFunction = inputAsMF )
+            self.__FO["Tangent"] = Operator( fromMatrix = __matrice,   avoidingRedundancy = avoidRC, inputAsMultiFunction = inputAsMF )
+            self.__FO["Adjoint"] = Operator( fromMatrix = __matrice.T, avoidingRedundancy = avoidRC, inputAsMultiFunction = inputAsMF )
             del __matrice
         else:
             raise ValueError("Improperly defined observation operator, it requires at minima either a matrix, a Direct for approximate derivatives or a Tangent/Adjoint pair.")
         #
         if __appliedInX is not None:
             self.__FO["AppliedInX"] = {}
-            if not isinstance(__appliedInX, dict):
-                raise ValueError("Error: observation operator defined by \"AppliedInX\" need a dictionary as argument.")
             for key in list(__appliedInX.keys()):
                 if type( __appliedInX[key] ) is type( numpy.matrix([]) ):
                     # Pour le cas où l'on a une vraie matrice
@@ -510,6 +556,7 @@ class Algorithm(object):
         self.StoredVariables["GradientOfCostFunctionJb"]             = Persistence.OneVector(name = "GradientOfCostFunctionJb")
         self.StoredVariables["GradientOfCostFunctionJo"]             = Persistence.OneVector(name = "GradientOfCostFunctionJo")
         self.StoredVariables["CurrentState"]                         = Persistence.OneVector(name = "CurrentState")
+        self.StoredVariables["PredictedState"]                       = Persistence.OneVector(name = "PredictedState")
         self.StoredVariables["Analysis"]                             = Persistence.OneVector(name = "Analysis")
         self.StoredVariables["IndexOfOptimum"]                       = Persistence.OneIndex(name = "IndexOfOptimum")
         self.StoredVariables["CurrentOptimum"]                       = Persistence.OneVector(name = "CurrentOptimum")
@@ -541,7 +588,7 @@ class Algorithm(object):
         self.__setParameters(Parameters)
         #
         # Corrections et complements
-        def __test_vvalue( argument, variable, argname):
+        def __test_vvalue(argument, variable, argname):
             if argument is None:
                 if variable in self.__required_inputs["RequiredInputValues"]["mandatory"]:
                     raise ValueError("%s %s vector %s has to be properly defined!"%(self._name,argname,variable))
@@ -551,9 +598,11 @@ class Algorithm(object):
                     logging.debug("%s %s vector %s is not set, but is not required."%(self._name,argname,variable))
             else:
                 logging.debug("%s %s vector %s is set, and its size is %i."%(self._name,argname,variable,numpy.array(argument).size))
+            return 0
         __test_vvalue( Xb, "Xb", "Background or initial state" )
         __test_vvalue( Y,  "Y",  "Observation" )
-        def __test_cvalue( argument, variable, argname):
+        #
+        def __test_cvalue(argument, variable, argname):
             if argument is None:
                 if variable in self.__required_inputs["RequiredInputValues"]["mandatory"]:
                     raise ValueError("%s %s error covariance matrix %s has to be properly defined!"%(self._name,argname,variable))
@@ -563,6 +612,7 @@ class Algorithm(object):
                     logging.debug("%s %s error covariance matrix %s is not set, but is not required."%(self._name,argname,variable))
             else:
                 logging.debug("%s %s error covariance matrix %s is set."%(self._name,argname,variable))
+            return 0
         __test_cvalue( R, "R", "Observation" )
         __test_cvalue( B, "B", "Background" )
         __test_cvalue( Q, "Q", "Evolution" )
@@ -609,6 +659,10 @@ class Algorithm(object):
         logging.debug("%s Terminé", self._name)
         return 0
 
+    def _toStore(self, key):
+        "True if in StoreSupplementaryCalculations, else False"
+        return key in self._parameters["StoreSupplementaryCalculations"]
+
     def get(self, key=None):
         """
         Renvoie l'une des variables stockées identifiée par la clé, ou le
@@ -824,6 +878,9 @@ class AlgorithmAndParameters(object):
         "Permet de lancer le calcul d'assimilation"
         if FileName is None or not os.path.exists(FileName):
             raise ValueError("a YACS file name has to be given for YACS execution.\n")
+        else:
+            __file    = os.path.abspath(FileName)
+            logging.debug("The YACS file name is \"%s\"."%__file)
         if not PlatformInfo.has_salome or \
             not PlatformInfo.has_yacs or \
             not PlatformInfo.has_adao:
@@ -840,13 +897,13 @@ class AlgorithmAndParameters(object):
         xmlLoader = loader.YACSLoader()
         xmlLoader.registerProcCataLoader()
         try:
-            catalogAd = r.loadCatalog("proc", os.path.abspath(FileName))
+            catalogAd = r.loadCatalog("proc", __file)
             r.addCatalog(catalogAd)
         except:
             pass
 
         try:
-            p = xmlLoader.load(os.path.abspath(FileName))
+            p = xmlLoader.load(__file)
         except IOError as ex:
             print("The YACS XML schema file can not be loaded: %s"%(ex,))
 
@@ -1095,6 +1152,45 @@ class AlgorithmAndParameters(object):
         #
         return 1
 
+# ==============================================================================
+class RegulationAndParameters(object):
+    """
+    Classe générale d'interface d'action pour la régulation et ses paramètres
+    """
+    def __init__(self,
+                 name               = "GenericRegulation",
+                 asAlgorithm        = None,
+                 asDict             = None,
+                 asScript           = None,
+                ):
+        """
+        """
+        self.__name       = str(name)
+        self.__P          = {}
+        #
+        if asAlgorithm is None and asScript is not None:
+            __Algo = ImportFromScript(asScript).getvalue( "Algorithm" )
+        else:
+            __Algo = asAlgorithm
+        #
+        if asDict is None and asScript is not None:
+            __Dict = ImportFromScript(asScript).getvalue( self.__name, "Parameters" )
+        else:
+            __Dict = asDict
+        #
+        if __Dict is not None:
+            self.__P.update( dict(__Dict) )
+        #
+        if __Algo is not None:
+            self.__P.update( {"Algorithm":self.__A} )
+
+    def get(self, key = None):
+        "Vérifie l'existence d'une clé de variable ou de paramètres"
+        if key in self.__P:
+            return self.__P[key]
+        else:
+            return self.__P
+
 # ==============================================================================
 class DataObserver(object):
     """
@@ -1176,6 +1272,9 @@ class State(object):
                  asVector           = None,
                  asPersistentVector = None,
                  asScript           = None,
+                 asDataFile         = None,
+                 colNames           = None,
+                 colMajor           = False,
                  scheduledBy        = None,
                  toBeChecked        = False,
                 ):
@@ -1190,6 +1289,14 @@ class State(object):
           nommée "name", la variable est de type "asVector" (par défaut) ou
           "asPersistentVector" selon que l'une de ces variables est placée à
           "True".
+        - asDataFile : si un ou plusieurs fichiers valides sont donnés
+          contenant des valeurs en colonnes, elles-mêmes nommées "colNames"
+          (s'il n'y a pas de nom de colonne indiquée, on cherche une colonne
+          nommée "name"), on récupère les colonnes et on les range ligne après
+          ligne (colMajor=False) ou colonne après colonne (colMajor=True). La
+          variable résultante est de type "asVector" (par défaut) ou
+          "asPersistentVector" selon que l'une de ces variables est placée à
+          "True".
         """
         self.__name       = str(name)
         self.__check      = bool(toBeChecked)
@@ -1205,6 +1312,26 @@ class State(object):
                 __Series = ImportFromScript(asScript).getvalue( self.__name )
             else:
                 __Vector = ImportFromScript(asScript).getvalue( self.__name )
+        elif asDataFile is not None:
+            __Vector, __Series = None, None
+            if asPersistentVector:
+                if colNames is not None:
+                    __Series = ImportFromFile(asDataFile).getvalue( colNames )[1]
+                else:
+                    __Series = ImportFromFile(asDataFile).getvalue( [self.__name,] )[1]
+                if bool(colMajor) and not ImportFromFile(asDataFile).getformat() == "application/numpy.npz":
+                    __Series = numpy.transpose(__Series)
+                elif not bool(colMajor) and ImportFromFile(asDataFile).getformat() == "application/numpy.npz":
+                    __Series = numpy.transpose(__Series)
+            else:
+                if colNames is not None:
+                    __Vector = ImportFromFile(asDataFile).getvalue( colNames )[1]
+                else:
+                    __Vector = ImportFromFile(asDataFile).getvalue( [self.__name,] )[1]
+                if bool(colMajor):
+                    __Vector = numpy.ravel(__Vector, order = "F")
+                else:
+                    __Vector = numpy.ravel(__Vector, order = "C")
         else:
             __Vector, __Series = asVector, asPersistentVector
         #
@@ -1220,7 +1347,6 @@ class State(object):
                 if isinstance(__Series, str): __Series = eval(__Series)
                 for member in __Series:
                     self.__V.store( numpy.matrix( numpy.asmatrix(member).A1, numpy.float ).T )
-                import sys ; sys.stdout.flush()
             else:
                 self.__V = __Series
             if isinstance(self.__V.shape, (tuple, list)):
@@ -1520,7 +1646,7 @@ class Covariance(object):
 
     def __mul__(self, other):
         "x.__mul__(y) <==> x*y"
-        if   self.ismatrix() and isinstance(other,numpy.matrix):
+        if   self.ismatrix() and isinstance(other, (int, numpy.matrix, float)):
             return self.__C * other
         elif self.ismatrix() and isinstance(other, (list, numpy.ndarray, tuple)):
             if numpy.ravel(other).size == self.shape[1]: # Vecteur
@@ -1550,7 +1676,7 @@ class Covariance(object):
 
     def __rmul__(self, other):
         "x.__rmul__(y) <==> y*x"
-        if self.ismatrix() and isinstance(other,numpy.matrix):
+        if self.ismatrix() and isinstance(other, (int, numpy.matrix, float)):
             return other * self.__C
         elif self.isvector() and isinstance(other,numpy.matrix):
             if numpy.ravel(other).size == self.shape[0]: # Vecteur
@@ -1558,7 +1684,7 @@ class Covariance(object):
             elif numpy.asmatrix(other).shape[1] == self.shape[0]: # Matrice
                 return numpy.asmatrix(numpy.array(other) * self.__C)
             else:
-                raise ValueError("operands could not be broadcast together with shapes %s %s in %s matrix"%(self.shape,numpy.ravel(other).shape,self.__name))
+                raise ValueError("operands could not be broadcast together with shapes %s %s in %s matrix"%(numpy.ravel(other).shape,self.shape,self.__name))
         elif self.isscalar() and isinstance(other,numpy.matrix):
             return other * self.__C
         elif self.isobject():
@@ -1595,15 +1721,13 @@ class CaseLogger(object):
         self.__logSerie = []
         self.__switchoff = False
         self.__viewers = {
-            "TUI":Interfaces._TUIViewer,
-            "DCT":Interfaces._DCTViewer,
-            "SCD":Interfaces._SCDViewer,
+            "TUI" :Interfaces._TUIViewer,
+            "SCD" :Interfaces._SCDViewer,
             "YACS":Interfaces._YACSViewer,
             }
         self.__loaders = {
-            "TUI":Interfaces._TUIViewer,
-            "EPD":Interfaces._EPDViewer,
-            "DCT":Interfaces._DCTViewer,
+            "TUI" :Interfaces._TUIViewer,
+            "COM" :Interfaces._COMViewer,
             }
         if __addViewers is not None:
             self.__viewers.update(dict(__addViewers))
@@ -1636,6 +1760,21 @@ class CaseLogger(object):
             raise ValueError("Loading as \"%s\" is not available"%__format)
         return __formater.load(__filename, __content, __object)
 
+# ==============================================================================
+def MultiFonction( __xserie, _sFunction = lambda x: x ):
+    """
+    Pour une liste ordonnée de vecteurs en entrée, renvoie en sortie la liste
+    correspondante de valeurs de la fonction en argument
+    """
+    if not PlatformInfo.isIterable( __xserie ):
+        raise ValueError("MultiFonction not iterable unkown input type: %s"%(type(__xserie),))
+    #
+    __multiHX = []
+    for __xvalue in __xserie:
+        __multiHX.append( _sFunction( __xvalue ) )
+    #
+    return __multiHX
+
 # ==============================================================================
 def CostFunction3D(_x,
                    _Hm  = None,  # Pour simuler Hm(x) : HO["Direct"].appliedTo